mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-03 19:30:09 +00:00
Album artwork from ID3 tags
Instant View improvements
This commit is contained in:
parent
9bd9327502
commit
c5c90f76d4
@ -57,6 +57,8 @@
|
||||
09AE3823214C110900850BFD /* LegacySecureIdScanController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */; };
|
||||
09C3466D2167D63A00B76780 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C3466C2167D63A00B76780 /* Accessibility.swift */; };
|
||||
09C500242142BA6400EF253E /* ItemListWebsiteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */; };
|
||||
09C9EA33219F79F600E90146 /* ID3Artwork.m in Sources */ = {isa = PBXBuildFile; fileRef = 09C9EA31219F79F500E90146 /* ID3Artwork.m */; };
|
||||
09C9EA34219F79F600E90146 /* ID3Artwork.h in Headers */ = {isa = PBXBuildFile; fileRef = 09C9EA32219F79F600E90146 /* ID3Artwork.h */; };
|
||||
09D304152173C0E900C00567 /* WatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D304142173C0E900C00567 /* WatchManager.swift */; };
|
||||
09D304182173C15700C00567 /* WatchSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D304172173C15700C00567 /* WatchSettingsController.swift */; };
|
||||
09FE756D2153F5F900A3120F /* CallRouteActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */; };
|
||||
@ -791,7 +793,6 @@
|
||||
D0EC6DA31EB9F58900EBF1C3 /* ChatMessageDateHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB381DCFF87B009AD9A1 /* ChatMessageDateHeader.swift */; };
|
||||
D0EC6DA41EB9F58900EBF1C3 /* ChatMessageActionButtonsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9171DD5033100E8160F /* ChatMessageActionButtonsNode.swift */; };
|
||||
D0EC6DA51EB9F58900EBF1C3 /* ChatBotInfoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */; };
|
||||
D0EC6DA61EB9F58900EBF1C3 /* ChatEmptyItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C48F431E81D5110075317D /* ChatEmptyItem.swift */; };
|
||||
D0EC6DA71EB9F58900EBF1C3 /* ChatMessageBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02298361E0C34E900707F91 /* ChatMessageBackground.swift */; };
|
||||
D0EC6DA81EB9F58900EBF1C3 /* ChatInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03ADB471D703268005A521C /* ChatInterfaceState.swift */; };
|
||||
D0EC6DA91EB9F58900EBF1C3 /* ChatPresentationInterfaceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B417C21D7DE54E004562A4 /* ChatPresentationInterfaceState.swift */; };
|
||||
@ -1115,6 +1116,8 @@
|
||||
09AE3822214C110800850BFD /* LegacySecureIdScanController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySecureIdScanController.swift; sourceTree = "<group>"; };
|
||||
09C3466C2167D63A00B76780 /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = "<group>"; };
|
||||
09C500232142BA6400EF253E /* ItemListWebsiteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListWebsiteItem.swift; sourceTree = "<group>"; };
|
||||
09C9EA31219F79F500E90146 /* ID3Artwork.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ID3Artwork.m; sourceTree = "<group>"; };
|
||||
09C9EA32219F79F600E90146 /* ID3Artwork.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ID3Artwork.h; sourceTree = "<group>"; };
|
||||
09D304142173C0E900C00567 /* WatchManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchManager.swift; sourceTree = "<group>"; };
|
||||
09D304172173C15700C00567 /* WatchSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchSettingsController.swift; sourceTree = "<group>"; };
|
||||
09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRouteActionSheetItem.swift; sourceTree = "<group>"; };
|
||||
@ -1773,7 +1776,6 @@
|
||||
D0C27B3C1F4B454800A4E170 /* InstantPagePlayableVideoNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPagePlayableVideoNode.swift; sourceTree = "<group>"; };
|
||||
D0C44B631FC64D0500227BE0 /* SwipeToDismissGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeToDismissGestureRecognizer.swift; sourceTree = "<group>"; };
|
||||
D0C45E9E213FFAFD00988156 /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D0C48F431E81D5110075317D /* ChatEmptyItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatEmptyItem.swift; sourceTree = "<group>"; };
|
||||
D0C50DE81E93A07900F62E39 /* libtgvoip.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = libtgvoip.framework; path = "../libtgvoip/build/Debug-iphoneos/libtgvoip.framework"; sourceTree = "<group>"; };
|
||||
D0C50E281E93A33700F62E39 /* VoipDynamic.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VoipDynamic.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphoneos/VoipDynamic.framework"; sourceTree = "<group>"; };
|
||||
D0C50E371E93CB1500F62E39 /* NotificationContainerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationContainerController.swift; sourceTree = "<group>"; };
|
||||
@ -4249,7 +4251,6 @@
|
||||
D0F7AB381DCFF87B009AD9A1 /* ChatMessageDateHeader.swift */,
|
||||
D01AC9171DD5033100E8160F /* ChatMessageActionButtonsNode.swift */,
|
||||
D0C932371E09E0EA0074F044 /* ChatBotInfoItem.swift */,
|
||||
D0C48F431E81D5110075317D /* ChatEmptyItem.swift */,
|
||||
D02298361E0C34E900707F91 /* ChatMessageBackground.swift */,
|
||||
D0754D231EEE0F4100884F6E /* ChatMessageInteractiveMediaLabelNode.swift */,
|
||||
D091C7A31F8EBB1E00D7DE13 /* ChatPresentationData.swift */,
|
||||
@ -4488,6 +4489,8 @@
|
||||
09C3466C2167D63A00B76780 /* Accessibility.swift */,
|
||||
D0068FA721760FA300D1B315 /* StoreDownloadedMedia.swift */,
|
||||
0902838C2194AEB90067EFBD /* ImageTransparency.swift */,
|
||||
09C9EA32219F79F600E90146 /* ID3Artwork.h */,
|
||||
09C9EA31219F79F500E90146 /* ID3Artwork.m */,
|
||||
);
|
||||
name = Utils;
|
||||
sourceTree = "<group>";
|
||||
@ -4649,6 +4652,7 @@
|
||||
D0E9BAC61F05738600F079A4 /* STPAPIClient.h in Headers */,
|
||||
D00ADFD91EBA2E9D00873D2E /* OngoingCallThreadLocalContext.h in Headers */,
|
||||
D06F31E22135829B001A0F12 /* EDSunriseSet.h in Headers */,
|
||||
09C9EA34219F79F600E90146 /* ID3Artwork.h in Headers */,
|
||||
D0E9BA531F0559DA00F079A4 /* STPImageLibrary+Private.h in Headers */,
|
||||
D0E9BA601F055A4300F079A4 /* STPDelegateProxy.h in Headers */,
|
||||
096C98C121787C6700C211FF /* TGBridgeAudioDecoder.h in Headers */,
|
||||
@ -5055,6 +5059,7 @@
|
||||
D0EC6D191EB9F58800EBF1C3 /* FFMpegMediaFrameSourceContext.swift in Sources */,
|
||||
D02D60AE206BD47300FEFE1E /* SecureIdDocumentTypeSelectionController.swift in Sources */,
|
||||
D079FCE11F05C9380038FADE /* BotReceiptControllerNode.swift in Sources */,
|
||||
09C9EA33219F79F600E90146 /* ID3Artwork.m in Sources */,
|
||||
D0FA08CA2049BEAC00DD23FC /* ChatEmptyNode.swift in Sources */,
|
||||
D053DADC201AAAB100993D32 /* ChatTextInputMenu.swift in Sources */,
|
||||
D0EC6D1A1EB9F58800EBF1C3 /* FFMpegMediaFrameSourceContextHelpers.swift in Sources */,
|
||||
@ -5337,7 +5342,6 @@
|
||||
D0EC6DA31EB9F58900EBF1C3 /* ChatMessageDateHeader.swift in Sources */,
|
||||
D0EC6DA41EB9F58900EBF1C3 /* ChatMessageActionButtonsNode.swift in Sources */,
|
||||
D0EC6DA51EB9F58900EBF1C3 /* ChatBotInfoItem.swift in Sources */,
|
||||
D0EC6DA61EB9F58900EBF1C3 /* ChatEmptyItem.swift in Sources */,
|
||||
D0E9BAE41F0574D800F079A4 /* STPBankAccountParams.m in Sources */,
|
||||
D0E412D3206A7DC100BEE4A2 /* DateSelectionActionSheetController.swift in Sources */,
|
||||
D06887F01F72DEE6000AB936 /* ShareInputFieldNode.swift in Sources */,
|
||||
|
@ -235,6 +235,8 @@ public final class AvatarNode: ASDisplayNode {
|
||||
self.editOverlayNode = editOverlayNode
|
||||
}
|
||||
self.editOverlayNode?.isHidden = false
|
||||
} else {
|
||||
self.editOverlayNode?.isHidden = true
|
||||
}
|
||||
|
||||
parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer.id, letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: true)
|
||||
|
@ -40,6 +40,7 @@ public enum ChatControllerInteractionLongTapAction {
|
||||
public enum ChatControllerInteractionOpenMessageMode {
|
||||
case `default`
|
||||
case stream
|
||||
case shared
|
||||
}
|
||||
|
||||
public final class ChatControllerInteraction {
|
||||
|
@ -1,186 +0,0 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
private let messageFont = Font.medium(14.0)
|
||||
|
||||
final class ChatEmptyItem: ListViewItem {
|
||||
fileprivate let presentationData: ChatPresentationData
|
||||
fileprivate let tagMask: MessageTags?
|
||||
|
||||
init(presentationData: ChatPresentationData, tagMask: MessageTags?) {
|
||||
self.presentationData = presentationData
|
||||
self.tagMask = tagMask
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, () -> Void)) -> Void) {
|
||||
let configure = {
|
||||
let node = ChatEmptyItemNode(rotated: self.tagMask == nil)
|
||||
|
||||
let nodeLayout = node.asyncLayout()
|
||||
let (layout, apply) = nodeLayout(self, params)
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
completion(node, {
|
||||
return (nil, { apply(.None) })
|
||||
})
|
||||
}
|
||||
if Thread.isMainThread {
|
||||
async {
|
||||
configure()
|
||||
}
|
||||
} else {
|
||||
configure()
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? ChatEmptyItemNode {
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = nodeLayout(self, params)
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, {
|
||||
apply(animation)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatEmptyItemNode: ListViewItemNode {
|
||||
private let rotated: Bool
|
||||
|
||||
var controllerInteraction: ChatControllerInteraction?
|
||||
|
||||
let offsetContainer: ASDisplayNode
|
||||
let backgroundNode: ASImageNode
|
||||
let iconNode: ASImageNode
|
||||
let textNode: TextNode
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
private var item: ChatEmptyItem?
|
||||
|
||||
init(rotated: Bool) {
|
||||
self.rotated = rotated
|
||||
self.offsetContainer = ASDisplayNode()
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.iconNode = ASImageNode()
|
||||
self.textNode = TextNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: true, rotated: rotated)
|
||||
|
||||
if rotated {
|
||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
}
|
||||
|
||||
self.addSubnode(self.offsetContainer)
|
||||
self.offsetContainer.addSubnode(self.backgroundNode)
|
||||
self.offsetContainer.addSubnode(self.iconNode)
|
||||
self.offsetContainer.addSubnode(self.textNode)
|
||||
self.wantsTrailingItemSpaceUpdates = true
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ChatEmptyItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let currentTheme = self.theme
|
||||
return { [weak self] item, params in
|
||||
self?.item = item
|
||||
|
||||
let width = params.width
|
||||
var updatedBackgroundImage: UIImage?
|
||||
|
||||
let iconImage: UIImage? = PresentationResourcesChat.chatEmptyItemIconImage(item.presentationData.theme.theme)
|
||||
|
||||
if currentTheme !== item.presentationData.theme {
|
||||
updatedBackgroundImage = PresentationResourcesChat.chatEmptyItemBackgroundImage(item.presentationData.theme.theme)
|
||||
}
|
||||
|
||||
let attributedText: NSAttributedString
|
||||
if let tagMask = item.tagMask {
|
||||
let text: String
|
||||
if tagMask == .photoOrVideo {
|
||||
text = item.presentationData.strings.SharedMedia_EmptyText
|
||||
} else if tagMask == .file {
|
||||
text = item.presentationData.strings.SharedMedia_EmptyFilesText
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
attributedText = NSAttributedString(string: text, font: messageFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor, paragraphAlignment: .center)
|
||||
} else {
|
||||
attributedText = NSAttributedString(string: item.presentationData.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: item.presentationData.theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor, paragraphAlignment: .center)
|
||||
}
|
||||
|
||||
let horizontalEdgeInset: CGFloat = 10.0
|
||||
let horizontalContentInset: CGFloat = 12.0
|
||||
let verticalItemInset: CGFloat = 10.0
|
||||
let verticalContentInset: CGFloat = 14.0
|
||||
|
||||
var imageSize = CGSize(width: 80.0, height: 80.0)
|
||||
if let iconImage = iconImage {
|
||||
imageSize = iconImage.size
|
||||
}
|
||||
let imageSpacing: CGFloat = 18.0
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - horizontalEdgeInset * 2.0 - horizontalContentInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentWidth = max(textLayout.size.width, 120.0)
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: floor((width - contentWidth - horizontalContentInset * 2.0) / 2.0), y: verticalItemInset + 4.0), size: CGSize(width: contentWidth + horizontalContentInset * 2.0, height: textLayout.size.height + imageSize.height + imageSpacing + verticalContentInset * 2.0))
|
||||
let textFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + horizontalContentInset + floor((contentWidth - textLayout.size.width) / 2.0), y: backgroundFrame.origin.y + verticalContentInset + imageSize.height + imageSpacing), size: textLayout.size)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + horizontalContentInset + floor((contentWidth - imageSize.width) / 2.0), y: backgroundFrame.origin.y + verticalContentInset), size: imageSize)
|
||||
|
||||
let itemLayout = ListViewItemNodeLayout(contentSize: CGSize(width: width, height: imageSize.height + imageSpacing + textLayout.size.height + verticalItemInset * 2.0 + verticalContentInset * 2.0 + 4.0), insets: UIEdgeInsets())
|
||||
return (itemLayout, { _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.theme = item.presentationData.theme.theme
|
||||
|
||||
if let updatedBackgroundImage = updatedBackgroundImage {
|
||||
strongSelf.backgroundNode.image = updatedBackgroundImage
|
||||
}
|
||||
|
||||
strongSelf.iconNode.image = iconImage
|
||||
|
||||
let _ = textApply()
|
||||
strongSelf.offsetContainer.frame = CGRect(origin: CGPoint(), size: itemLayout.contentSize)
|
||||
strongSelf.backgroundNode.frame = backgroundFrame
|
||||
strongSelf.textNode.frame = textFrame
|
||||
strongSelf.iconNode.frame = iconFrame
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if height.isLessThanOrEqualTo(0.0) {
|
||||
transition.updateBounds(node: self.offsetContainer, bounds: CGRect(origin: CGPoint(), size: self.offsetContainer.bounds.size))
|
||||
} else {
|
||||
transition.updateBounds(node: self.offsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.rotated ? (floor(height) / 2.0) : (-floor(height) / 4.0)), size: self.offsetContainer.bounds.size))
|
||||
}
|
||||
}
|
||||
|
||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
|
||||
}
|
||||
}
|
@ -12,19 +12,16 @@ private let titleFont = Font.medium(15.0)
|
||||
private let messageFont = Font.regular(14.0)
|
||||
|
||||
private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNodeContent {
|
||||
private let iconNode: ASImageNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private var currentTheme: PresentationTheme?
|
||||
private var currentStrings: PresentationStrings?
|
||||
|
||||
override init() {
|
||||
self.iconNode = ASImageNode()
|
||||
self.textNode = ImmediateTextNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
@ -33,24 +30,18 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod
|
||||
self.currentTheme = interfaceState.theme
|
||||
self.currentStrings = interfaceState.strings
|
||||
|
||||
self.iconNode.image = PresentationResourcesChat.chatEmptyItemIconImage(interfaceState.theme)
|
||||
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: messageFont, textColor: interfaceState.theme.chat.serviceMessage.serviceMessagePrimaryTextColor)
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
|
||||
let insets = UIEdgeInsets(top: 6.0, left: 10.0, bottom: 6.0, right: 10.0)
|
||||
|
||||
let iconVerticalInset: CGFloat = 14.0
|
||||
let iconSize = self.iconNode.image?.size ?? CGSize()
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
let spacing: CGFloat = 26.0
|
||||
|
||||
let contentWidth = max(iconSize.width, textSize.width)
|
||||
let contentHeight = iconVerticalInset + iconSize.height + spacing + textSize.height
|
||||
let contentWidth = textSize.width
|
||||
let contentHeight = textSize.height
|
||||
let contentRect = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: contentWidth, height: contentHeight))
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - iconSize.width) / 2.0), y: contentRect.minY + iconVerticalInset), size: iconSize)
|
||||
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - textSize.width) / 2.0), y: iconFrame.maxY + spacing), size: textSize))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentRect.minX + floor((contentRect.width - textSize.width) / 2.0), y: insets.top), size: textSize))
|
||||
|
||||
return contentRect.insetBy(dx: -insets.left, dy: -insets.top).size
|
||||
}
|
||||
|
@ -98,8 +98,6 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
|
||||
}
|
||||
if let cachedPeerData = cachedPeerData as? CachedUserData, let botInfo = cachedPeerData.botInfo, !botInfo.description.isEmpty {
|
||||
entries.insert(.ChatInfoEntry(botInfo.description, presentationData), at: 0)
|
||||
} else if view.entries.isEmpty && includeEmptyEntry {
|
||||
//entries.insert(.EmptyChatInfoEntry(presentationData.theme, presentationData.strings, view.tagMask), at: 0)
|
||||
}
|
||||
}
|
||||
} else if includeSearchEntry {
|
||||
|
@ -29,7 +29,6 @@ enum ChatHistoryEntry: Identifiable, Comparable {
|
||||
case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, Bool)], ChatPresentationData)
|
||||
case UnreadEntry(MessageIndex, ChatPresentationData)
|
||||
case ChatInfoEntry(String, ChatPresentationData)
|
||||
case EmptyChatInfoEntry(ChatPresentationData, MessageTags?)
|
||||
case SearchEntry(PresentationTheme, PresentationStrings)
|
||||
|
||||
var stableId: UInt64 {
|
||||
@ -44,10 +43,8 @@ enum ChatHistoryEntry: Identifiable, Comparable {
|
||||
return UInt64(3) << 40
|
||||
case .ChatInfoEntry:
|
||||
return UInt64(4) << 40
|
||||
case .EmptyChatInfoEntry:
|
||||
return UInt64(5) << 40
|
||||
case .SearchEntry:
|
||||
return UInt64(6) << 40
|
||||
return UInt64(5) << 40
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,8 +60,6 @@ enum ChatHistoryEntry: Identifiable, Comparable {
|
||||
return index
|
||||
case .ChatInfoEntry:
|
||||
return MessageIndex.absoluteLowerBound()
|
||||
case .EmptyChatInfoEntry:
|
||||
return MessageIndex.absoluteLowerBound()
|
||||
case .SearchEntry:
|
||||
return MessageIndex.absoluteLowerBound()
|
||||
}
|
||||
@ -185,12 +180,6 @@ enum ChatHistoryEntry: Identifiable, Comparable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .EmptyChatInfoEntry(lhsPresentationData, lhsTagMask):
|
||||
if case let .EmptyChatInfoEntry(rhsPresentationData, rhsTagMask) = rhs, lhsPresentationData === rhsPresentationData, lhsTagMask == rhsTagMask {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .SearchEntry(lhsTheme, lhsStrings):
|
||||
if case let .SearchEntry(rhsTheme, rhsStrings) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
|
||||
return true
|
||||
|
@ -86,7 +86,7 @@ private func mappedInsertEntries(account: Account, peerId: PeerId, controllerInt
|
||||
case .UnreadEntry:
|
||||
assertionFailure()
|
||||
return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex)
|
||||
case .ChatInfoEntry, .EmptyChatInfoEntry, .SearchEntry:
|
||||
case .ChatInfoEntry, .SearchEntry:
|
||||
assertionFailure()
|
||||
return GridNodeInsertItem(index: entry.index, item: GridHoleItem(), previousIndex: entry.previousIndex)
|
||||
}
|
||||
@ -105,7 +105,7 @@ private func mappedUpdateEntries(account: Account, peerId: PeerId, controllerInt
|
||||
case .UnreadEntry:
|
||||
assertionFailure()
|
||||
return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem())
|
||||
case .ChatInfoEntry, .EmptyChatInfoEntry, .SearchEntry:
|
||||
case .ChatInfoEntry, .SearchEntry:
|
||||
assertionFailure()
|
||||
return GridNodeUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: GridHoleItem())
|
||||
}
|
||||
|
@ -179,8 +179,6 @@ private func mappedInsertEntries(account: Account, chatLocation: ChatLocation, a
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData), directionHint: entry.directionHint)
|
||||
case let .ChatInfoEntry(text, presentationData):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint)
|
||||
case let .EmptyChatInfoEntry(presentationData, tagMask):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatEmptyItem(presentationData: presentationData, tagMask: tagMask), directionHint: entry.directionHint)
|
||||
case let .SearchEntry(theme, strings):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: {
|
||||
controllerInteraction.openSearch()
|
||||
@ -224,8 +222,6 @@ private func mappedUpdateEntries(account: Account, chatLocation: ChatLocation, a
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData), directionHint: entry.directionHint)
|
||||
case let .ChatInfoEntry(text, presentationData):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatBotInfoItem(text: text, controllerInteraction: controllerInteraction, presentationData: presentationData), directionHint: entry.directionHint)
|
||||
case let .EmptyChatInfoEntry(presentationData, tagMask):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatEmptyItem(presentationData: presentationData, tagMask: tagMask), directionHint: entry.directionHint)
|
||||
case let .SearchEntry(theme, strings):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSearchItem(theme: theme, placeholder: strings.Common_Search, activate: {
|
||||
controllerInteraction.openSearch()
|
||||
|
@ -213,7 +213,16 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
|
||||
let edited = false
|
||||
let sentViaBot = false
|
||||
let viewCount: Int? = nil
|
||||
var viewCount: Int? = nil
|
||||
for attribute in item.message.attributes {
|
||||
if let _ = attribute as? EditedMessageAttribute {
|
||||
// edited = true
|
||||
} else if let attribute = attribute as? ViewCountMessageAttribute {
|
||||
viewCount = attribute.count
|
||||
}// else if let _ = attribute as? InlineBotMessageAttribute {
|
||||
// sentViaBot = true
|
||||
// }
|
||||
}
|
||||
|
||||
let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, strings: item.presentationData.strings, format: .minimal)
|
||||
|
||||
|
@ -1334,8 +1334,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if isPNG && images.count == 1, let image = images.first {
|
||||
if imageHasTransparency(image) {
|
||||
if isPNG && images.count == 1, let image = images.first, let cgImage = image.cgImage {
|
||||
if imageHasTransparency(cgImage) {
|
||||
self.paste(.sticker(image))
|
||||
return false
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let updateAvatarDisposable = MetaDisposable()
|
||||
actionsDisposable.add(updateAvatarDisposable)
|
||||
//actionsDisposable.add(updateAvatarDisposable)
|
||||
|
||||
let updatePeerNameDisposable = MetaDisposable()
|
||||
actionsDisposable.add(updatePeerNameDisposable)
|
||||
|
@ -119,7 +119,7 @@ private let italicFont = Font.italic(16.0)
|
||||
private let fixedFont = UIFont(name: "Menlo-Regular", size: 15.0) ?? textFont
|
||||
|
||||
func galleryCaptionStringWithAppliedEntities(_ text: String, entities: [MessageTextEntity]) -> NSAttributedString {
|
||||
return stringWithAppliedEntities(text, entities: entities, baseColor: .white, linkColor: .white, baseFont: textFont, linkFont: textFont, boldFont: boldFont, italicFont: italicFont, fixedFont: fixedFont)
|
||||
return stringWithAppliedEntities(text, entities: entities, baseColor: .white, linkColor: UIColor(rgb: 0x5ac8fa), baseFont: textFont, linkFont: textFont, boldFont: boldFont, italicFont: italicFont, fixedFont: fixedFont, underlineLinks: false)
|
||||
}
|
||||
|
||||
func galleryItemForEntry(account: Account, presentationData: PresentationData, entry: MessageHistoryEntry, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, playbackCompleted: @escaping () -> Void = {}, openUrl: @escaping (String) -> Void = { _ in }, openUrlOptions: @escaping (String) -> Void = { _ in }) -> GalleryItem? {
|
||||
|
9
TelegramUI/ID3Artwork.h
Normal file
9
TelegramUI/ID3Artwork.h
Normal file
@ -0,0 +1,9 @@
|
||||
#ifndef Telegram_ID3Artwork_h
|
||||
#define Telegram_ID3Artwork_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NSData * _Nullable albumArtworkData(NSData * _Nonnull data);
|
||||
|
||||
#endif
|
127
TelegramUI/ID3Artwork.m
Normal file
127
TelegramUI/ID3Artwork.m
Normal file
@ -0,0 +1,127 @@
|
||||
#import "ID3Artwork.h"
|
||||
|
||||
const uint8_t ID3v2[5] = {0x49, 0x44, 0x33, 0x02, 0x00};
|
||||
const uint8_t ID3v3[5] = {0x49, 0x44, 0x33, 0x03, 0x00};
|
||||
const NSUInteger ID3VersionOffset = 3;
|
||||
const NSUInteger ID3SizeOffset = 6;
|
||||
const NSUInteger ID3TagOffset = 10;
|
||||
const NSUInteger ID3ArtOffset = 12;
|
||||
|
||||
const NSUInteger ID3v2FrameOffset = 6;
|
||||
const NSUInteger ID3v3FrameOffset = 10;
|
||||
|
||||
const uint8_t ID3v2Artwork[3] = {0x50, 0x49, 0x43};
|
||||
const uint8_t ID3v3Artwork[4] = {0x41, 0x50, 0x49, 0x43};
|
||||
|
||||
const uint8_t JPGMagic[3] = {0xff, 0xd8, 0xff};
|
||||
const uint8_t PNGMagic[4] = {0x89, 0x50, 0x4e, 0x47};
|
||||
|
||||
uint32_t getSize(const uint8_t *bytes) {
|
||||
uint32_t size = CFSwapInt32HostToBig(*(const uint32_t *)(bytes + ID3SizeOffset));
|
||||
uint32_t b1 = (size & 0x7F000000) >> 3;
|
||||
uint32_t b2 = (size & 0x007F0000) >> 2;
|
||||
uint32_t b3 = (size & 0x00007F00) >> 1;
|
||||
uint32_t b4 = size & 0x0000007F;
|
||||
return b1 + b2 + b3 + b4;
|
||||
}
|
||||
|
||||
uint32_t frameOffsetForVersion(uint8_t version) {
|
||||
return version == 2 ? ID3v2FrameOffset : ID3v3FrameOffset;
|
||||
}
|
||||
|
||||
uint32_t frameSizeForBytes(const uint8_t *framePtr, uint8_t version) {
|
||||
uint8_t offset = version == 2 ? 2 : 4;
|
||||
uint32_t size = CFSwapInt32HostToBig(*(uint32_t *)(framePtr + offset));
|
||||
|
||||
if (version == 2) {
|
||||
size &= 0x00FFFFFF;
|
||||
}
|
||||
|
||||
return size + frameOffsetForVersion(version);
|
||||
}
|
||||
|
||||
bool isArtworkFrame(const uint8_t *framePtr, uint8_t version) {
|
||||
if (version == 2) {
|
||||
return memcmp(framePtr, ID3v2Artwork, 3) == 0;
|
||||
}
|
||||
|
||||
return memcmp(framePtr, ID3v3Artwork, 4) == 0;
|
||||
}
|
||||
|
||||
NSData * _Nullable albumArtworkData(NSData * _Nonnull data) {
|
||||
if (data.length < 4) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
const uint8_t *bytes = data.bytes;
|
||||
|
||||
if (!(memcmp(bytes, ID3v2, 5) == 0 || memcmp(bytes, ID3v3, 5) == 0)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
uint8_t version = bytes[ID3VersionOffset];
|
||||
uint32_t size = CFSwapInt32HostToBig(*(const uint32_t *)(bytes + ID3SizeOffset));
|
||||
uint32_t b1 = (size & 0x7F000000) >> 3;
|
||||
uint32_t b2 = (size & 0x007F0000) >> 2;
|
||||
uint32_t b3 = (size & 0x00007F00) >> 1;
|
||||
uint32_t b4 = size & 0x0000007F;
|
||||
size = b1 + b2 + b3 + b4;
|
||||
|
||||
const uint8_t *ptr = data.bytes + ID3TagOffset;
|
||||
|
||||
uint32_t pos = 0;
|
||||
while (pos < size) {
|
||||
const uint8_t * const frameBytes = ptr + pos;
|
||||
uint32_t frameSize = frameSizeForBytes(frameBytes, version);
|
||||
|
||||
if (isArtworkFrame(frameBytes, version)) {
|
||||
uint32_t frameOffset = frameOffsetForVersion(version);
|
||||
const uint8_t *ptr = frameBytes + frameOffset;
|
||||
|
||||
bool isJpg = false;
|
||||
uint32_t imageOffset = UINT32_MAX;
|
||||
for (uint32_t i = 0; i < frameSize - 4; i++) {
|
||||
if (memcmp(ptr + i, JPGMagic, 3) == 0) {
|
||||
imageOffset = i;
|
||||
isJpg = true;
|
||||
break;
|
||||
} else if (memcmp(ptr + i, PNGMagic, 4) == 0) {
|
||||
imageOffset = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (imageOffset != UINT32_MAX) {
|
||||
if (isJpg) {
|
||||
NSMutableData *jpgData = [[NSMutableData alloc] initWithCapacity:frameSize + 1024];
|
||||
uint8_t previousByte = 0xff;
|
||||
uint32_t skippedBytes = 0;
|
||||
|
||||
for (NSUInteger i = 0; i < frameSize - imageOffset + skippedBytes; i++) {
|
||||
uint8_t byte = (uint8_t)ptr[imageOffset + i];
|
||||
if (byte == 0x00 && previousByte == 0xff) {
|
||||
skippedBytes++;
|
||||
} else {
|
||||
[jpgData appendBytes:&byte length:1];
|
||||
}
|
||||
if (byte == 0xd9 && previousByte == 0xff) {
|
||||
break;
|
||||
}
|
||||
previousByte = byte;
|
||||
}
|
||||
return jpgData;
|
||||
}
|
||||
else {
|
||||
return [[NSData alloc] initWithBytes:ptr + imageOffset length:frameSize - imageOffset];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (frameBytes[0] == 0x00 && frameBytes[1] == 0x00 && frameBytes[2] == 0x00) {
|
||||
break;
|
||||
}
|
||||
|
||||
pos += frameSize;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
@ -1,15 +1,7 @@
|
||||
import UIKit
|
||||
import Accelerate
|
||||
|
||||
func imageHasTransparency(_ image: UIImage) -> Bool {
|
||||
guard let cgImage = image.cgImage, cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else {
|
||||
return false
|
||||
}
|
||||
let alphaInfo = cgImage.alphaInfo
|
||||
guard alphaInfo == .first || alphaInfo == .last || alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast else {
|
||||
return false
|
||||
}
|
||||
|
||||
private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? {
|
||||
var sourceBuffer = vImage_Buffer()
|
||||
defer {
|
||||
free(sourceBuffer.data)
|
||||
@ -29,9 +21,9 @@ func imageHasTransparency(_ image: UIImage) -> Bool {
|
||||
var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &cgImageFormat, nil, cgImage, noFlags)
|
||||
assert(error == kvImageNoError)
|
||||
|
||||
if alphaInfo == .premultipliedLast {
|
||||
if cgImage.alphaInfo == .premultipliedLast {
|
||||
error = vImageUnpremultiplyData_RGBA8888(&sourceBuffer, &sourceBuffer, noFlags)
|
||||
} else if alphaInfo == .premultipliedFirst {
|
||||
} else if cgImage.alphaInfo == .premultipliedFirst {
|
||||
error = vImageUnpremultiplyData_ARGB8888(&sourceBuffer, &sourceBuffer, noFlags)
|
||||
}
|
||||
assert(error == kvImageNoError)
|
||||
@ -45,11 +37,43 @@ func imageHasTransparency(_ image: UIImage) -> Bool {
|
||||
error = vImageHistogramCalculation_ARGB8888(&sourceBuffer, &mutableHistogram, noFlags)
|
||||
assert(error == kvImageNoError)
|
||||
|
||||
let alphaBinIndex = alphaInfo == .last || alphaInfo == .premultipliedLast ? 3 : 0
|
||||
for i in 0 ..< 255 {
|
||||
if histogramBins[alphaBinIndex][i] > 0 {
|
||||
return true
|
||||
let alphaBinIndex = [.last, .premultipliedLast].contains(cgImage.alphaInfo) ? 3 : 0
|
||||
return (histogramBins, alphaBinIndex)
|
||||
}
|
||||
|
||||
func imageHasTransparency(_ cgImage: CGImage) -> Bool {
|
||||
guard cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else {
|
||||
return false
|
||||
}
|
||||
guard [.first, .last, .premultipliedFirst, .premultipliedLast].contains(cgImage.alphaInfo) else {
|
||||
return false
|
||||
}
|
||||
if let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) {
|
||||
for i in 0 ..< 255 {
|
||||
if histogramBins[alphaBinIndex][i] > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func imageIsMonochrome(_ cgImage: CGImage) -> Bool {
|
||||
guard cgImage.bitsPerComponent == 8, cgImage.bitsPerPixel == 32 else {
|
||||
return false
|
||||
}
|
||||
if let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) {
|
||||
|
||||
}
|
||||
|
||||
// SSE, bias = 0, [0,0,0]
|
||||
// if adjust_color_bias:
|
||||
// bias = ImageStat.Stat(thumb).mean[:3]
|
||||
// bias = [b - sum(bias)/3 for b in bias ]
|
||||
// for pixel in thumb.getdata():
|
||||
// mu = sum(pixel)/3
|
||||
// SSE += sum((pixel[i] - mu - bias[i])*(pixel[i] - mu - bias[i]) for i in [0,1,2])
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -204,7 +204,9 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
if !(item is InstantPageImageItem || item is InstantPagePlayableVideoItem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -654,8 +656,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.statusBar.verticalOffset = 0.0
|
||||
}
|
||||
|
||||
var title: String?
|
||||
if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||
title = content.websiteName
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.navigationBar, frame: navigationBarFrame)
|
||||
self.navigationBar.updateLayout(size: navigationBarFrame.size, minHeight: minBarHeight, maxHeight: maxBarHeight, topInset: containerLayout.safeInsets.top, leftInset: containerLayout.safeInsets.left, rightInset: containerLayout.safeInsets.right, pageProgress: pageProgress, transition: transition)
|
||||
self.navigationBar.updateLayout(size: navigationBarFrame.size, minHeight: minBarHeight, maxHeight: maxBarHeight, topInset: containerLayout.safeInsets.top, leftInset: containerLayout.safeInsets.left, rightInset: containerLayout.safeInsets.right, title: title, pageProgress: pageProgress, transition: transition)
|
||||
|
||||
transition.animateView {
|
||||
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: navigationBarFrame.size.height, left: 0.0, bottom: containerLayout.intrinsicInsets.bottom, right: 0.0)
|
||||
@ -690,7 +697,8 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
if let current = self.linkHighlightingNode {
|
||||
linkHighlightingNode = current
|
||||
} else {
|
||||
linkHighlightingNode = LinkHighlightingNode(color: UIColor(rgb: 0x007be8).withAlphaComponent(0.4))
|
||||
let highlightColor = self.theme?.linkHighlightColor ?? UIColor(rgb: 0x007ee5).withAlphaComponent(0.4)
|
||||
linkHighlightingNode = LinkHighlightingNode(color: highlightColor)
|
||||
linkHighlightingNode.isUserInteractionEnabled = false
|
||||
self.linkHighlightingNode = linkHighlightingNode
|
||||
self.scrollNode.addSubnode(linkHighlightingNode)
|
||||
@ -716,13 +724,21 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
return contentOffset
|
||||
}
|
||||
|
||||
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
|
||||
private func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
|
||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
|
||||
return CGSize(width: item.frame.width, height: detailsNode.effectiveContentSize.height + item.titleHeight)
|
||||
return detailsNode
|
||||
}
|
||||
}
|
||||
return item.frame.size
|
||||
return nil
|
||||
}
|
||||
|
||||
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
|
||||
if let node = nodeForDetailsItem(item) {
|
||||
return CGSize(width: item.frame.width, height: node.effectiveContentSize.height + item.titleHeight)
|
||||
} else {
|
||||
return item.frame.size
|
||||
}
|
||||
}
|
||||
|
||||
private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect {
|
||||
@ -925,11 +941,14 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl, let anchor = anchor {
|
||||
if !anchor.isEmpty {
|
||||
if let (anchorItem, offset) = findAnchorItem(String(anchor), items: items) {
|
||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: anchorItem.frame.minY + offset - self.scrollNode.view.contentInset.top), animated: true)
|
||||
return
|
||||
if let (item, offset) = findAnchorItem(String(anchor), items: items) {
|
||||
let frame = effectiveFrameForItem(item)
|
||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: frame.minY + offset - self.scrollNode.view.contentInset.top), animated: true)
|
||||
}
|
||||
} else {
|
||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: -self.scrollNode.view.contentInset.top), animated: true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
|
@ -178,7 +178,6 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
||||
|
||||
if itemNode == nil {
|
||||
let itemIndex = itemIndex
|
||||
let embedIndex = embedIndex
|
||||
let detailsIndex = detailsIndex
|
||||
if let newNode = item.node(account: self.account, strings: self.strings, theme: theme, openMedia: { [weak self] media in
|
||||
self?.openMedia(media)
|
||||
@ -187,15 +186,11 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
||||
}, openUrl: { [weak self] url in
|
||||
self?.openUrl(url)
|
||||
}, updateWebEmbedHeight: { [weak self] height in
|
||||
//self?.updateWebEmbedHeight(embedIndex, height)
|
||||
}, updateDetailsExpanded: { [weak self] expanded in
|
||||
self?.updateDetailsExpanded(detailsIndex, expanded)
|
||||
}, currentExpandedDetails: self.currentExpandedDetails) {
|
||||
newNode.frame = itemFrame
|
||||
newNode.updateLayout(size: itemFrame.size, transition: transition)
|
||||
// if case let .animated(duration, _) = transition {
|
||||
// newNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||
// }
|
||||
if let topNode = topNode {
|
||||
self.insertSubnode(newNode, aboveSubnode: topNode)
|
||||
} else {
|
||||
@ -240,9 +235,6 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
||||
if self.visibleTiles[tileIndex] == nil {
|
||||
let tileNode = InstantPageTileNode(tile: tile, backgroundColor: theme.pageBackgroundColor)
|
||||
tileNode.frame = tileFrame
|
||||
// if case let .animated(duration, _) = transition {
|
||||
// tileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||
// }
|
||||
if let topNode = topNode {
|
||||
self.insertSubnode(tileNode, aboveSubnode: topNode)
|
||||
} else {
|
||||
@ -339,13 +331,21 @@ final class InstantPageDetailsContentNode : ASDisplayNode {
|
||||
return contentOffset
|
||||
}
|
||||
|
||||
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
|
||||
private func nodeForDetailsItem(_ item: InstantPageDetailsItem) -> InstantPageDetailsNode? {
|
||||
for (_, itemNode) in self.visibleItemsWithNodes {
|
||||
if let detailsNode = itemNode as? InstantPageDetailsNode, detailsNode.item === item {
|
||||
return CGSize(width: item.frame.width, height: detailsNode.effectiveContentSize.height + item.titleHeight)
|
||||
return detailsNode
|
||||
}
|
||||
}
|
||||
return item.frame.size
|
||||
return nil
|
||||
}
|
||||
|
||||
private func effectiveSizeForDetails(_ item: InstantPageDetailsItem) -> CGSize {
|
||||
if let node = nodeForDetailsItem(item) {
|
||||
return CGSize(width: item.frame.width, height: node.effectiveContentSize.height + item.titleHeight)
|
||||
} else {
|
||||
return item.frame.size
|
||||
}
|
||||
}
|
||||
|
||||
private func effectiveFrameForTile(_ tile: InstantPageTile) -> CGRect {
|
||||
|
@ -36,6 +36,8 @@ struct InstantPageGalleryEntry: Equatable {
|
||||
styleStack.push(.fontSize(16.0))
|
||||
styleStack.push(.textColor(.white))
|
||||
styleStack.push(.markerColor(UIColor(rgb: 0x313131)))
|
||||
styleStack.push(.linkColor(UIColor(rgb: 0x5ac8fa)))
|
||||
styleStack.push(.linkMarkerColor(UIColor(rgb: 0x5ac8fa, alpha: 0.2)))
|
||||
styleStack.push(.fontSerif(false))
|
||||
|
||||
if let url = self.media.url {
|
||||
@ -59,6 +61,8 @@ struct InstantPageGalleryEntry: Equatable {
|
||||
styleStack.push(.fontSize(14.0))
|
||||
styleStack.push(.textColor(.white))
|
||||
styleStack.push(.markerColor(UIColor(rgb: 0x313131)))
|
||||
styleStack.push(.linkColor(UIColor(rgb: 0x5ac8fa)))
|
||||
styleStack.push(.linkMarkerColor(UIColor(rgb: 0x5ac8fa, alpha: 0.2)))
|
||||
styleStack.push(.fontSerif(false))
|
||||
//styleStack.push(.lineSpacingFactor(1.0))
|
||||
credit = attributedStringForRichText(mediaCredit, styleStack: styleStack)
|
||||
|
@ -58,7 +58,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
||||
let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file)
|
||||
if file.mimeType.hasPrefix("image/") {
|
||||
_ = freeMediaFileInteractiveFetched(account: account, fileReference: fileReference).start()
|
||||
self.imageNode.setSignal(chatMessageImageFile(account: account, fileReference: fileReference, thumbnail: false, fetched: true))
|
||||
self.imageNode.setSignal(instantPageImageFile(account: account, fileReference: fileReference, fetched: true))
|
||||
} else {
|
||||
self.imageNode.setSignal(chatMessageVideo(postbox: account.postbox, videoReference: fileReference))
|
||||
}
|
||||
@ -76,6 +76,10 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode {
|
||||
}
|
||||
let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(dimensions.width), height: Int32(dimensions.height))
|
||||
self.imageNode.setSignal(chatMapSnapshotImage(account: account, resource: resource))
|
||||
} else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image {
|
||||
let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image)
|
||||
self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference))
|
||||
self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,8 @@ private func setupStyleStack(_ stack: InstantPageTextStyleStack, theme: InstantP
|
||||
let attributes = theme.textCategories.attributes(type: category, link: link)
|
||||
stack.push(.textColor(attributes.color))
|
||||
stack.push(.markerColor(theme.markerColor))
|
||||
stack.push(.linkColor(theme.linkColor))
|
||||
stack.push(.linkMarkerColor(theme.linkHighlightColor))
|
||||
switch attributes.font.style {
|
||||
case .sans:
|
||||
stack.push(.fontSerif(false))
|
||||
@ -232,7 +234,11 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
||||
let styleStack = InstantPageTextStyleStack()
|
||||
setupStyleStack(styleStack, theme: theme, category: .paragraph, link: false)
|
||||
|
||||
switch item {
|
||||
var effectiveItem = item
|
||||
if case let .blocks(blocks, num) = effectiveItem, blocks.isEmpty {
|
||||
effectiveItem = .text(.plain(" "), num)
|
||||
}
|
||||
switch effectiveItem {
|
||||
case let .text(text, _):
|
||||
let (textItem, textItems, textItemSize) = layoutTextItemWithString(attributedStringForRichText(text, styleStack: styleStack), boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, offset: CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height), media: media, webpage: webpage)
|
||||
|
||||
@ -259,14 +265,14 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
||||
listItems.append(contentsOf: textItems)
|
||||
case let .blocks(blocks, _):
|
||||
var previousBlock: InstantPageBlock?
|
||||
var originY: CGFloat = 0.0
|
||||
var originY: CGFloat = contentSize.height
|
||||
for subBlock in blocks {
|
||||
let subLayout = layoutInstantPageBlock(webpage: webpage, rtl: rtl, block: subBlock, boundingWidth: boundingWidth - horizontalInset * 2.0 - indexSpacing - maxIndexWidth, horizontalInset: 0.0, safeInset: 0.0, isCover: false, previousItems: listItems, fillToSize: nil, media: media, mediaIndexCounter: &mediaIndexCounter, embedIndexCounter: &embedIndexCounter, detailsIndexCounter: &detailsIndexCounter, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, webEmbedHeights: webEmbedHeights)
|
||||
|
||||
let spacing: CGFloat = previousBlock != nil ? spacingBetweenBlocks(upper: previousBlock, lower: subBlock) : 0.0
|
||||
let blockItems = subLayout.flattenedItemsWithOrigin(CGPoint(x: horizontalInset + indexSpacing + maxIndexWidth, y: contentSize.height + spacing))
|
||||
if previousBlock == nil {
|
||||
originY = contentSize.height + spacing
|
||||
originY += spacing
|
||||
}
|
||||
listItems.append(contentsOf: blockItems)
|
||||
contentSize.height += subLayout.contentSize.height + spacing
|
||||
@ -605,10 +611,20 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
||||
}
|
||||
|
||||
var items: [InstantPageItem] = []
|
||||
let item = InstantPageWebEmbedItem(frame: CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size), url: url, html: html, enableScrolling: allowScrolling)
|
||||
var contentSize: CGSize
|
||||
let frame = CGRect(origin: CGPoint(x: floor((boundingWidth - size.width) / 2.0), y: 0.0), size: size)
|
||||
let item: InstantPageItem
|
||||
if let url = url, let coverId = coverId, let image = media[coverId] as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
|
||||
let loadedContent = TelegramMediaWebpageLoadedContent(url: url, displayUrl: url, hash: 0, type: "video", websiteName: nil, title: nil, text: nil, embedUrl: url, embedType: "video", embedSize: size, duration: nil, author: nil, image: image, file: nil, instantPage: nil)
|
||||
let content = TelegramMediaWebpageContent.Loaded(loadedContent)
|
||||
|
||||
item = InstantPageImageItem(frame: frame, webPage: webpage, media: InstantPageMedia(index: embedIndex, media: TelegramMediaWebpage(webpageId: MediaId(namespace: 0, id: 0), content: content), url: nil, caption: nil, credit: nil), attributes: [], interactive: true, roundCorners: false, fit: false)
|
||||
|
||||
} else {
|
||||
item = InstantPageWebEmbedItem(frame: frame, url: url, html: html, enableScrolling: allowScrolling)
|
||||
}
|
||||
items.append(item)
|
||||
|
||||
var contentSize = item.frame.size
|
||||
contentSize = item.frame.size
|
||||
|
||||
let (captionItems, captionSize) = layoutCaption(caption, contentSize)
|
||||
items.append(contentsOf: captionItems)
|
||||
@ -619,17 +635,31 @@ func layoutInstantPageBlock(webpage: TelegramMediaWebpage, rtl: Bool, block: Ins
|
||||
var contentSize = CGSize(width: boundingWidth, height: 0.0)
|
||||
var items: [InstantPageItem] = []
|
||||
|
||||
var offset: CGFloat = 0.0
|
||||
|
||||
var previousItemHasRTL = false
|
||||
if let previousItem = previousItems.last as? InstantPageTextItem, previousItem.containsRTL {
|
||||
previousItemHasRTL = true
|
||||
if let previousItem = previousItems.last as? InstantPageTextItem {
|
||||
if previousItem.containsRTL {
|
||||
previousItemHasRTL = true
|
||||
}
|
||||
var minY = previousItem.frame.minY
|
||||
if let firstItem = previousItems.first {
|
||||
minY = firstItem.frame.maxY
|
||||
}
|
||||
offset = minY - previousItem.frame.maxY
|
||||
}
|
||||
if !offset.isZero {
|
||||
offset -= 40.0 + 14.0
|
||||
}
|
||||
|
||||
if let peer = peer {
|
||||
let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, safeInset: safeInset, rtl: rtl || previousItemHasRTL)
|
||||
let item = InstantPagePeerReferenceItem(frame: CGRect(origin: CGPoint(x: 0.0, y: offset), size: CGSize(width: boundingWidth, height: 40.0)), initialPeer: peer, safeInset: safeInset, transparent: !offset.isZero, rtl: rtl || previousItemHasRTL)
|
||||
items.append(item)
|
||||
contentSize.height += 40.0
|
||||
if offset.isZero {
|
||||
contentSize.height += 40.0
|
||||
}
|
||||
}
|
||||
return InstantPageLayout(origin: CGPoint(), contentSize: contentSize, items: items)
|
||||
return InstantPageLayout(origin: CGPoint(x: 0.0, y: offset), contentSize: contentSize, items: items)
|
||||
case let .anchor(name):
|
||||
let item = InstantPageAnchorItem(frame: CGRect(origin: CGPoint(), size: CGSize(width: boundingWidth, height: 0.0)), anchor: name)
|
||||
return InstantPageLayout(origin: CGPoint(), contentSize: item.frame.size, items: [item])
|
||||
|
@ -10,7 +10,7 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) ->
|
||||
return 25.0
|
||||
case (_, .blockQuote), (.blockQuote, _), (_, .pullQuote), (.pullQuote, _):
|
||||
return 27.0
|
||||
case (.kicker, .title):
|
||||
case (.kicker, .title), (.cover, .title):
|
||||
return 16.0
|
||||
case (_, .title):
|
||||
return 20.0
|
||||
@ -49,16 +49,16 @@ func spacingBetweenBlocks(upper: InstantPageBlock?, lower: InstantPageBlock?) ->
|
||||
}
|
||||
} else if let lower = lower {
|
||||
switch lower {
|
||||
case .cover, .channelBanner, .details, .anchor:
|
||||
case .cover, .channelBanner, .details:
|
||||
return 0.0
|
||||
default:
|
||||
return 24.0
|
||||
return 25.0
|
||||
}
|
||||
} else {
|
||||
if let upper = upper, case .relatedArticles = upper {
|
||||
return 0.0
|
||||
} else {
|
||||
return 24.0
|
||||
return 25.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ final class InstantPageNavigationBar: ASDisplayNode {
|
||||
private let arrowNode: ASImageNode
|
||||
private let titleNode: ASTextNode
|
||||
|
||||
private let progressNode: ASDisplayNode
|
||||
|
||||
private let intrinsicMoreSize: CGSize
|
||||
private let intrinsicSmallMoreSize: CGSize
|
||||
private let intrinsicActionSize: CGSize
|
||||
@ -25,6 +27,8 @@ final class InstantPageNavigationBar: ASDisplayNode {
|
||||
private var dimmed: Bool = false
|
||||
private var buttonsAlphaFactor: CGFloat = 1.0
|
||||
|
||||
private var currentTitle: String?
|
||||
|
||||
var back: (() -> Void)?
|
||||
var share: (() -> Void)?
|
||||
var settings: (() -> Void)?
|
||||
@ -59,6 +63,10 @@ final class InstantPageNavigationBar: ASDisplayNode {
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
|
||||
self.progressNode = ASDisplayNode()
|
||||
self.progressNode.backgroundColor = .white
|
||||
|
||||
super.init()
|
||||
|
||||
@ -72,6 +80,7 @@ final class InstantPageNavigationBar: ASDisplayNode {
|
||||
self.addSubnode(self.moreButton)
|
||||
self.addSubnode(self.actionButton)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.progressNode)
|
||||
|
||||
self.backButton.addTarget(self, action: #selector(self.backPressed), forControlEvents: .touchUpInside)
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside)
|
||||
@ -107,7 +116,7 @@ final class InstantPageNavigationBar: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, minHeight: CGFloat, maxHeight: CGFloat, topInset: CGFloat, leftInset: CGFloat, rightInset: CGFloat, 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
|
||||
if !topInset.isZero {
|
||||
progressHeight = size.height - topInset + 11.0
|
||||
@ -149,10 +158,23 @@ final class InstantPageNavigationBar: ASDisplayNode {
|
||||
alphaFactor *= 0.5
|
||||
}
|
||||
|
||||
if title != self.currentTitle {
|
||||
self.currentTitle = title
|
||||
if let title = title {
|
||||
self.titleNode.transform = CATransform3DIdentity
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center)
|
||||
let titleSize = self.titleNode.measure(CGSize(width: size.width - leftInset - rightInset - 44.0 - 88.0, height: size.height))
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: (size.width - titleSize.width) / 2.0, y: size.height - 30.0), size: titleSize)
|
||||
}
|
||||
}
|
||||
|
||||
let maxMoreOffset = self.intrinsicMoreSize.height / 2.0 + floor((44.0 - self.intrinsicMoreSize.height) / 2.0)
|
||||
let minMoreOffset = self.intrinsicSmallMoreSize.height / 2.0 + floor((20.0 - self.intrinsicSmallMoreSize.height) / 2.0)
|
||||
let moreOffset = (transitionFactor * maxMoreOffset) + ((1.0 - transitionFactor) * minMoreOffset)
|
||||
|
||||
transition.updateTransformScale(node: self.titleNode, scale: 0.75 + transitionFactor * 0.25)
|
||||
transition.updatePosition(node: self.titleNode, position: CGPoint(x: size.width / 2.0, y: size.height - moreOffset))
|
||||
|
||||
transition.updateTransformScale(node: self.moreButton, scale: buttonScaleFactor)
|
||||
transition.updatePosition(node: self.moreButton, position: CGPoint(x: size.width - rightInset - buttonScaleFactor * self.intrinsicMoreSize.width / 2.0, y: size.height - moreOffset))
|
||||
transition.updateAlpha(node: self.moreButton, alpha: alphaFactor)
|
||||
|
@ -11,17 +11,19 @@ final class InstantPagePeerReferenceItem: InstantPageItem {
|
||||
|
||||
let initialPeer: Peer
|
||||
let safeInset: CGFloat
|
||||
let transparent: Bool
|
||||
let rtl: Bool
|
||||
|
||||
init(frame: CGRect, initialPeer: Peer, safeInset: CGFloat, rtl: Bool) {
|
||||
init(frame: CGRect, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool) {
|
||||
self.frame = frame
|
||||
self.initialPeer = initialPeer
|
||||
self.safeInset = safeInset
|
||||
self.transparent = transparent
|
||||
self.rtl = rtl
|
||||
}
|
||||
|
||||
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 InstantPagePeerReferenceNode(account: account, strings: strings, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, rtl: self.rtl, openPeer: openPeer)
|
||||
return InstantPagePeerReferenceNode(account: account, strings: strings, theme: theme, initialPeer: self.initialPeer, safeInset: self.safeInset, transparent: self.transparent, rtl: self.rtl, openPeer: openPeer)
|
||||
}
|
||||
|
||||
func matchesAnchor(_ anchor: String) -> Bool {
|
||||
|
@ -45,6 +45,7 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
|
||||
private let account: Account
|
||||
let initialPeer: Peer
|
||||
let safeInset: CGFloat
|
||||
private let transparent: Bool
|
||||
private let rtl: Bool
|
||||
private var strings: PresentationStrings
|
||||
private var theme: InstantPageTheme
|
||||
@ -64,18 +65,18 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
|
||||
|
||||
private var joinState: JoinState = .none
|
||||
|
||||
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, rtl: Bool, openPeer: @escaping (PeerId) -> Void) {
|
||||
init(account: Account, strings: PresentationStrings, theme: InstantPageTheme, initialPeer: Peer, safeInset: CGFloat, transparent: Bool, rtl: Bool, openPeer: @escaping (PeerId) -> Void) {
|
||||
self.account = account
|
||||
self.strings = strings
|
||||
self.theme = theme
|
||||
self.initialPeer = initialPeer
|
||||
self.safeInset = safeInset
|
||||
self.transparent = transparent
|
||||
self.rtl = rtl
|
||||
self.openPeer = openPeer
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
|
||||
self.buttonNode = HighlightableButtonNode()
|
||||
@ -97,7 +98,14 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = theme.panelBackgroundColor
|
||||
if self.transparent {
|
||||
self.backgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
||||
self.highlightedBackgroundNode.backgroundColor = UIColor(white: 1.0, alpha: 0.1)
|
||||
} else {
|
||||
self.backgroundColor = theme.panelBackgroundColor
|
||||
self.highlightedBackgroundNode.backgroundColor = theme.panelHighlightedBackgroundColor
|
||||
}
|
||||
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.joinNode)
|
||||
@ -152,7 +160,8 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
|
||||
|
||||
self.peerDisposable = (signal |> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
strongSelf.nameNode.attributedText = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: strongSelf.theme.panelPrimaryColor)
|
||||
let textColor = strongSelf.transparent ? UIColor.white : strongSelf.theme.panelPrimaryColor
|
||||
strongSelf.nameNode.attributedText = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: textColor)
|
||||
if let peer = peer as? TelegramChannel {
|
||||
var joinState = strongSelf.joinState
|
||||
if case .member = peer.participationStatus {
|
||||
@ -195,12 +204,15 @@ final class InstantPagePeerReferenceNode: ASDisplayNode, InstantPageNode {
|
||||
|
||||
private func applyThemeAndStrings(themeUpdated: Bool) {
|
||||
if let peer = self.peer {
|
||||
self.nameNode.attributedText = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: self.theme.panelPrimaryColor)
|
||||
let textColor = self.transparent ? UIColor.white : self.theme.panelPrimaryColor
|
||||
self.nameNode.attributedText = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: textColor)
|
||||
}
|
||||
self.joinNode.setAttributedTitle(NSAttributedString(string: self.strings.Channel_JoinChannel, font: Font.medium(17.0), textColor: self.theme.panelAccentColor), for: [])
|
||||
let accentColor = self.transparent ? UIColor.white : self.theme.panelAccentColor
|
||||
self.joinNode.setAttributedTitle(NSAttributedString(string: self.strings.Channel_JoinChannel, font: Font.medium(17.0), textColor: accentColor), for: [])
|
||||
|
||||
if themeUpdated {
|
||||
self.checkNode.image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/PanelCheck"), color: self.theme.panelSecondaryColor)
|
||||
let secondaryColor = self.transparent ? UIColor.white : self.theme.panelSecondaryColor
|
||||
self.checkNode.image = generateTintedImage(image: UIImage(bundleImageName: "Instant View/PanelCheck"), color: secondaryColor)
|
||||
self.activityIndicator.type = .custom(self.theme.panelAccentColor, 22.0, 2.0, false)
|
||||
}
|
||||
self.setNeedsLayout()
|
||||
|
@ -214,7 +214,7 @@ final class InstantPageTextItem: InstantPageItem {
|
||||
if let (index, dict) = self.attributesAtPoint(point) {
|
||||
if let _ = dict[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] {
|
||||
if let rects = self.attributeRects(name: NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), at: index) {
|
||||
return rects
|
||||
return rects.map { $0.insetBy(dx: 2.0, dy: -3.0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -360,7 +360,7 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
|
||||
styleStack.pop()
|
||||
return result
|
||||
case let .url(text, url, webpageId):
|
||||
styleStack.push(.underline)
|
||||
styleStack.push(.link(webpageId != nil))
|
||||
let result = attributedStringForRichText(text, styleStack: styleStack, url: InstantPageUrlItem(url: url, webpageId: webpageId))
|
||||
styleStack.pop()
|
||||
return result
|
||||
@ -503,7 +503,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
|
||||
let lineRange = NSMakeRange(lastIndex, lineCharacterCount)
|
||||
|
||||
var stop = false
|
||||
if maxNumberOfLines > 0 && lines.count == maxNumberOfLines - 1 {
|
||||
if maxNumberOfLines > 0 && lines.count == maxNumberOfLines - 1 && lastIndex + lineCharacterCount < string.length {
|
||||
let attributes = string.attributes(at: lastIndex + lineCharacterCount - 1, effectiveRange: nil)
|
||||
if let truncateString = CFAttributedStringCreate(nil, "\u{2026}" as CFString, attributes as CFDictionary) {
|
||||
let truncateToken = CTLineCreateWithAttributedString(truncateString)
|
||||
@ -525,11 +525,20 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
|
||||
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)))
|
||||
} else if let item = attributes[NSAttributedStringKey.init(rawValue: InstantPageMarkerColorAttribute)] as? UIColor {
|
||||
}
|
||||
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, width: upperX - lowerX, height: fontLineHeight), color: item))
|
||||
} else if let item = attributes[NSAttributedStringKey.init(rawValue: InstantPageAnchorAttribute)] as? String {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,9 @@ enum InstantPageTextStyle {
|
||||
case markerColor(UIColor)
|
||||
case marker
|
||||
case anchor(String)
|
||||
case linkColor(UIColor)
|
||||
case linkMarkerColor(UIColor)
|
||||
case link(Bool)
|
||||
}
|
||||
|
||||
let InstantPageLineSpacingFactorAttribute = "LineSpacingFactorAttribute"
|
||||
@ -51,6 +54,9 @@ final class InstantPageTextStyleStack {
|
||||
var markerColor: UIColor?
|
||||
var marker: Bool?
|
||||
var anchor: String?
|
||||
var linkColor: UIColor?
|
||||
var linkMarkerColor: UIColor?
|
||||
var link: Bool?
|
||||
|
||||
for item in self.items.reversed() {
|
||||
switch item {
|
||||
@ -111,6 +117,18 @@ final class InstantPageTextStyleStack {
|
||||
if anchor == nil {
|
||||
anchor = name
|
||||
}
|
||||
case let .linkColor(color):
|
||||
if linkColor == nil {
|
||||
linkColor = color
|
||||
}
|
||||
case let .linkMarkerColor(color):
|
||||
if linkMarkerColor == nil {
|
||||
linkMarkerColor = color
|
||||
}
|
||||
case let .link(instant):
|
||||
if link == nil {
|
||||
link = instant
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +152,7 @@ final class InstantPageTextStyleStack {
|
||||
} else if fontFixed != nil && fontFixed! {
|
||||
attributes[NSAttributedStringKey.font] = UIFont(name: "Menlo-BoldItalic", size: parsedFontSize)
|
||||
} else {
|
||||
attributes[NSAttributedStringKey.font] = Font.bold(parsedFontSize)
|
||||
attributes[NSAttributedStringKey.font] = Font.semiboldItalic(parsedFontSize)
|
||||
}
|
||||
} else if bold != nil && bold! {
|
||||
if fontSerif != nil && fontSerif! {
|
||||
@ -170,10 +188,17 @@ final class InstantPageTextStyleStack {
|
||||
attributes[NSAttributedStringKey.underlineStyle] = NSUnderlineStyle.styleSingle.rawValue as NSNumber
|
||||
}
|
||||
|
||||
if let color = color {
|
||||
attributes[NSAttributedStringKey.foregroundColor] = color
|
||||
if let link = link, let linkColor = linkColor {
|
||||
attributes[NSAttributedStringKey.foregroundColor] = linkColor
|
||||
if link, let linkMarkerColor = linkMarkerColor {
|
||||
attributes[NSAttributedStringKey(rawValue: InstantPageMarkerColorAttribute)] = linkMarkerColor
|
||||
}
|
||||
} else {
|
||||
attributes[NSAttributedStringKey.foregroundColor] = UIColor.black
|
||||
if let color = color {
|
||||
attributes[NSAttributedStringKey.foregroundColor] = color
|
||||
} else {
|
||||
attributes[NSAttributedStringKey.foregroundColor] = UIColor.black
|
||||
}
|
||||
}
|
||||
|
||||
if let lineSpacingFactor = lineSpacingFactor {
|
||||
|
@ -87,6 +87,7 @@ final class InstantPageTheme {
|
||||
|
||||
let codeBlockBackgroundColor: UIColor
|
||||
|
||||
let linkColor: UIColor
|
||||
let textHighlightColor: UIColor
|
||||
let linkHighlightColor: UIColor
|
||||
let markerColor: UIColor
|
||||
@ -104,11 +105,12 @@ final class InstantPageTheme {
|
||||
|
||||
let imageEmptyColor: UIColor?
|
||||
|
||||
init(pageBackgroundColor: UIColor, textCategories: InstantPageTextCategories, serif: Bool, codeBlockBackgroundColor: 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, imageEmptyColor: UIColor?) {
|
||||
self.pageBackgroundColor = pageBackgroundColor
|
||||
self.textCategories = textCategories
|
||||
self.serif = serif
|
||||
self.codeBlockBackgroundColor = codeBlockBackgroundColor
|
||||
self.linkColor = linkColor
|
||||
self.textHighlightColor = textHighlightColor
|
||||
self.linkHighlightColor = linkHighlightColor
|
||||
self.markerColor = markerColor
|
||||
@ -124,7 +126,7 @@ final class InstantPageTheme {
|
||||
}
|
||||
|
||||
func withUpdatedFontStyles(sizeMultiplier: CGFloat, forceSerif: Bool) -> InstantPageTheme {
|
||||
return InstantPageTheme(pageBackgroundColor: pageBackgroundColor, textCategories: self.textCategories.withUpdatedFontStyles(sizeMultiplier: sizeMultiplier, forceSerif: forceSerif), serif: forceSerif, codeBlockBackgroundColor: codeBlockBackgroundColor, 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, imageEmptyColor: imageEmptyColor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,8 +144,9 @@ private let lightTheme = InstantPageTheme(
|
||||
),
|
||||
serif: false,
|
||||
codeBlockBackgroundColor: UIColor(rgb: 0xf5f8fc),
|
||||
linkColor: UIColor(rgb: 0x007ee5),
|
||||
textHighlightColor: UIColor(rgb: 0, alpha: 0.12),
|
||||
linkHighlightColor: UIColor(rgb: 0, alpha: 0.12),
|
||||
linkHighlightColor: UIColor(rgb: 0x007ee5, alpha: 0.07),
|
||||
markerColor: UIColor(rgb: 0xfef3bc),
|
||||
panelBackgroundColor: UIColor(rgb: 0xf3f4f5),
|
||||
panelHighlightedBackgroundColor: UIColor(rgb: 0xe7e7e7),
|
||||
@ -170,8 +173,9 @@ private let sepiaTheme = InstantPageTheme(
|
||||
),
|
||||
serif: false,
|
||||
codeBlockBackgroundColor: UIColor(rgb: 0xefe7d6),
|
||||
linkColor: UIColor(rgb: 0xd19600),
|
||||
textHighlightColor: UIColor(rgb: 0, alpha: 0.1),
|
||||
linkHighlightColor: UIColor(rgb: 0, alpha: 0.1),
|
||||
linkHighlightColor: UIColor(rgb: 0xd19600, alpha: 0.1),
|
||||
markerColor: UIColor(rgb: 0xe5ddcd),
|
||||
panelBackgroundColor: UIColor(rgb: 0xefe7d6),
|
||||
panelHighlightedBackgroundColor: UIColor(rgb: 0xe3dccb),
|
||||
@ -198,8 +202,9 @@ private let grayTheme = InstantPageTheme(
|
||||
),
|
||||
serif: false,
|
||||
codeBlockBackgroundColor: UIColor(rgb: 0x555556),
|
||||
linkColor: UIColor(rgb: 0x5ac8fa),
|
||||
textHighlightColor: UIColor(rgb: 0, alpha: 0.16),
|
||||
linkHighlightColor: UIColor(rgb: 0, alpha: 0.16),
|
||||
linkHighlightColor: UIColor(rgb: 0x5ac8fa, alpha: 0.13),
|
||||
markerColor: UIColor(rgb: 0x4b4b4b),
|
||||
panelBackgroundColor: UIColor(rgb: 0x555556),
|
||||
panelHighlightedBackgroundColor: UIColor(rgb: 0x505051),
|
||||
@ -226,8 +231,9 @@ private let darkTheme = InstantPageTheme(
|
||||
),
|
||||
serif: false,
|
||||
codeBlockBackgroundColor: UIColor(rgb: 0x131313),
|
||||
linkColor: UIColor(rgb: 0x5ac8fa),
|
||||
textHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.1),
|
||||
linkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.1),
|
||||
linkHighlightColor: UIColor(rgb: 0x5ac8fa, alpha: 0.2),
|
||||
markerColor: UIColor(rgb: 0x313131),
|
||||
panelBackgroundColor: UIColor(rgb: 0x131313),
|
||||
panelHighlightedBackgroundColor: UIColor(rgb: 0x1f1f1f),
|
||||
|
@ -5,7 +5,7 @@ import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
|
||||
private let updatingAvatarOverlayImage = generateFilledCircleImage(diameter: 66.0, color: UIColor(white: 1.0, alpha: 0.5), backgroundColor: nil)
|
||||
private let updatingAvatarOverlayImage = generateFilledCircleImage(diameter: 66.0, color: UIColor(white: 0.0, alpha: 0.4), backgroundColor: nil)
|
||||
|
||||
enum ItemListAvatarAndNameInfoItemTitleType {
|
||||
case group
|
||||
@ -254,6 +254,7 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
|
||||
private let avatarNode: AvatarNode
|
||||
private let updatingAvatarOverlay: ASImageNode
|
||||
private let activityIndicator: ActivityIndicator
|
||||
|
||||
private let callButton: HighlightableButtonNode
|
||||
|
||||
@ -304,6 +305,9 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
self.updatingAvatarOverlay.displayWithoutProcessing = true
|
||||
self.updatingAvatarOverlay.displaysAsynchronously = false
|
||||
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(.white, 24.0, 1.0, false))
|
||||
self.activityIndicator.isHidden = true
|
||||
|
||||
self.nameNode = TextNode()
|
||||
self.nameNode.isUserInteractionEnabled = false
|
||||
self.nameNode.contentMode = .left
|
||||
@ -324,6 +328,8 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.activityIndicator)
|
||||
|
||||
self.addSubnode(self.nameNode)
|
||||
self.addSubnode(self.statusNode)
|
||||
|
||||
@ -531,6 +537,7 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
if strongSelf.updatingAvatarOverlay.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.updatingAvatarOverlay, aboveSubnode: strongSelf.avatarNode)
|
||||
}
|
||||
strongSelf.activityIndicator.isHidden = false
|
||||
} else if strongSelf.updatingAvatarOverlay.supernode != nil {
|
||||
if animated {
|
||||
strongSelf.updatingAvatarOverlay.alpha = 0.0
|
||||
@ -542,6 +549,7 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
} else {
|
||||
strongSelf.updatingAvatarOverlay.removeFromSupernode()
|
||||
}
|
||||
strongSelf.activityIndicator.isHidden = true
|
||||
}
|
||||
|
||||
if item.call != nil {
|
||||
@ -621,6 +629,9 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
|
||||
strongSelf.avatarNode.frame = avatarFrame
|
||||
strongSelf.updatingAvatarOverlay.frame = avatarFrame
|
||||
|
||||
let indicatorSize = strongSelf.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
||||
strongSelf.activityIndicator.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(avatarFrame.midX - indicatorSize.width / 2.0), y: floorToScreenPixels(avatarFrame.midY - indicatorSize.height / 2.0)), size: indicatorSize)
|
||||
|
||||
let nameY: CGFloat
|
||||
if statusText.isEmpty {
|
||||
nameY = floor((layout.contentSize.height - nameNodeLayout.size.height) / 2.0)
|
||||
|
@ -476,13 +476,19 @@ final class ListMessageSnippetItemNode: ListMessageNode {
|
||||
|
||||
func activateMedia() {
|
||||
if let item = self.item, let currentPrimaryUrl = self.currentPrimaryUrl {
|
||||
if let webpage = self.currentMedia as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.instantPage != nil {
|
||||
if websiteType(of: content) == .instagram {
|
||||
if !item.controllerInteraction.openMessage(item.message, .default) {
|
||||
if let webpage = self.currentMedia as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
if content.instantPage != nil {
|
||||
if websiteType(of: content) == .instagram {
|
||||
if !item.controllerInteraction.openMessage(item.message, .default) {
|
||||
item.controllerInteraction.openInstantPage(item.message)
|
||||
}
|
||||
} else {
|
||||
item.controllerInteraction.openInstantPage(item.message)
|
||||
}
|
||||
} else {
|
||||
item.controllerInteraction.openInstantPage(item.message)
|
||||
if isTelegramMeLink(content.url) || !item.controllerInteraction.openMessage(item.message, .default) {
|
||||
item.controllerInteraction.openUrl(currentPrimaryUrl, false, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !item.controllerInteraction.openMessage(item.message, .default) {
|
||||
|
@ -20,7 +20,7 @@ private enum ChatMessageGalleryControllerData {
|
||||
case chatAvatars(AvatarGalleryController, Media)
|
||||
}
|
||||
|
||||
private func chatMessageGalleryControllerData(account: Account, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool, synchronousLoad: Bool) -> ChatMessageGalleryControllerData? {
|
||||
private func chatMessageGalleryControllerData(account: Account, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool, synchronousLoad: Bool, excludeWebPageMedia: Bool = false) -> ChatMessageGalleryControllerData? {
|
||||
var galleryMedia: Media?
|
||||
var otherMedia: Media?
|
||||
var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])?
|
||||
@ -42,7 +42,7 @@ private func chatMessageGalleryControllerData(account: Account, message: Message
|
||||
galleryMedia = file
|
||||
} else if let image = media as? TelegramMediaImage {
|
||||
galleryMedia = image
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, !excludeWebPageMedia {
|
||||
if let file = content.file {
|
||||
galleryMedia = file
|
||||
} else if let image = content.image {
|
||||
@ -151,8 +151,8 @@ func chatMessagePreviewControllerData(account: Account, message: Message, standa
|
||||
return nil
|
||||
}
|
||||
|
||||
func openChatMessage(account: Account, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool = false, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> UIView?)?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal<InstantPageGalleryEntry?, NoError>, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void) -> Bool {
|
||||
if let mediaData = chatMessageGalleryControllerData(account: account, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, stream: stream, synchronousLoad: false) {
|
||||
func openChatMessage(account: Account, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, stream: Bool = false, excludeWebPageMedia: Bool = false, navigationController: NavigationController?, modal: Bool = false, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> UIView?)?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal<InstantPageGalleryEntry?, NoError>, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void) -> Bool {
|
||||
if let mediaData = chatMessageGalleryControllerData(account: account, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, stream: stream, synchronousLoad: false, excludeWebPageMedia: excludeWebPageMedia) {
|
||||
switch mediaData {
|
||||
case let .url(url):
|
||||
openUrl(url)
|
||||
|
@ -78,13 +78,13 @@ public class PeerMediaCollectionController: TelegramController {
|
||||
}
|
||||
})
|
||||
|
||||
let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, _ in
|
||||
let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, mode in
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded, let galleryMessage = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id) {
|
||||
guard let navigationController = strongSelf.navigationController as? NavigationController else {
|
||||
return false
|
||||
}
|
||||
strongSelf.mediaCollectionDisplayNode.view.endEditing(true)
|
||||
return openChatMessage(account: account, message: galleryMessage.message, standalone: false, reverseMessageGalleryOrder: true, navigationController: navigationController, dismissInput: {
|
||||
return openChatMessage(account: account, message: galleryMessage.message, standalone: false, reverseMessageGalleryOrder: true, excludeWebPageMedia: mode == .shared, navigationController: navigationController, dismissInput: {
|
||||
self?.mediaCollectionDisplayNode.view.endEditing(true)
|
||||
}, present: { c, a in
|
||||
self?.present(c, in: .window(.root), with: a, blockInteraction: true)
|
||||
|
@ -1811,28 +1811,69 @@ func chatSecretMessageVideo(account: Account, videoReference: FileMediaReference
|
||||
}
|
||||
}
|
||||
|
||||
private func orientationFromExif(orientation: Int) -> UIImageOrientation {
|
||||
switch orientation {
|
||||
case 1:
|
||||
return .up;
|
||||
case 3:
|
||||
return .down;
|
||||
case 8:
|
||||
return .left;
|
||||
case 6:
|
||||
return .right;
|
||||
case 2:
|
||||
return .upMirrored;
|
||||
case 4:
|
||||
return .downMirrored;
|
||||
case 5:
|
||||
return .leftMirrored;
|
||||
case 7:
|
||||
return .rightMirrored;
|
||||
default:
|
||||
return .up
|
||||
}
|
||||
}
|
||||
|
||||
func imageOrientationFromSource(_ source: CGImageSource) -> UIImageOrientation {
|
||||
if let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) {
|
||||
let dict = properties as NSDictionary
|
||||
if let value = dict.object(forKey: "Orientation") as? NSNumber {
|
||||
return UIImageOrientation(rawValue: value.intValue) ?? .up
|
||||
if let value = dict.object(forKey: kCGImagePropertyOrientation) as? NSNumber {
|
||||
return orientationFromExif(orientation: value.intValue)
|
||||
}
|
||||
}
|
||||
|
||||
return .up
|
||||
}
|
||||
|
||||
private func rotationFor(_ orientation: UIImageOrientation) -> CGFloat {
|
||||
switch orientation {
|
||||
case .left:
|
||||
return CGFloat.pi / 2.0
|
||||
case .right:
|
||||
return -CGFloat.pi / 2.0
|
||||
case .down:
|
||||
return -CGFloat.pi
|
||||
default:
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func drawImage(context: CGContext, image: CGImage, orientation: UIImageOrientation, in rect: CGRect) {
|
||||
var restore = true
|
||||
var drawRect = rect
|
||||
switch orientation {
|
||||
case .left:
|
||||
fallthrough
|
||||
case .right:
|
||||
fallthrough
|
||||
case .down:
|
||||
let angle = rotationFor(orientation)
|
||||
context.saveGState()
|
||||
context.translateBy(x: rect.midX, y: rect.midY)
|
||||
context.rotate(by: -CGFloat.pi / 2.0)
|
||||
context.rotate(by: angle)
|
||||
context.translateBy(x: -rect.midX, y: -rect.midY)
|
||||
var t = CGAffineTransform(translationX: rect.midX, y: rect.midY)
|
||||
t = t.rotated(by: -CGFloat.pi / 2.0)
|
||||
t = t.rotated(by: angle)
|
||||
t = t.translatedBy(x: -rect.midX, y: -rect.midY)
|
||||
|
||||
drawRect = rect.applying(t)
|
||||
@ -1949,6 +1990,51 @@ func chatMessageImageFile(account: Account, fileReference: FileMediaReference, t
|
||||
}
|
||||
}
|
||||
|
||||
func instantPageImageFile(account: Account, fileReference: FileMediaReference, fetched: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
return chatMessageFileDatas(account: account, fileReference: fileReference, progressive: false, fetched: fetched)
|
||||
|> map { (thumbnailData, fullSizePath, fullSizeComplete) in
|
||||
return { arguments in
|
||||
assertNotOnMainThread()
|
||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||
|
||||
let drawingRect = arguments.drawingRect
|
||||
let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize)
|
||||
|
||||
var fullSizeImage: CGImage?
|
||||
var imageOrientation: UIImageOrientation = .up
|
||||
if let fullSizePath = fullSizePath {
|
||||
if fullSizeComplete {
|
||||
let options = NSMutableDictionary()
|
||||
options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String)
|
||||
options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String)
|
||||
if let imageSource = CGImageSourceCreateWithURL(URL(fileURLWithPath: fullSizePath) as CFURL, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) {
|
||||
imageOrientation = imageOrientationFromSource(imageSource)
|
||||
fullSizeImage = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
|
||||
|
||||
context.withFlippedContext { c in
|
||||
if var fullSizeImage = fullSizeImage {
|
||||
// if true || imageIsMonochrome(fullSizeImage), let tintedImage = generateTintedImage(image: UIImage(cgImage: fullSizeImage), color: .white)?.cgImage {
|
||||
// fullSizeImage = tintedImage
|
||||
// }
|
||||
|
||||
c.setBlendMode(.normal)
|
||||
c.interpolationQuality = .medium
|
||||
drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect)
|
||||
}
|
||||
}
|
||||
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func avatarGalleryPhotoDatas(account: Account, representations: [(TelegramMediaImageRepresentation, MediaResourceReference)], autoFetchFullSize: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
||||
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.0 })), let largestRepresentation = largestImageRepresentation(representations.map({ $0.0 })), let smallestIndex = representations.index(where: { $0.0 == smallestRepresentation }), let largestIndex = representations.index(where: { $0.0 == largestRepresentation }) {
|
||||
let maybeFullSize = account.postbox.mediaBox.resourceData(largestRepresentation.resource)
|
||||
@ -2425,80 +2511,59 @@ private func drawAlbumArtPlaceholder(into c: CGContext, arguments: TransformImag
|
||||
}
|
||||
|
||||
func playerAlbumArt(postbox: Postbox, fileReference: FileMediaReference?, albumArt: SharedMediaPlaybackAlbumArt?, thumbnail: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
|
||||
var fileThumbnailResource: TelegramMediaResource?
|
||||
if let fileReference = fileReference, let smallestRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
|
||||
fileThumbnailResource = smallestRepresentation.resource
|
||||
var fileArtworkData: Signal<Data?, NoError> = .single(nil)
|
||||
if let fileReference = fileReference, let size = fileReference.media.resource.size {
|
||||
fileArtworkData = fileArtworkData
|
||||
|> then(postbox.mediaBox.resourceData(fileReference.media.resource, size: size, in: 0 ..< min(size, 1024 * 256))
|
||||
|> mapToSignal { data -> Signal<Data?, NoError> in
|
||||
return .single(albumArtworkData(data))
|
||||
})
|
||||
}
|
||||
|
||||
var remoteArtworkData: Signal<(Data?, Data?, Bool), NoError> = .single((nil, nil, false))
|
||||
if let albumArt = albumArt {
|
||||
if thumbnail {
|
||||
return albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource) |> map { thumbnailData in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||
|
||||
var sourceImage: UIImage?
|
||||
if let thumbnailData = thumbnailData, let image = UIImage(data: thumbnailData) {
|
||||
sourceImage = image
|
||||
}
|
||||
|
||||
if let sourceImage = sourceImage, let cgImage = sourceImage.cgImage {
|
||||
let imageSize = sourceImage.size.aspectFilled(arguments.drawingRect.size)
|
||||
context.withFlippedContext { c in
|
||||
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.size.width - imageSize.width) / 2.0), y: floor((arguments.drawingRect.size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||
}
|
||||
} else {
|
||||
context.withFlippedContext { c in
|
||||
drawAlbumArtPlaceholder(into: c, arguments: arguments, thumbnail: thumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
return context
|
||||
}
|
||||
remoteArtworkData = albumArtThumbnailData(postbox: postbox, thumbnail: albumArt.thumbnailResource)
|
||||
|> map { thumbnailData in
|
||||
return (thumbnailData, nil, false)
|
||||
}
|
||||
} else {
|
||||
return albumArtFullSizeDatas(postbox: postbox, thumbnail: albumArt.thumbnailResource, fullSize: albumArt.fullSizeResource) |> map { (thumbnailData, fullSizeData, fullSizeComplete) in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||
|
||||
var sourceImage: UIImage?
|
||||
if fullSizeComplete, let fullSizeData = fullSizeData, let image = UIImage(data: fullSizeData) {
|
||||
sourceImage = image
|
||||
} else if let thumbnailData = thumbnailData, let image = UIImage(data: thumbnailData) {
|
||||
sourceImage = image
|
||||
}
|
||||
|
||||
if let sourceImage = sourceImage, let cgImage = sourceImage.cgImage {
|
||||
let imageSize = sourceImage.size.aspectFilled(arguments.drawingRect.size)
|
||||
context.withFlippedContext { c in
|
||||
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.size.width - imageSize.width) / 2.0), y: floor((arguments.drawingRect.size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||
}
|
||||
} else {
|
||||
context.withFlippedContext { c in
|
||||
drawAlbumArtPlaceholder(into: c, arguments: arguments, thumbnail: thumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
remoteArtworkData = albumArtFullSizeDatas(postbox: postbox, thumbnail: albumArt.thumbnailResource, fullSize: albumArt.fullSizeResource)
|
||||
}
|
||||
} else {
|
||||
return .single({ arguments in
|
||||
}
|
||||
|
||||
return combineLatest(fileArtworkData, remoteArtworkData)
|
||||
|> map { fileArtworkData, remoteArtworkData in
|
||||
let remoteThumbnailData = remoteArtworkData.0
|
||||
let remoteFullSizeData = remoteArtworkData.1
|
||||
let remoteFullSizeComplete = remoteArtworkData.2
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||
|
||||
context.withFlippedContext { c in
|
||||
drawAlbumArtPlaceholder(into: c, arguments: arguments, thumbnail: thumbnail)
|
||||
|
||||
var sourceImage: UIImage?
|
||||
if let fileArtworkData = fileArtworkData, let image = UIImage(data: fileArtworkData) {
|
||||
sourceImage = image
|
||||
} else if remoteFullSizeComplete, let fullSizeData = remoteFullSizeData, let image = UIImage(data: fullSizeData) {
|
||||
sourceImage = image
|
||||
} else if let thumbnailData = remoteThumbnailData, let image = UIImage(data: thumbnailData) {
|
||||
sourceImage = image
|
||||
}
|
||||
|
||||
if let sourceImage = sourceImage, let cgImage = sourceImage.cgImage {
|
||||
let imageSize = sourceImage.size.aspectFilled(arguments.drawingRect.size)
|
||||
context.withFlippedContext { c in
|
||||
c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.size.width - imageSize.width) / 2.0), y: floor((arguments.drawingRect.size.height - imageSize.height) / 2.0)), size: imageSize))
|
||||
}
|
||||
} else {
|
||||
context.withFlippedContext { c in
|
||||
drawAlbumArtPlaceholder(into: c, arguments: arguments, thumbnail: thumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
addCorners(context, arguments: arguments)
|
||||
|
||||
return context
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,27 +165,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
strongSelf.peersContentNode?.updateFoundPeers()
|
||||
}
|
||||
|
||||
func updateActionNodesAlpha(_ nodes: [ASDisplayNode], alpha: CGFloat) {
|
||||
for node in nodes {
|
||||
if !node.alpha.isEqual(to: alpha) {
|
||||
let previousAlpha = node.alpha
|
||||
node.alpha = alpha
|
||||
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: alpha.isZero ? 0.18 : 0.32)
|
||||
|
||||
if let inputNode = node as? ShareInputFieldNode, alpha.isZero {
|
||||
inputNode.deactivateInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let actionNodes: [ASDisplayNode]
|
||||
if strongSelf.defaultAction == nil {
|
||||
actionNodes = [strongSelf.inputFieldNode, strongSelf.actionsBackgroundNode, strongSelf.actionButtonNode, strongSelf.actionSeparatorNode]
|
||||
} else {
|
||||
actionNodes = [strongSelf.inputFieldNode]
|
||||
}
|
||||
updateActionNodesAlpha(actionNodes, alpha: strongSelf.controllerInteraction!.selectedPeers.isEmpty ? 0.0 : 1.0)
|
||||
strongSelf.setActionNodesHidden(strongSelf.controllerInteraction!.selectedPeers.isEmpty, inputField: true, actions: strongSelf.defaultAction == nil)
|
||||
|
||||
strongSelf.updateButton()
|
||||
|
||||
@ -251,6 +231,31 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
}
|
||||
}
|
||||
|
||||
func setActionNodesHidden(_ hidden: Bool, inputField: Bool = false, actions: Bool = false) {
|
||||
func updateActionNodesAlpha(_ nodes: [ASDisplayNode], alpha: CGFloat) {
|
||||
for node in nodes {
|
||||
if !node.alpha.isEqual(to: alpha) {
|
||||
let previousAlpha = node.alpha
|
||||
node.alpha = alpha
|
||||
node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: alpha.isZero ? 0.18 : 0.32)
|
||||
|
||||
if let inputNode = node as? ShareInputFieldNode, alpha.isZero {
|
||||
inputNode.deactivateInput()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var actionNodes: [ASDisplayNode] = []
|
||||
if inputField {
|
||||
actionNodes.append(self.inputFieldNode)
|
||||
}
|
||||
if actions {
|
||||
actionNodes.append(contentsOf: [self.actionsBackgroundNode, self.actionButtonNode, self.actionSeparatorNode])
|
||||
}
|
||||
updateActionNodesAlpha(actionNodes, alpha: hidden ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
func transitionToContentNode(_ contentNode: (ASDisplayNode & ShareContentContainerNode)?, fastOut: Bool = false) {
|
||||
if self.contentNode !== contentNode {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
@ -298,6 +303,12 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
contentNode.activate()
|
||||
previous.deactivate()
|
||||
|
||||
if contentNode is ShareSearchContainerNode {
|
||||
self.setActionNodesHidden(true, inputField: true, actions: true)
|
||||
} else if !(contentNode is ShareLoadingContainerNode) {
|
||||
self.setActionNodesHidden(false, inputField: !self.controllerInteraction!.selectedPeers.isEmpty, actions: true)
|
||||
}
|
||||
} else {
|
||||
if let contentNode = self.contentNode {
|
||||
contentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in
|
||||
|
@ -39,7 +39,7 @@ func chatInputStateStringWithAppliedEntities(_ text: String, entities: [MessageT
|
||||
}
|
||||
|
||||
|
||||
func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, fixedFont: UIFont) -> NSAttributedString {
|
||||
func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], baseColor: UIColor, linkColor: UIColor, baseFont: UIFont, linkFont: UIFont, boldFont: UIFont, italicFont: UIFont, fixedFont: UIFont, underlineLinks: Bool = true) -> NSAttributedString {
|
||||
var nsString: NSString?
|
||||
let string = NSMutableAttributedString(string: text, attributes: [NSAttributedStringKey.font: baseFont, NSAttributedStringKey.foregroundColor: baseColor])
|
||||
var skipEntity = false
|
||||
@ -65,7 +65,9 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
switch entity.type {
|
||||
case .Url:
|
||||
string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range)
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
if underlineLinks {
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
}
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
}
|
||||
@ -75,7 +77,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
}
|
||||
if underlineAllLinks {
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
}
|
||||
string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: "mailto:\(nsString!.substring(with: range))", range: range)
|
||||
@ -84,7 +86,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
}
|
||||
if underlineAllLinks {
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
}
|
||||
string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: "tel:\(nsString!.substring(with: range))", range: range)
|
||||
@ -93,7 +95,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
if nsString == nil {
|
||||
nsString = text as NSString
|
||||
}
|
||||
if underlineAllLinks {
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
}
|
||||
string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.URL), value: url, range: range)
|
||||
@ -103,7 +105,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
string.addAttribute(NSAttributedStringKey.font, value: italicFont, range: range)
|
||||
case .Mention:
|
||||
string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range)
|
||||
if linkColor.isEqual(baseColor) {
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
}
|
||||
if linkFont !== baseFont {
|
||||
@ -115,7 +117,7 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.PeerTextMention), value: nsString!.substring(with: range), range: range)
|
||||
case let .TextMention(peerId):
|
||||
string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range)
|
||||
if linkColor.isEqual(baseColor) {
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
}
|
||||
if linkFont !== baseFont {
|
||||
@ -146,14 +148,14 @@ func stringWithAppliedEntities(_ text: String, entities: [MessageTextEntity], ba
|
||||
}
|
||||
if !skipEntity {
|
||||
string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range)
|
||||
if linkColor.isEqual(baseColor) {
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
}
|
||||
string.addAttribute(NSAttributedStringKey(rawValue: TelegramTextAttributes.Hashtag), value: TelegramHashtag(peerName: nil, hashtag: hashtag), range: range)
|
||||
}
|
||||
case .BotCommand:
|
||||
string.addAttribute(NSAttributedStringKey.foregroundColor, value: linkColor, range: range)
|
||||
if linkColor.isEqual(baseColor) {
|
||||
if underlineLinks && underlineAllLinks {
|
||||
string.addAttribute(NSAttributedStringKey.underlineStyle, value: NSUnderlineStyle.styleSingle.rawValue as NSNumber, range: range)
|
||||
}
|
||||
if nsString == nil {
|
||||
|
@ -30,4 +30,5 @@ module TelegramUIPrivateModule {
|
||||
header "../EDSunriseSet.h"
|
||||
header "../TGBridgeAudioDecoder.h"
|
||||
header "../TGBridgeAudioEncoder.h"
|
||||
header "../ID3Artwork.h"
|
||||
}
|
||||
|
@ -161,6 +161,20 @@ private func resolveInternalUrl(account: Account, url: ParsedInternalUrl) -> Sig
|
||||
}
|
||||
}
|
||||
|
||||
func isTelegramMeLink(_ url: String) -> Bool {
|
||||
let schemes = ["http://", "https://", ""]
|
||||
let baseTelegramMePaths = ["telegram.me", "t.me"]
|
||||
for basePath in baseTelegramMePaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func parseProxyUrl(_ url: String) -> (host: String, port: Int32, username: String?, password: String?, secret: Data?)? {
|
||||
let schemes = ["http://", "https://", ""]
|
||||
let baseTelegramMePaths = ["telegram.me", "t.me"]
|
||||
|
Loading…
x
Reference in New Issue
Block a user