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 */; }; 0958FBB9218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */; };
0958FBBB218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */; }; 0958FBBB218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */; };
0958FBBD218B03CA00E0CBD8 /* InstantPageDetailsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */; }; 0958FBBD218B03CA00E0CBD8 /* InstantPageDetailsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */; };
09619B8E21A34C0100493558 /* InstantPageScrollableNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */; };
096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; }; 096C98BA21787A5C00C211FF /* LegacyBridgeAudio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */; };
096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; }; 096C98BF21787C6700C211FF /* TGBridgeAudioEncoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */; };
096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; }; 096C98C021787C6700C211FF /* TGBridgeAudioEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */; };
@ -1094,6 +1095,7 @@
0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageFeedbackItem.swift; sourceTree = "<group>"; }; 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>"; }; 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageFeedbackNode.swift; sourceTree = "<group>"; };
0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageDetailsNode.swift; sourceTree = "<group>"; }; 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageDetailsNode.swift; sourceTree = "<group>"; };
09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPageScrollableNode.swift; sourceTree = "<group>"; };
096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBridgeAudio.swift; sourceTree = "<group>"; }; 096C98B921787A5C00C211FF /* LegacyBridgeAudio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBridgeAudio.swift; sourceTree = "<group>"; };
096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGBridgeAudioEncoder.m; sourceTree = "<group>"; }; 096C98BB21787C6600C211FF /* TGBridgeAudioEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGBridgeAudioEncoder.m; sourceTree = "<group>"; };
096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = "<group>"; }; 096C98BC21787C6600C211FF /* TGBridgeAudioEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGBridgeAudioEncoder.h; sourceTree = "<group>"; };
@ -3117,6 +3119,7 @@
0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */, 0958FBBC218B03CA00E0CBD8 /* InstantPageDetailsNode.swift */,
0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */, 0958FBB8218AD6AF00E0CBD8 /* InstantPageFeedbackItem.swift */,
0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */, 0958FBBA218AD6BC00E0CBD8 /* InstantPageFeedbackNode.swift */,
09619B8D21A34C0100493558 /* InstantPageScrollableNode.swift */,
); );
name = "Instant Page"; name = "Instant Page";
sourceTree = "<group>"; sourceTree = "<group>";
@ -5108,6 +5111,7 @@
D0EC6D231EB9F58800EBF1C3 /* StickerResources.swift in Sources */, D0EC6D231EB9F58800EBF1C3 /* StickerResources.swift in Sources */,
09C9EA3821A044B500E90146 /* StringForDuration.swift in Sources */, 09C9EA3821A044B500E90146 /* StringForDuration.swift in Sources */,
D0EC6D241EB9F58800EBF1C3 /* CachedResourceRepresentations.swift in Sources */, D0EC6D241EB9F58800EBF1C3 /* CachedResourceRepresentations.swift in Sources */,
09619B8E21A34C0100493558 /* InstantPageScrollableNode.swift in Sources */,
D01BAA201ECC9A2500295217 /* CallListNodeLocation.swift in Sources */, D01BAA201ECC9A2500295217 /* CallListNodeLocation.swift in Sources */,
D0EC6D251EB9F58800EBF1C3 /* FetchCachedRepresentations.swift in Sources */, D0EC6D251EB9F58800EBF1C3 /* FetchCachedRepresentations.swift in Sources */,
D0EC6D261EB9F58800EBF1C3 /* TransformOutgoingMessageMedia.swift in Sources */, D0EC6D261EB9F58800EBF1C3 /* TransformOutgoingMessageMedia.swift in Sources */,

View File

@ -80,6 +80,10 @@ final class AuthorizationSequenceCodeEntryController: ViewController {
self?.requestNextOption?() self?.requestNextOption?()
} }
self.controllerNode.updateNextEnabled = { [weak self] value in
self?.navigationItem.rightBarButtonItem?.isEnabled = value
}
if let (number, codeType, nextType, timeout) = self.data { if let (number, codeType, nextType, timeout) = self.data {
self.controllerNode.updateData(number: number, codeType: codeType, nextType: nextType, timeout: timeout) 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 loginWithCode: ((String) -> Void)?
var requestNextOption: (() -> Void)? var requestNextOption: (() -> Void)?
var requestAnotherOption: (() -> Void)? var requestAnotherOption: (() -> Void)?
var updateNextEnabled: ((Bool) -> Void)?
var inProgress: Bool = false { var inProgress: Bool = false {
didSet { didSet {
@ -188,6 +189,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
func updateCode(_ code: String) { func updateCode(_ code: String) {
self.codeField.textField.text = code self.codeField.textField.text = code
self.codeFieldTextChanged(self.codeField.textField)
if let codeType = self.codeType { if let codeType = self.codeType {
var codeLength: Int32? var codeLength: Int32?
switch codeType { switch codeType {
@ -303,6 +305,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
} }
@objc func codeFieldTextChanged(_ textField: UITextField) { @objc func codeFieldTextChanged(_ textField: UITextField) {
self.updateNextEnabled?(!(textField.text ?? "").isEmpty)
if let codeType = self.codeType { if let codeType = self.codeType {
var codeLength: Int32? var codeLength: Int32?
switch codeType { switch codeType {

View File

@ -277,11 +277,11 @@ public final class AuthorizationSequenceController: NavigationController {
case .generic: case .generic:
text = strongSelf.strings.Login_UnknownError text = strongSelf.strings.Login_UnknownError
case .codeExpired: case .codeExpired:
text = strongSelf.strings.Login_CodeExpired
let account = strongSelf.account let account = strongSelf.account
let _ = (strongSelf.account.postbox.transaction { transaction -> Void in let _ = (strongSelf.account.postbox.transaction { transaction -> Void in
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty)) transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .empty))
}).start() }).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)) 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.statusBar.statusBarStyle = self.theme.statusBarStyle
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) 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) { required init(coder aDecoder: NSCoder) {

View File

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

View File

@ -4915,7 +4915,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UID
if let applicationContext = self.account.applicationContext as? TelegramApplicationContext { 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 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 { 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() 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.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)) 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) transition.updateFrame(node: self.loadingNode, frame: contentBounds)
if let restrictedNode = self.restrictedNode { 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 { if let containerNode = self.containerNode {
contentBottomInset += 8.0 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)) 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

@ -345,10 +345,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.dateNode.attributedText = nil self.dateNode.attributedText = nil
} }
//self.deleteButton.isHidden = !canDelete
self.requestLayout?(.immediate) self.requestLayout?(.immediate)
} }
self.deleteButton.isHidden = origin == nil
} }
func setMessage(_ message: Message) { func setMessage(_ message: Message) {
@ -695,7 +695,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
if let strongSelf = self { 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 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 { 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) strongSelf.controllerInteraction?.presentController(openInController, nil)

View File

@ -195,7 +195,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
guard let strongSelf = self else { guard let strongSelf = self else {
return 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,7 +513,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
if secretBeginTimeAndTimeout?.0 != nil { if secretBeginTimeAndTimeout?.0 != nil {
progressRequired = true progressRequired = true
} else if let fetchStatus = self.fetchStatus { } else if let fetchStatus = self.fetchStatus {
if case .Local = fetchStatus { switch fetchStatus {
case .Local:
if let file = media as? TelegramMediaFile, file.isVideo { if let file = media as? TelegramMediaFile, file.isVideo {
progressRequired = true progressRequired = true
} else if isSecretMedia { } else if isSecretMedia {
@ -525,10 +526,14 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
progressRequired = true progressRequired = true
} }
} }
case .Remote, .Fetching:
if let _ = webpage, let automaticDownload = self.automaticDownload, automaticDownload {
progressRequired = false
} else { } else {
progressRequired = true progressRequired = true
} }
} }
}
let radialStatusSize: CGFloat let radialStatusSize: CGFloat
if case .unconstrained = sizeCalculation { if case .unconstrained = sizeCalculation {

View File

@ -138,10 +138,12 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let contrainedTextSize = CGSize(width: maximumTextWidth, height: constrainedSize.height) 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 textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
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 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, { return (size, {
let node: ChatMessageReplyInfoNode let node: ChatMessageReplyInfoNode
@ -185,8 +187,8 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
node.imageNode = nil node.imageNode = nil
} }
titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: spacing), size: titleLayout.size) titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: spacing - textInsets.top), size: titleLayout.size)
textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: titleNode.frame.maxY + spacing), size: textLayout.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.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))) 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) 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)) 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 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? var statusFrame: CGRect?
if let statusSize = statusSize { 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) 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) statusFrame = statusFrame?.offsetBy(dx: layoutConstants.text.bubbleInsets.left, dy: layoutConstants.text.bubbleInsets.top)
var boundingSize: CGSize var boundingSize: CGSize
if let statusFrame = statusFrame { if let statusFrame = statusFrame {
boundingSize = textFrame.union(statusFrame).size boundingSize = textFrameWithoutInsets.union(statusFrame).size
} else { } else {
boundingSize = textFrame.size boundingSize = textFrameWithoutInsets.size
} }
boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right boundingSize.width += layoutConstants.text.bubbleInsets.left + layoutConstants.text.bubbleInsets.right
boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom boundingSize.height += layoutConstants.text.bubbleInsets.top + layoutConstants.text.bubbleInsets.bottom

View File

@ -1,5 +1,7 @@
import UIKit import UIKit
import Accelerate import Accelerate
import Display
import TelegramCore
private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? { private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? {
var sourceBuffer = vImage_Buffer() var sourceBuffer = vImage_Buffer()
@ -58,22 +60,57 @@ func imageHasTransparency(_ cgImage: CGImage) -> Bool {
return false 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 { guard cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else {
return false 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] let context = scaledContext(cgImage, maxSize: CGSize(width: 128.0, height: 128.0))
// if adjust_color_bias: if let cgImage = context.generateImage()?.cgImage, let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) {
// bias = ImageStat.Stat(thumb).mean[:3] var hasAlpha = false
// bias = [b - sum(bias)/3 for b in bias ] for i in 0 ..< 255 {
// for pixel in thumb.getdata(): if histogramBins[alphaBinIndex][i] > 0 {
// mu = sum(pixel)/3 hasAlpha = true
// SSE += sum((pixel[i] - mu - bias[i])*(pixel[i] - mu - bias[i]) for i in [0,1,2]) }
}
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 return false
} }

View File

@ -54,6 +54,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
private let resolveUrlDisposable = MetaDisposable() private let resolveUrlDisposable = MetaDisposable()
private let loadWebpageDisposable = MetaDisposable() private let loadWebpageDisposable = MetaDisposable()
private let loadProgress = ValuePromise<CGFloat>(1.0, ignoreRepeated: true)
private let loadProgressDisposable = MetaDisposable()
private let updateLayoutDisposable = MetaDisposable() private let updateLayoutDisposable = MetaDisposable()
private var themeReferenceDate: Date? 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) 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 { deinit {
self.hiddenMediaDisposable.dispose() self.hiddenMediaDisposable.dispose()
self.resolveUrlDisposable.dispose() self.resolveUrlDisposable.dispose()
self.loadWebpageDisposable.dispose() self.loadWebpageDisposable.dispose()
self.loadProgressDisposable.dispose()
} }
func update(settings: InstantPagePresentationSettings, strings: PresentationStrings) { func update(settings: InstantPagePresentationSettings, strings: PresentationStrings) {
@ -404,6 +413,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
var embedIndex = -1 var embedIndex = -1
var detailsIndex = -1 var detailsIndex = -1
var previousDetailsNode: InstantPageDetailsNode?
for item in self.currentLayoutItemsWithNodes { for item in self.currentLayoutItemsWithNodes {
itemIndex += 1 itemIndex += 1
if item is InstantPageWebEmbedItem { if item is InstantPageWebEmbedItem {
@ -462,9 +473,6 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
}, currentExpandedDetails: self.currentExpandedDetails) { }, currentExpandedDetails: self.currentExpandedDetails) {
newNode.frame = itemFrame newNode.frame = itemFrame
newNode.updateLayout(size: itemFrame.size, transition: transition) 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 { if let topNode = topNode {
self.scrollNode.insertSubnode(newNode, aboveSubnode: topNode) self.scrollNode.insertSubnode(newNode, aboveSubnode: topNode)
} else { } else {
@ -480,6 +488,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
strongSelf.updateVisibleItems(visibleBounds: strongSelf.scrollNode.view.bounds, animated: true) 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 { } else {
@ -676,8 +691,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
let itemFrame = effectiveFrameForItem(item) let itemFrame = effectiveFrameForItem(item)
if itemFrame.contains(location) { if itemFrame.contains(location) {
var contentOffset = CGPoint() var contentOffset = CGPoint()
if let item = item as? InstantPageTableItem { if let item = item as? InstantPageScrollableItem {
contentOffset = tableContentOffset(item: item) contentOffset = scrollableContentOffset(item: item)
} }
var itemRects = item.linkSelectionRects(at: location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) 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() var contentOffset = CGPoint()
for (_, itemNode) in self.visibleItemsWithNodes { 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 contentOffset = itemNode.contentOffset
break break
} }
@ -778,12 +793,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? { private func textItemAtLocation(_ location: CGPoint) -> (InstantPageTextItem, CGPoint)? {
if let currentLayout = self.currentLayout { if let currentLayout = self.currentLayout {
for item in currentLayout.items { for item in currentLayout.items {
let itemFrame = self.effectiveFrameForItem(item) let itemFrame = self.effectiveFrameForItem(item).insetBy(dx: -2.0, dy: -2.0)
if itemFrame.contains(location) { if itemFrame.contains(location) {
if let item = item as? InstantPageTextItem, item.selectable { if let item = item as? InstantPageTextItem, item.selectable {
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY)) return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
} else if let item = item as? InstantPageTableItem { } else if let item = item as? InstantPageScrollableItem {
let contentOffset = tableContentOffset(item: item) let contentOffset = scrollableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) { 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)) return (textItem, itemFrame.origin.offsetBy(dx: parentOffset.x - contentOffset.x, dy: parentOffset.y))
} }
@ -951,35 +966,12 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return return
} }
var cancelImpl: (() -> Void)? self.loadProgress.set(0.02)
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()
let resolveSignal = resolveUrl(account: self.account, url: url.url) let resolveSignal = resolveUrl(account: self.account, url: url.url)
|> afterDisposed { |> afterCompleted { [weak self] in
Queue.mainQueue().async { self?.loadProgress.set(0.07)
progressDisposable.dispose()
}
}
cancelImpl = { [weak self] in
self?.resolveUrlDisposable.set(nil)
} }
self.resolveUrlDisposable.set((resolveSignal |> deliverOnMainQueue).start(next: { [weak self] result in self.resolveUrlDisposable.set((resolveSignal |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self { if let strongSelf = self {
switch result { switch result {
@ -989,10 +981,20 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let anchorRange = externalUrl.range(of: "#") { if let anchorRange = externalUrl.range(of: "#") {
anchor = String(externalUrl[anchorRange.upperBound...]) anchor = String(externalUrl[anchorRange.upperBound...])
} }
strongSelf.loadWebpageDisposable.set((webpagePreview(account: strongSelf.account, url: externalUrl, webpageId: webpageId) |> deliverOnMainQueue).start(next: { webpage in strongSelf.loadWebpageDisposable.set((webpagePreviewWithProgress(account: strongSelf.account, url: externalUrl, webpageId: webpageId)
if let strongSelf = self, let webpage = webpage { |> 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)) 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 { } else {
openExternalUrl(account: strongSelf.account, url: externalUrl, presentationData: strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 }, applicationContext: strongSelf.account.telegramApplicationContext, navigationController: strongSelf.getNavigationController(), dismissInput: { openExternalUrl(account: strongSelf.account, url: externalUrl, presentationData: strongSelf.account.telegramApplicationContext.currentPresentationData.with { $0 }, applicationContext: strongSelf.account.telegramApplicationContext, navigationController: strongSelf.getNavigationController(), dismissInput: {
@ -1072,15 +1074,19 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
return return
} }
var entries: [InstantPageGalleryEntry] = []
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) var medias: [InstantPageMedia] = mediasFromItems(items)
medias = medias.filter { medias = medias.filter {
$0.media is TelegramMediaImage $0.media is TelegramMediaImage
} }
var entries: [InstantPageGalleryEntry] = []
for media in medias { 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)))) 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? var centralIndex: Int?
for i in 0 ..< entries.count { for i in 0 ..< entries.count {

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() var contentOffset = CGPoint()
for (_, itemNode) in self.visibleItemsWithNodes { 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 contentOffset = itemNode.contentOffset
break break
} }
@ -388,8 +388,8 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
if itemFrame.contains(location) { if itemFrame.contains(location) {
if let item = item as? InstantPageTextItem, item.selectable { if let item = item as? InstantPageTextItem, item.selectable {
return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY)) return (item, CGPoint(x: itemFrame.minX - item.frame.minX, y: itemFrame.minY - item.frame.minY))
} else if let item = item as? InstantPageTableItem { } else if let item = item as? InstantPageScrollableItem {
let contentOffset = tableContentOffset(item: item) let contentOffset = scrollableContentOffset(item: item)
if let (textItem, parentOffset) = item.textItemAtLocation(location.offsetBy(dx: -itemFrame.minX + contentOffset.x, dy: -itemFrame.minY)) { 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)) 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 highlightedBackgroundNode: ASDisplayNode
private let buttonNode: HighlightableButtonNode private let buttonNode: HighlightableButtonNode
private let arrowNode: InstantPageDetailsArrowNode private let arrowNode: InstantPageDetailsArrowNode
private let separatorNode: ASDisplayNode let separatorNode: ASDisplayNode
let contentNode: InstantPageDetailsContentNode let contentNode: InstantPageDetailsContentNode
private let updateExpanded: (Bool) -> Void private let updateExpanded: (Bool) -> Void
var expanded: Bool var expanded: Bool
var previousNode: InstantPageDetailsNode?
var requestLayoutUpdate: (() -> Void)? 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) { 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 { if highlighted {
strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.highlightedBackgroundNode.alpha = 1.0 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 { } else {
strongSelf.highlightedBackgroundNode.alpha = 0.0 strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) 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.alpha = 1.0
strongSelf.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) 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(.linkColor(UIColor(rgb: 0x5ac8fa)))
styleStack.push(.linkMarkerColor(UIColor(rgb: 0x5ac8fa, alpha: 0.2))) styleStack.push(.linkMarkerColor(UIColor(rgb: 0x5ac8fa, alpha: 0.2)))
styleStack.push(.fontSerif(false)) styleStack.push(.fontSerif(false))
//styleStack.push(.lineSpacingFactor(1.0))
credit = attributedStringForRichText(mediaCredit, styleStack: styleStack) credit = attributedStringForRichText(mediaCredit, styleStack: styleStack)
} else { } else {
credit = NSAttributedString(string: "") 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) 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 { } 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 }) 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 { } else {
preconditionFailure() preconditionFailure()
} }

View File

@ -17,6 +17,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
private let openMedia: (InstantPageMedia) -> Void private let openMedia: (InstantPageMedia) -> Void
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private let statusNode: RadialStatusNode
private let linkIconNode: ASImageNode private let linkIconNode: ASImageNode
private let pinNode: ChatMessageLiveLocationPositionNode private let pinNode: ChatMessageLiveLocationPositionNode
@ -38,6 +39,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
self.openMedia = openMedia self.openMedia = openMedia
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))
self.linkIconNode = ASImageNode() self.linkIconNode = ASImageNode()
self.pinNode = ChatMessageLiveLocationPositionNode() self.pinNode = ChatMessageLiveLocationPositionNode()
@ -80,6 +82,8 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference)) self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference))
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start()) 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 { if self.interactive {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) 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) { func update(strings: PresentationStrings, theme: InstantPageTheme) {
if self.theme.imageEmptyColor != theme.imageEmptyColor { if self.theme.imageTintColor != theme.imageTintColor {
self.theme = theme self.theme = theme
self.themeUpdated = true self.themeUpdated = true
self.setNeedsLayout() 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) 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 { } 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 imageSize = dimensions.aspectFilled(size)
let boundingSize = size let boundingSize = size
@ -155,6 +161,16 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
let (pinSize, pinApply) = makePinLayout(self.account, theme, nil, false) 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) 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() 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?) { func updateHiddenMedia(media: InstantPageMedia?) {
self.imageNode.isHidden = self.media == media self.imageNode.isHidden = self.media == media
self.statusNode.isHidden = self.imageNode.isHidden
} }
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) { @objc func tapGesture(_ recognizer: UITapGestureRecognizer) {

View File

@ -164,7 +164,7 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
case let .paragraph(text): case let .paragraph(text):
let styleStack = InstantPageTextStyleStack() let styleStack = InstantPageTextStyleStack()
setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false) 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) return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
case let .preformatted(text): case let .preformatted(text):
let styleStack = InstantPageTextStyleStack() let styleStack = InstantPageTextStyleStack()
@ -215,12 +215,12 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
} else { } else {
value = "\(i + 1)." 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 { if let textItem = textItem, let line = textItem.lines.first {
textItem.selectable = false textItem.selectable = false
maxIndexWidth = max(maxIndexWidth, line.frame.width) maxIndexWidth = max(maxIndexWidth, line.frame.width)
indexItems.append(textItem)
} }
indexItems.append(textItems.first!)
} else { } 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) 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) indexItems.append(shapeItem)
@ -614,11 +614,11 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
var contentSize: CGSize var contentSize: CGSize
let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size) let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size)
let item: InstantPageItem 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 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) 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 { } else {
item = InstantPageWebEmbedItem(frame: frame, url: url, html: html, enableScrolling: allowScrolling) 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 moreImage = generateTintedImage(image: UIImage(bundleImageName: "Instant View/MoreIcon"), color: .white)
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Instant View/ActionIcon"), 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 { final class InstantPageNavigationBar: ASDisplayNode {
private var strings: PresentationStrings private var strings: PresentationStrings
@ -17,7 +60,7 @@ final class InstantPageNavigationBar: ASDisplayNode {
private let arrowNode: ASImageNode private let arrowNode: ASImageNode
private let titleNode: ASTextNode private let titleNode: ASTextNode
private let progressNode: ASDisplayNode private let progressNode: InstantPageProgressNode
private let intrinsicMoreSize: CGSize private let intrinsicMoreSize: CGSize
private let intrinsicSmallMoreSize: CGSize private let intrinsicSmallMoreSize: CGSize
@ -65,8 +108,7 @@ final class InstantPageNavigationBar: ASDisplayNode {
self.titleNode = ASTextNode() self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 1 self.titleNode.maximumNumberOfLines = 1
self.progressNode = ASDisplayNode() self.progressNode = InstantPageProgressNode()
self.progressNode.backgroundColor = .white
super.init() 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) { func updateLayout(size: CGSize, minHeight: CGFloat, maxHeight: CGFloat, topInset: CGFloat, leftInset: CGFloat, rightInset: CGFloat, title: String?, pageProgress: CGFloat, transition: ContainedViewLayoutTransition) {
let progressHeight: CGFloat let progressHeight: CGFloat
if !topInset.isZero { if !topInset.isZero {
@ -183,6 +229,9 @@ final class InstantPageNavigationBar: ASDisplayNode {
transition.updateAlpha(node: self.actionButton, alpha: alphaFactor) 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))) 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? { 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 tableBorderWidth: CGFloat = 1.0
private let tableCornerRadius: CGFloat = 5.0 private let tableCornerRadius: CGFloat = 5.0
final class InstantPageTableItem: InstantPageItem { final class InstantPageTableItem: InstantPageScrollableItem {
var frame: CGRect var frame: CGRect
let totalWidth: CGFloat let totalWidth: CGFloat
let horizontalInset: CGFloat let horizontalInset: CGFloat
@ -104,7 +104,7 @@ final class InstantPageTableItem: InstantPageItem {
let theme: InstantPageTheme let theme: InstantPageTheme
let rtl: Bool let isRTL: Bool
fileprivate let cells: [InstantPageTableCellItem] fileprivate let cells: [InstantPageTableCellItem]
private let borderWidth: CGFloat private let borderWidth: CGFloat
@ -115,7 +115,11 @@ final class InstantPageTableItem: InstantPageItem {
self.borderWidth = borderWidth self.borderWidth = borderWidth
self.theme = theme self.theme = theme
self.cells = cells 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) { 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)? { 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 { func matchesNode(_ node: InstantPageNode) -> Bool {
if let node = node as? InstantPageTableNode { if let node = node as? InstantPageScrollableNode {
return node.item === self return node.item === self
} }
return false 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 { private struct TableRow {
var minColumnWidths: [Int : CGFloat] var minColumnWidths: [Int : CGFloat]
var maxColumnWidths: [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 { func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextStyleStack, url: InstantPageUrlItem? = nil) -> NSAttributedString {
switch text { switch text {
case .empty: case .empty:
@ -407,7 +484,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
let width: CGFloat let width: CGFloat
} }
let extentBuffer = UnsafeMutablePointer<RunStruct>.allocate(capacity: 1) 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 var callbacks = CTRunDelegateCallbacks(version: kCTRunDelegateVersion1, dealloc: { (pointer) in
}, getAscent: { (pointer) -> CGFloat in }, getAscent: { (pointer) -> CGFloat in
let d = pointer.assumingMemoryBound(to: RunStruct.self) let d = pointer.assumingMemoryBound(to: RunStruct.self)
@ -420,7 +497,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
return d.pointee.width return d.pointee.width
}) })
let delegate = CTRunDelegateCreate(&callbacks, extentBuffer) 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) return NSAttributedString(string: " ", attributes: attrDictionaryDelegate)
case let .anchor(text, name): case let .anchor(text, name):
styleStack.push(.anchor(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 { if string.length == 0 {
return (nil, [], CGSize()) return (nil, [], CGSize())
} }
@ -470,12 +547,15 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
var lastIndex: CFIndex = 0 var lastIndex: CFIndex = 0
var currentLineOrigin = CGPoint() var currentLineOrigin = CGPoint()
var maxLineWidth: CGFloat = 0.0
var maxImageHeight: CGFloat = 0.0 var maxImageHeight: CGFloat = 0.0
var extraDescent: CGFloat = 0.0 var extraDescent: CGFloat = 0.0
let text = string.string let text = string.string
var indexOffset: CFIndex? var indexOffset: CFIndex?
while true { while true {
let currentMaxWidth = boundingWidth - currentLineOrigin.x var workingLineOrigin = currentLineOrigin
let currentMaxWidth = boundingWidth - workingLineOrigin.x
let lineCharacterCount: CFIndex let lineCharacterCount: CFIndex
var hadIndexOffset = false var hadIndexOffset = false
if minimizeWidth { if minimizeWidth {
@ -516,33 +596,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
stop = true stop = true
} }
var strikethroughItems: [InstantPageTextStrikethroughItem] = [] let hadExtraDescent = extraDescent > 0.0
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))
}
}
extraDescent = 0.0 extraDescent = 0.0
var lineImageItems: [InstantPageTextImageItem] = [] var lineImageItems: [InstantPageTextImageItem] = []
var isRTL = false var isRTL = false
@ -556,25 +610,22 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
let cfRunRange = CTRunGetStringRange(run) let cfRunRange = CTRunGetStringRange(run)
let runRange = NSMakeRange(cfRunRange.location == kCFNotFound ? NSNotFound : cfRunRange.location, cfRunRange.length) let runRange = NSMakeRange(cfRunRange.location == kCFNotFound ? NSNotFound : cfRunRange.location, cfRunRange.length)
string.enumerateAttributes(in: runRange, options: []) { attributes, range, _ in string.enumerateAttributes(in: runRange, options: []) { attributes, range, _ in
if let id = attributes[NSAttributedStringKey.init(rawValue: InstantPageMediaIdAttribute)] as? Int64 { if let id = attributes[NSAttributedStringKey.init(rawValue: InstantPageMediaIdAttribute)] as? Int64, let dimensions = attributes[NSAttributedStringKey.init(rawValue: InstantPageMediaDimensionsAttribute)] as? CGSize {
var imageFrame = CGRect() var imageFrame = CGRect(origin: CGPoint(), size: dimensions)
var ascent: CGFloat = 0
imageFrame.size.width = CGFloat(CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, nil, nil))
imageFrame.size.height = ascent
let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil) let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil)
let yOffset = fontLineHeight.isZero ? 0.0 : floorToScreenPixels((fontLineHeight - imageFrame.size.height) / 2.0) 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 minSpacing = fontLineSpacing - 4.0
let delta = currentLineOrigin.y - minSpacing - imageFrame.minY - appliedLineOffset let delta = workingLineOrigin.y - minSpacing - imageFrame.minY - appliedLineOffset
if !fontAscent.isZero && delta > 0.0 { if !fontAscent.isZero && delta > 0.0 {
currentLineOrigin.y += delta workingLineOrigin.y += delta
appliedLineOffset += delta appliedLineOffset += delta
imageFrame.origin = imageFrame.origin.offsetBy(dx: 0.0, dy: delta) imageFrame.origin = imageFrame.origin.offsetBy(dx: 0.0, dy: delta)
} }
if !fontLineHeight.isZero { 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) maxImageHeight = max(maxImageHeight, imageFrame.height)
lineImageItems.append(InstantPageTextImageItem(frame: imageFrame, range: range, id: MediaId(namespace: Namespaces.Media.CloudFile, id: id))) 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) indexOffset = -(lastIndex + lineCharacterCount - imageItem.range.lowerBound)
continue 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 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) lines.append(textLine)
imageItems.append(contentsOf: lineImageItems) imageItems.append(contentsOf: lineImageItems)
currentLineOrigin.x = 0.0; if lineWidth > maxLineWidth {
currentLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent maxLineWidth = lineWidth
}
workingLineOrigin.x = 0.0
workingLineOrigin.y += fontLineHeight + fontLineSpacing + extraDescent
currentLineOrigin = workingLineOrigin
lastIndex += lineCharacterCount lastIndex += lineCharacterCount
@ -612,23 +699,56 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
height = lines.last!.frame.maxY + extraDescent 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) 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) textItem.frame = textItem.frame.offsetBy(dx: offset.x, dy: offset.y)
}
var items: [InstantPageItem] = [] var items: [InstantPageItem] = []
if imageItems.isEmpty || string.length > 1 { if !requiresScroll && (imageItems.isEmpty || string.length > 1) {
items.append(textItem) items.append(textItem)
} }
var topInset: CGFloat = 0.0
var bottomInset: CGFloat = 0.0
var additionalItems: [InstantPageItem] = []
if let webpage = webpage { if let webpage = webpage {
let offset = requiresScroll ? CGPoint() : offset
for line in textItem.lines { for line in textItem.lines {
let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment) let lineFrame = frameForLine(line, boundingWidth: boundingWidth, alignment: alignment)
for imageItem in line.imageItems { for imageItem in line.imageItems {
if let image = media[imageItem.id] as? TelegramMediaFile { 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 InstantPageLineSpacingFactorAttribute = "LineSpacingFactorAttribute"
let InstantPageMarkerColorAttribute = "MarkerColorAttribute" let InstantPageMarkerColorAttribute = "MarkerColorAttribute"
let InstantPageMediaIdAttribute = "MediaIdAttribute" let InstantPageMediaIdAttribute = "MediaIdAttribute"
let InstantPageMediaDimensionsAttribute = "MediaDimensionsAttribute"
let InstantPageAnchorAttribute = "AnchorAttribute" let InstantPageAnchorAttribute = "AnchorAttribute"
final class InstantPageTextStyleStack { final class InstantPageTextStyleStack {

View File

@ -103,9 +103,9 @@ final class InstantPageTheme {
let controlColor: UIColor 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.pageBackgroundColor = pageBackgroundColor
self.textCategories = textCategories self.textCategories = textCategories
self.serif = serif self.serif = serif
@ -122,11 +122,11 @@ final class InstantPageTheme {
self.tableBorderColor = tableBorderColor self.tableBorderColor = tableBorderColor
self.tableHeaderColor = tableHeaderColor self.tableHeaderColor = tableHeaderColor
self.controlColor = controlColor self.controlColor = controlColor
self.imageEmptyColor = imageEmptyColor self.imageTintColor = imageTintColor
} }
func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTheme { 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), tableBorderColor: UIColor(rgb: 0xe2e2e2),
tableHeaderColor: UIColor(rgb: 0xf4f4f4), tableHeaderColor: UIColor(rgb: 0xf4f4f4),
controlColor: UIColor(rgb: 0xc7c7cd), controlColor: UIColor(rgb: 0xc7c7cd),
imageEmptyColor: nil imageTintColor: nil
) )
private let sepiaTheme = InstantPageTheme( private let sepiaTheme = InstantPageTheme(
@ -185,7 +185,7 @@ private let sepiaTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0xddd1b8), tableBorderColor: UIColor(rgb: 0xddd1b8),
tableHeaderColor: UIColor(rgb: 0xf0e7d4), tableHeaderColor: UIColor(rgb: 0xf0e7d4),
controlColor: UIColor(rgb: 0xddd1b8), controlColor: UIColor(rgb: 0xddd1b8),
imageEmptyColor: nil imageTintColor: nil
) )
private let grayTheme = InstantPageTheme( private let grayTheme = InstantPageTheme(
@ -214,7 +214,7 @@ private let grayTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0x484848), tableBorderColor: UIColor(rgb: 0x484848),
tableHeaderColor: UIColor(rgb: 0x555556), tableHeaderColor: UIColor(rgb: 0x555556),
controlColor: UIColor(rgb: 0x484848), controlColor: UIColor(rgb: 0x484848),
imageEmptyColor: nil imageTintColor: UIColor(rgb: 0xcecece)
) )
private let darkTheme = InstantPageTheme( private let darkTheme = InstantPageTheme(
@ -243,7 +243,7 @@ private let darkTheme = InstantPageTheme(
tableBorderColor: UIColor(rgb: 0x303030), tableBorderColor: UIColor(rgb: 0x303030),
tableHeaderColor: UIColor(rgb: 0x131313), tableHeaderColor: UIColor(rgb: 0x131313),
controlColor: UIColor(rgb: 0x303030), controlColor: UIColor(rgb: 0x303030),
imageEmptyColor: UIColor(rgb: 0xb0b0b0) imageTintColor: UIColor(rgb: 0xb0b0b0)
) )
private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFontSize) -> CGFloat { private func fontSizeMultiplierForVariant(_ variant: InstantPagePresentationFontSize) -> CGFloat {

View File

@ -91,6 +91,16 @@ public func parseConfirmationCodeUrl(_ url: URL) -> Int? {
return code 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 return nil
} }

View File

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

View File

@ -228,7 +228,7 @@ public final class PresentationCallManager {
private func ringingStatesUpdated(_ ringingStates: [(Peer, CallSessionRingingState, Bool)], currentNetworkType: NetworkType, enableCallKit: Bool) { private func ringingStatesUpdated(_ ringingStates: [(Peer, CallSessionRingingState, Bool)], currentNetworkType: NetworkType, enableCallKit: Bool) {
if let firstState = ringingStates.first { if let firstState = ringingStates.first {
if self.currentCall == nil { 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.currentCall = call
self.currentCallPromise.set(.single(call)) self.currentCallPromise.set(.single(call))
self.hasActiveCallsPromise.set(true) 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) 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 { 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 self.defaultAction = ShareControllerAction(title: self.presentationData.strings.ShareMenu_CopyShareLink, action: { [weak self] in
UIPasteboard.general.string = "https://t.me/\(addressName)/\(message.id.id)" UIPasteboard.general.string = "https://t.me/\(addressName)/\(message.id.id)"

View File

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

View File

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

View File

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