Album artwork from ID3 tags

Instant View improvements
This commit is contained in:
Ilya Laktyushin 2018-11-17 12:32:30 +04:00
parent 9bd9327502
commit c5c90f76d4
37 changed files with 618 additions and 411 deletions

View File

@ -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 */,

View File

@ -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)

View File

@ -40,6 +40,7 @@ public enum ChatControllerInteractionLongTapAction {
public enum ChatControllerInteractionOpenMessageMode {
case `default`
case stream
case shared
}
public final class ChatControllerInteraction {

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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

View File

@ -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())
}

View File

@ -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()

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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
View 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
View 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;
}

View File

@ -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
}

View File

@ -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)?

View File

@ -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 {

View File

@ -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)

View File

@ -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())
}
}

View File

@ -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])

View File

@ -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
}
}
}

View File

@ -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)

View File

@ -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 {

View File

@ -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()

View File

@ -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))
}
}

View File

@ -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 {

View File

@ -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),

View File

@ -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)

View File

@ -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) {

View File

@ -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)

View File

@ -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)

View File

@ -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
})
}
}
}

View File

@ -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

View File

@ -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 {

View File

@ -30,4 +30,5 @@ module TelegramUIPrivateModule {
header "../EDSunriseSet.h"
header "../TGBridgeAudioDecoder.h"
header "../TGBridgeAudioEncoder.h"
header "../ID3Artwork.h"
}

View File

@ -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"]