diff --git a/Telegram/Telegram-iOS/Resources/Passcode.tgs b/Telegram/Telegram-iOS/Resources/Passcode.tgs new file mode 100644 index 0000000000..0f52ff3221 Binary files /dev/null and b/Telegram/Telegram-iOS/Resources/Passcode.tgs differ diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index a51cf0233d..9795c13e04 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -535,7 +535,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage } - let (_, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) + let (_, initialHideAuthor, messageText, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" } @@ -569,7 +569,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { result += item.presentationData.strings.VoiceOver_ChatList_OutgoingMessage } - let (_, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) + let (_, initialHideAuthor, messageText, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: peer, accountPeerId: item.context.account.peerId, isPeerGroup: false) if message.flags.contains(.Incoming), !initialHideAuthor, let author = message.author, case .user = author { result += "\n\(item.presentationData.strings.VoiceOver_ChatList_MessageFrom(author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)).string)" } @@ -965,7 +965,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let leftInset: CGFloat = params.leftInset + avatarLeftInset enum ContentData { - case chat(itemPeer: EngineRenderedPeer, peer: EnginePeer?, hideAuthor: Bool, messageText: String) + case chat(itemPeer: EngineRenderedPeer, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?) case group(peers: [EngineChatList.GroupItem.Item]) } @@ -974,14 +974,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var hideAuthor = false switch contentPeer { case let .chat(itemPeer): - var (peer, initialHideAuthor, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup) + var (peer, initialHideAuthor, messageText, spoilers) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: messages, chatPeer: itemPeer, accountPeerId: item.context.account.peerId, enableMediaEmoji: !enableChatListPhotos, isPeerGroup: isPeerGroup) if case let .psa(_, maybePsaText) = promoInfo, let psaText = maybePsaText { initialHideAuthor = true messageText = psaText } - contentData = .chat(itemPeer: itemPeer, peer: peer, hideAuthor: hideAuthor, messageText: messageText) + contentData = .chat(itemPeer: itemPeer, peer: peer, hideAuthor: hideAuthor, messageText: messageText, spoilers: spoilers) hideAuthor = initialHideAuthor case let .group(groupPeers): contentData = .group(peers: groupPeers) @@ -1012,7 +1012,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var contentImageSpecs: [(message: EngineMessage, media: EngineMedia, size: CGSize)] = [] switch contentData { - case let .chat(itemPeer, _, _, text): + case let .chat(itemPeer, _, _, text, spoilers): var isUser = false if case .user = itemPeer.chatMainPeer { isUser = true @@ -1039,7 +1039,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { messageText = currentChatListText.1 chatListText = currentChatListText } else { - messageText = foldLineBreaks(text) + if let spoilers = spoilers, !spoilers.isEmpty { + messageText = text + } else { + messageText = foldLineBreaks(text) + } chatListText = (text, messageText) } @@ -1053,6 +1057,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else if let message = messages.last { var composedString: NSMutableAttributedString + if let peerText = peerText { + authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor) + } + let entities = (message._asMessage().textEntitiesAttribute?.entities ?? []).filter { entity in if case .Spoiler = entity.type { return true @@ -1062,7 +1070,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let messageString: NSAttributedString if !message.text.isEmpty && entities.count > 0 { - messageString = stringWithAppliedEntities(message.text, entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) + messageString = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: authorAttributedString == nil ? 2 : 1), entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) + } else if let spoilers = spoilers { + let mutableString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor) + for range in spoilers { + mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: range) + } + messageString = mutableString } else { messageString = NSAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor) } @@ -1109,9 +1123,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { attributedText = composedString - if let peerText = peerText { - authorAttributedString = NSAttributedString(string: peerText, font: textFont, textColor: theme.authorNameColor) - } + var displayMediaPreviews = true if message._asMessage().containsSecretMedia { @@ -1202,7 +1214,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } switch contentData { - case let .chat(itemPeer, _, _, _): + case let .chat(itemPeer, _, _, _, _): if let message = messages.last, case let .user(author) = message.author, displayAsMessage { titleAttributedString = NSAttributedString(string: author.id == account.peerId ? item.presentationData.strings.DialogList_You : EnginePeer.user(author).displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder), font: titleFont, textColor: theme.titleColor) } else if isPeerGroup { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 2ab683fa11..fdcb30c450 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -44,13 +44,14 @@ private func messageGroupType(messages: [EngineMessage]) -> MessageGroupType { return currentType } -public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String) { +public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, messages: [EngineMessage], chatPeer: EngineRenderedPeer, accountPeerId: EnginePeer.Id, enableMediaEmoji: Bool = true, isPeerGroup: Bool = false) -> (peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?) { let peer: EnginePeer? let message = messages.last var hideAuthor = false var messageText: String + var spoilers: [NSRange]? if let message = message { if let messageMain = messageMainPeer(message) { peer = messageMain @@ -268,12 +269,13 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: } default: hideAuthor = true - if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { + if let (text, textSpoilers) = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { messageText = text + spoilers = textSpoilers } } case _ as TelegramMediaExpiredContent: - if let text = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { + if let (text, _) = plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: true) { messageText = text } case let poll as TelegramMediaPoll: @@ -312,5 +314,5 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: } } - return (peer, hideAuthor, messageText) + return (peer, hideAuthor, messageText, spoilers) } diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index d855be2af2..570deb5dda 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -409,8 +409,15 @@ open class NavigationController: UINavigationController, ContainableController, let overlayContainerLayout = layout if let inCallStatusBar = self.inCallStatusBar { - var inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(layout.statusBarHeight ?? 0.0, max(40.0, layout.safeInsets.top)))) - if layout.deviceMetrics.hasTopNotch { + let isLandscape = layout.size.width > layout.size.height + var minHeight: CGFloat + if case .compact = layout.metrics.widthClass, isLandscape { + minHeight = 22.0 + } else { + minHeight = 40.0 + } + var inCallStatusBarFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(layout.statusBarHeight ?? 0.0, max(minHeight, layout.safeInsets.top)))) + if layout.deviceMetrics.hasTopNotch && !isLandscape { inCallStatusBarFrame.size.height += 12.0 } if inCallStatusBar.frame.isEmpty { diff --git a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift index b466f8665f..341905959f 100644 --- a/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift +++ b/submodules/JoinLinkPreviewUI/Sources/JoinLinkPreviewPeerContentNode.swift @@ -182,10 +182,12 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer } func updateLayout(size: CGSize, isLandscape: Bool, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { - var nodeHeight: CGFloat = (self.peerNodes.isEmpty ? 264.0 : 364.0) - + let showPeers = !self.peerNodes.isEmpty && !isLandscape + var nodeHeight: CGFloat = (!showPeers ? 236.0 : 320.0) let paddedSize = CGSize(width: size.width - 60.0, height: size.height) + self.peersScrollNode.isHidden = !showPeers + var aboutSize: CGSize? var descriptionSize: CGSize? if self.aboutNode.supernode != nil { @@ -210,14 +212,16 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer descriptionSize = measuredSize } + let constrainSize = CGSize(width: size.width - 32.0, height: size.height) + let titleSize = self.titleNode.measure(constrainSize) + nodeHeight += titleSize.height + let verticalOrigin = size.height - nodeHeight let avatarSize: CGFloat = 100.0 transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: floor((size.width - avatarSize) / 2.0), y: verticalOrigin + 32.0), size: CGSize(width: avatarSize, height: avatarSize))) - let constrainSize = CGSize(width: size.width - 32.0, height: size.height) - let titleSize = self.titleNode.measure(constrainSize) transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: verticalOrigin + 27.0 + avatarSize + 15.0), size: titleSize)) let countSize = self.countNode.measure(constrainSize) @@ -246,9 +250,9 @@ final class JoinLinkPreviewPeerContentNode: ASDisplayNode, ShareContentContainer } self.peersScrollNode.view.contentSize = CGSize(width: CGFloat(self.peerNodes.count) * peerSize.width + (self.moreNode != nil ? peerSize.width : 0.0) + peerInset * 2.0, height: peerSize.height) - transition.updateFrame(node: self.peersScrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOrigin + 210.0), size: CGSize(width: size.width, height: peerSize.height))) + transition.updateFrame(node: self.peersScrollNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOrigin + 27.0 + avatarSize + 15.0 + titleSize.height + 3.0 + countSize.height + 12.0), size: CGSize(width: size.width, height: peerSize.height))) - if !self.peerNodes.isEmpty { + if showPeers { verticalOffset += 100.0 } diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index 32ac140020..8508419ca6 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -95,6 +95,8 @@ swift_library( "//submodules/Components/ReactionImageComponent:ReactionImageComponent", "//submodules/Translate:Translate", "//submodules/QrCodeUI:QrCodeUI", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift index 22fd2541c1..0449e6daa1 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroController.swift @@ -13,6 +13,15 @@ enum PrivacyIntroControllerMode { case passcode case twoStepVerification + var animationName: String? { + switch self { + case .passcode: + return "Passcode" + case .twoStepVerification: + return nil + } + } + func icon(theme: PresentationTheme) -> UIImage? { switch self { case .passcode: diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift index f8264cfc43..f24fb5c3bd 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift @@ -8,6 +8,8 @@ import SwiftSignalKit import TelegramPresentationData import AccountContext import AuthorizationUI +import AnimatedStickerNode +import TelegramAnimatedStickerNode private func generateButtonImage(backgroundColor: UIColor, highlightColor: UIColor?) -> UIImage? { return generateImage(CGSize(width: 24.0, height: 44.0), contextGenerator: { size, context in @@ -39,6 +41,7 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { private let proceedAction: () -> Void private let iconNode: ASImageNode + private let animationNode: AnimatedStickerNode private let titleNode: ASTextNode private let textNode: ASTextNode private let buttonNode: HighlightTrackingButtonNode @@ -55,6 +58,8 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { self.proceedAction = proceedAction self.iconNode = ASImageNode() + self.animationNode = AnimatedStickerNode() + self.titleNode = ASTextNode() self.textNode = ASTextNode() self.buttonNode = HighlightTrackingButtonNode() @@ -68,7 +73,19 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { super.init() + if let animationName = mode.animationName { + self.iconNode.isHidden = true + self.animationNode.isHidden = false + + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: animationName), width: 380, height: 380, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) + self.animationNode.visibility = true + } else { + self.iconNode.isHidden = false + self.animationNode.isHidden = true + } + self.addSubnode(self.iconNode) + self.addSubnode(self.animationNode) self.addSubnode(self.titleNode) self.addSubnode(self.textNode) self.addSubnode(self.buttonBackgroundNode) @@ -98,7 +115,9 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { self.presentationData = presentationData self.backgroundColor = presentationData.theme.list.blocksBackgroundColor - self.iconNode.image = self.mode.icon(theme: presentationData.theme) + if self.animationNode.isHidden { + self.iconNode.image = self.mode.icon(theme: presentationData.theme) + } self.titleNode.attributedText = NSAttributedString(string: self.mode.title(strings: presentationData.strings), font: titleFont, textColor: presentationData.theme.list.sectionHeaderTextColor, paragraphAlignment: .center) self.textNode.attributedText = NSAttributedString(string: self.mode.text(strings: presentationData.strings), font: textFont, textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center) self.noticeNode.attributedText = NSAttributedString(string: self.mode.notice(strings: presentationData.strings), font: textFont, textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center) @@ -120,7 +139,18 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { insets.top += navigationBarHeight var iconSize = CGSize() - if let size = self.iconNode.image?.size { + var animationSize = CGSize() + if !self.animationNode.isHidden { + animationSize = CGSize(width: 180.0, height: 180.0) + self.animationNode.updateLayout(size: animationSize) + + var iconAlpha: CGFloat = 1.0 + if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height { + iconAlpha = 0.0 + iconSize = CGSize() + } + transition.updateAlpha(node: self.animationNode, alpha: iconAlpha) + } else if let size = self.iconNode.image?.size { iconSize = size var iconAlpha: CGFloat = 1.0 @@ -142,9 +172,10 @@ final class PrivacyIntroControllerNode: ViewControllerTracingNode { } else { buttonInset = 0.0 } - + let items: [AuthorizationLayoutItem] = [ AuthorizationLayoutItem(node: self.iconNode, size: iconSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), + AuthorizationLayoutItem(node: self.animationNode, size: animationSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 20.0, maxValue: 30.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.textNode, size: textSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 16.0, maxValue: 16.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), AuthorizationLayoutItem(node: self.buttonNode, size: CGSize(width: layout.size.width - buttonInset * 2.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 40.0, maxValue: 40.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0)), diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index d66d16280a..f7baacd111 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -200,17 +200,17 @@ private final class CallVideoNode: ASDisplayNode, PreviewVideoNode { case .rotation90: rotationAngle = CGFloat.pi / 2.0 case .rotation180: - if isCompactLayout { +// if isCompactLayout { rotationAngle = CGFloat.pi - } else { - rotationAngle = 0.0 - } +// } else { +// rotationAngle = 0.0 +// } case .rotation270: - if isCompactLayout { +// if isCompactLayout { rotationAngle = -CGFloat.pi / 2.0 - } else { - rotationAngle = CGFloat.pi / 2.0 - } +// } else { +// rotationAngle = CGFloat.pi / 2.0 +// } } var additionalAngle: CGFloat = 0.0 diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index bbd4720999..2ba32e44ea 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -165,7 +165,7 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil } case .action: if let message = message, let strings = strings, let nameDisplayOrder = nameDisplayOrder, let accountPeerId = accountPeerId { - return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat ?? PresentationDateTimeFormat(timeFormat: .military, dateFormat: .dayFirst, dateSeparator: ".", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), message: message, accountPeerId: accountPeerId, forChatList: false) ?? "") + return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat ?? PresentationDateTimeFormat(timeFormat: .military, dateFormat: .dayFirst, dateSeparator: ".", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: ""), message: message, accountPeerId: accountPeerId, forChatList: false)?.0 ?? "") } else { return nil } @@ -253,3 +253,20 @@ public func foldLineBreaks(_ text: String) -> String { } return result } + + +public func trimToLineCount(_ text: String, lineCount: Int) -> String { + if lineCount < 1 { + return "" + } + + let lines = text.split { $0.isNewline } + var result = "" + for line in lines.prefix(lineCount) { + if !result.isEmpty { + result += "\n" + } + result += line + } + return result +} diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 123341cba9..1ffeb48bbb 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -11,6 +11,10 @@ import Markdown private let titleFont = Font.regular(13.0) private let titleBoldFont = Font.bold(13.0) +private func spoilerAttributes(primaryTextColor: UIColor) -> MarkdownAttributeSet { + return MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [TelegramTextAttributes.Spoiler: true]) +} + private func peerMentionAttributes(primaryTextColor: UIColor, peerId: EnginePeer.Id) -> MarkdownAttributeSet { return MarkdownAttributeSet(font: titleBoldFont, textColor: primaryTextColor, additionalAttributes: [TelegramTextAttributes.PeerMention: TelegramPeerMention(peerId: peerId, mention: "")]) } @@ -25,8 +29,18 @@ private func peerMentionsAttributes(primaryTextColor: UIColor, peerIds: [(Int, E return result } -public func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id, forChatList: Bool) -> String? { - return universalServiceMessageString(presentationData: nil, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: forChatList)?.string +public func plainServiceMessageString(strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id, forChatList: Bool) -> (String, [NSRange])? { + if let attributedString = universalServiceMessageString(presentationData: nil, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: forChatList) { + var ranges: [NSRange] = [] + attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: [], using: { attributes, range, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler)] { + ranges.append(range) + } + }) + return (attributedString.string, ranges) + } else { + return nil + } } public func universalServiceMessageString(presentationData: (PresentationTheme, TelegramWallpaper)?, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id, forChatList: Bool) -> NSAttributedString? { @@ -137,7 +151,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } case .pinnedMessageUpdated: enum PinnnedMediaType { - case text(String) + case text(String, [MessageTextEntity]) case game case photo case video @@ -160,8 +174,15 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } var type: PinnnedMediaType - if let pinnedMessage = pinnedMessage { - type = .text(pinnedMessage.text) + if let pinnedMessage = pinnedMessage?._asMessage() { + let entities = (pinnedMessage.textEntitiesAttribute?.entities ?? []).filter { entity in + if case .Spoiler = entity.type { + return true + } else { + return false + } + } + type = .text(pinnedMessage.text, entities) inner: for media in pinnedMessage.media { if media is TelegramMediaGame { type = .game @@ -213,8 +234,13 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } switch type { - case let .text(text): - var clippedText = text.replacingOccurrences(of: "\n", with: " ") + case let .text(text, entities): + var clippedText = text + if !entities.isEmpty { + clippedText = trimToLineCount(clippedText, lineCount: 1) + } else { + clippedText = clippedText.replacingOccurrences(of: "\n", with: " ") + } if clippedText.count > 14 { clippedText = "\(clippedText[...clippedText.index(clippedText.startIndex, offsetBy: 14)])..." } @@ -224,7 +250,21 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } else { textWithRanges = strings.Notification_PinnedTextMessage(authorName, clippedText) } - attributedString = addAttributesToStringWithRanges(textWithRanges._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) + + let string = textWithRanges._tuple.0 + let stringLength = (clippedText as NSString).length + var ranges = textWithRanges._tuple.1 + let entityOffset = ranges.first(where: { $0.0 == 1 })?.1.location ?? 0 + var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]) + for entity in entities { + if entity.range.startIndex >= stringLength { + continue + } + let index = ranges.count + ranges.append((ranges.count, NSRange(location: entityOffset + entity.range.startIndex, length: entity.range.count))) + attributes[index] = spoilerAttributes(primaryTextColor: primaryTextColor) + } + attributedString = addAttributesToStringWithRanges((string, ranges), body: bodyAttributes, argumentAttributes: attributes) case .game: attributedString = addAttributesToStringWithRanges(strings.Message_AuthorPinnedGame(authorName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) case .photo: diff --git a/submodules/TelegramUI/Resources/Animations/TabContacts.json b/submodules/TelegramUI/Resources/Animations/TabContacts.json index 726465ec1c..20637b7373 100644 --- a/submodules/TelegramUI/Resources/Animations/TabContacts.json +++ b/submodules/TelegramUI/Resources/Animations/TabContacts.json @@ -1 +1 @@ -{"v":"4.8.0","meta":{"g":"LottieFiles AE ","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":60,"w":512,"h":512,"nm":"Contacts 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Main","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.7,0.7,0.7],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":20,"s":[97,97,100]},{"t":39,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":16,"s":[{"i":[[0.057,0],[0,-0.057],[-0.057,0],[0,0.057]],"o":[[-0.057,0],[0,0.057],[0.057,0],[0,-0.057]],"v":[[-13.08,19],[-13.183,19.103],[-13.08,19.205],[-12.978,19.103]],"c":true}]},{"t":39,"s":[{"i":[[21.242,0],[0,-21.242],[-21.242,0],[0,21.242]],"o":[[-21.242,0],[0,21.242],[21.242,0],[0,-21.242]],"v":[[0,-67.308],[-38.462,-28.846],[0,9.615],[38.462,-28.846]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":16,"s":[{"i":[[0.077,0],[0.043,-0.036],[0.005,-0.006],[-0.009,-0.01],[-0.002,-0.001],[-0.068,0],[-0.049,0.04],[-0.005,0.005],[0.007,0.01],[0.007,0.005]],"o":[[-0.077,0],[-0.004,0.003],[-0.009,0.009],[0,0],[0.049,0.04],[0.068,0],[0.003,-0.003],[0.009,-0.009],[0,0],[-0.043,-0.036]],"v":[[-13.08,19.282],[-13.26,19.337],[-13.273,19.35],[-13.274,19.384],[-13.26,19.397],[-13.08,19.462],[-12.901,19.397],[-12.888,19.386],[-12.885,19.352],[-12.901,19.337]],"c":true}]},{"t":39,"s":[{"i":[[28.941,0],[15.96,-13.616],[1.957,-2.123],[-3.375,-3.669],[-0.654,-0.54],[-25.552,0],[-18.285,15.109],[-1.809,1.726],[2.746,3.839],[2.536,2.04]],"o":[[-28.914,0],[-1.444,1.232],[-3.281,3.561],[0,0],[18.282,15.084],[25.573,0],[1.296,-1.071],[3.5,-3.341],[0,0],[-15.952,-13.638]],"v":[[0,38.462],[-67.306,58.884],[-72.407,63.917],[-72.687,76.606],[-67.269,81.625],[0,105.769],[67.311,81.585],[71.967,77.39],[73.156,64.848],[67.339,58.919]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":16,"s":[86.947,69.231],"to":[0,0],"ti":[0,0]},{"t":39,"s":[-0.053,19.231]}],"ix":2},"a":{"a":0,"k":[-0.053,19.231],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.7,"y":0},"t":0,"s":[{"i":[[21.242,0],[0,-21.242],[-21.242,0],[0,21.242]],"o":[[-21.242,0],[0,21.242],[21.242,0],[0,-21.242]],"v":[[0,-67.308],[-38.462,-28.846],[0,9.615],[38.462,-28.846]],"c":true}]},{"t":20,"s":[{"i":[[0.057,0],[0,-0.057],[-0.057,0],[0,0.057]],"o":[[-0.057,0],[0,0.057],[0.057,0],[0,-0.057]],"v":[[-13.08,19],[-13.183,19.103],[-13.08,19.205],[-12.978,19.103]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.7,"y":0},"t":0,"s":[{"i":[[28.941,0],[15.96,-13.616],[1.957,-2.123],[-3.375,-3.669],[-0.654,-0.54],[-25.552,0],[-18.285,15.109],[-1.809,1.726],[2.746,3.839],[2.536,2.04]],"o":[[-28.914,0],[-1.444,1.232],[-3.281,3.561],[0,0],[18.282,15.084],[25.573,0],[1.296,-1.071],[3.5,-3.341],[0,0],[-15.952,-13.638]],"v":[[0,38.462],[-67.306,58.884],[-72.407,63.917],[-72.687,76.606],[-67.269,81.625],[0,105.769],[67.311,81.585],[71.967,77.39],[73.156,64.848],[67.339,58.919]],"c":true}]},{"t":20,"s":[{"i":[[0.077,0],[0.043,-0.036],[0.005,-0.006],[-0.009,-0.01],[-0.002,-0.001],[-0.068,0],[-0.049,0.04],[-0.005,0.005],[0.007,0.01],[0.007,0.005]],"o":[[-0.077,0],[-0.004,0.003],[-0.009,0.009],[0,0],[0.049,0.04],[0.068,0],[0.003,-0.003],[0.009,-0.009],[0,0],[-0.043,-0.036]],"v":[[-13.08,19.282],[-13.26,19.337],[-13.273,19.35],[-13.274,19.384],[-13.26,19.397],[-13.08,19.462],[-12.901,19.397],[-12.888,19.386],[-12.885,19.352],[-12.901,19.337]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.7,"y":0},"t":0,"s":[-0.053,19.231],"to":[0,0],"ti":[0,0]},{"t":20,"s":[-60.053,69.231]}],"ix":2},"a":{"a":0,"k":[-0.053,19.231],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.7,"y":0},"t":0,"s":[{"i":[[-69.036,0],[0,-69.036],[69.036,0],[0,69.036]],"o":[[69.036,0],[0,69.036],[-69.036,0],[0,-69.036]],"v":[[0,-125],[125,0],[0,125],[-125,0]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":17,"s":[{"i":[[-60.751,0],[0,-60.751],[60.751,0],[0,60.751]],"o":[[60.751,0],[0,60.751],[-60.751,0],[0,-60.751]],"v":[[0,-110],[110,0],[0,110],[-110,0]],"c":true}]},{"t":36,"s":[{"i":[[-69.036,0],[0,-69.036],[69.036,0],[0,69.036]],"o":[[69.036,0],[0,69.036],[-69.036,0],[0,-69.036]],"v":[[0,-125],[125,0],[0,125],[-125,0]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[]} \ No newline at end of file +{"v":"4.8.0","meta":{"g":"LottieFiles AE ","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":35,"w":512,"h":512,"nm":"Contacts","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Main","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256,256,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.3,0.3,0.3],"y":[1,1,1]},"o":{"x":[0.15,0.15,0.15],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":16,"s":[97,97,100]},{"t":34,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":10,"s":[{"i":[[0.057,0],[0,-0.057],[-0.057,0],[0,0.057]],"o":[[-0.057,0],[0,0.057],[0.057,0],[0,-0.057]],"v":[[-13.08,19],[-13.183,19.103],[-13.08,19.205],[-12.978,19.103]],"c":true}]},{"t":33,"s":[{"i":[[21.242,0],[0,-21.242],[-21.242,0],[0,21.242]],"o":[[-21.242,0],[0,21.242],[21.242,0],[0,-21.242]],"v":[[0,-67.308],[-38.462,-28.846],[0,9.615],[38.462,-28.846]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":10,"s":[{"i":[[0.077,0],[0.043,-0.036],[0.005,-0.006],[-0.009,-0.01],[-0.002,-0.001],[-0.068,0],[-0.049,0.04],[-0.005,0.005],[0.007,0.01],[0.007,0.005]],"o":[[-0.077,0],[-0.004,0.003],[-0.009,0.009],[0,0],[0.049,0.04],[0.068,0],[0.003,-0.003],[0.009,-0.009],[0,0],[-0.043,-0.036]],"v":[[-13.08,19.282],[-13.26,19.337],[-13.273,19.35],[-13.274,19.384],[-13.26,19.397],[-13.08,19.462],[-12.901,19.397],[-12.888,19.386],[-12.885,19.352],[-12.901,19.337]],"c":true}]},{"t":33,"s":[{"i":[[28.941,0],[15.96,-13.616],[1.957,-2.123],[-3.375,-3.669],[-0.654,-0.54],[-25.552,0],[-18.285,15.109],[-1.809,1.726],[2.746,3.839],[2.536,2.04]],"o":[[-28.914,0],[-1.444,1.232],[-3.281,3.561],[0,0],[18.282,15.084],[25.573,0],[1.296,-1.071],[3.5,-3.341],[0,0],[-15.952,-13.638]],"v":[[0,38.462],[-67.306,58.884],[-72.407,63.917],[-72.687,76.606],[-67.269,81.625],[0,105.769],[67.311,81.585],[71.967,77.39],[73.156,64.848],[67.339,58.919]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.2,"y":1},"o":{"x":0.2,"y":0},"t":10,"s":[86.947,69.231],"to":[0,0],"ti":[0,0]},{"t":33,"s":[-0.053,19.231]}],"ix":2},"a":{"a":0,"k":[-0.053,19.231],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.15,"y":0},"t":0,"s":[{"i":[[21.242,0],[0,-21.242],[-21.242,0],[0,21.242]],"o":[[-21.242,0],[0,21.242],[21.242,0],[0,-21.242]],"v":[[0,-67.308],[-38.462,-28.846],[0,9.615],[38.462,-28.846]],"c":true}]},{"t":16,"s":[{"i":[[0.057,0],[0,-0.057],[-0.057,0],[0,0.057]],"o":[[-0.057,0],[0,0.057],[0.057,0],[0,-0.057]],"v":[[-13.08,19],[-13.183,19.103],[-13.08,19.205],[-12.978,19.103]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.15,"y":0},"t":0,"s":[{"i":[[28.941,0],[15.96,-13.616],[1.957,-2.123],[-3.375,-3.669],[-0.654,-0.54],[-25.552,0],[-18.285,15.109],[-1.809,1.726],[2.746,3.839],[2.536,2.04]],"o":[[-28.914,0],[-1.444,1.232],[-3.281,3.561],[0,0],[18.282,15.084],[25.573,0],[1.296,-1.071],[3.5,-3.341],[0,0],[-15.952,-13.638]],"v":[[0,38.462],[-67.306,58.884],[-72.407,63.917],[-72.687,76.606],[-67.269,81.625],[0,105.769],[67.311,81.585],[71.967,77.39],[73.156,64.848],[67.339,58.919]],"c":true}]},{"t":16,"s":[{"i":[[0.077,0],[0.043,-0.036],[0.005,-0.006],[-0.009,-0.01],[-0.002,-0.001],[-0.068,0],[-0.049,0.04],[-0.005,0.005],[0.007,0.01],[0.007,0.005]],"o":[[-0.077,0],[-0.004,0.003],[-0.009,0.009],[0,0],[0.049,0.04],[0.068,0],[0.003,-0.003],[0.009,-0.009],[0,0],[-0.043,-0.036]],"v":[[-13.08,19.282],[-13.26,19.337],[-13.273,19.35],[-13.274,19.384],[-13.26,19.397],[-13.08,19.462],[-12.901,19.397],[-12.888,19.386],[-12.885,19.352],[-12.901,19.337]],"c":true}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.15,"y":0},"t":0,"s":[-0.053,19.231],"to":[0,0],"ti":[0,0]},{"t":16,"s":[-60.053,69.231]}],"ix":2},"a":{"a":0,"k":[-0.053,19.231],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.15,"y":0},"t":0,"s":[{"i":[[-69.036,0],[0,-69.036],[69.036,0],[0,69.036]],"o":[[69.036,0],[0,69.036],[-69.036,0],[0,-69.036]],"v":[[0,-125],[125,0],[0,125],[-125,0]],"c":true}]},{"i":{"x":0.2,"y":1},"o":{"x":0.167,"y":0},"t":10,"s":[{"i":[[-60.751,0],[0,-60.751],[60.751,0],[0,60.751]],"o":[[60.751,0],[0,60.751],[-60.751,0],[0,-60.751]],"v":[[0,-110],[110,0],[0,110],[-110,0]],"c":true}]},{"t":33,"s":[{"i":[[-69.036,0],[0,-69.036],[69.036,0],[0,69.036]],"o":[[69.036,0],[0,69.036],[-69.036,0],[0,-69.036]],"v":[[0,-125],[125,0],[0,125],[-125,0]],"c":true}]}],"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 7bc27bfa27..9c16ec9707 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3961,7 +3961,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } var renderedPeer: RenderedPeer? var contactStatus: ChatContactStatus? + var copyProtectionEnabled: Bool = false if let peer = peerView.peers[peerView.peerId] { + copyProtectionEnabled = peer.isCopyProtectionEnabled if let cachedData = peerView.cachedData as? CachedUserData { contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) } else if let cachedData = peerView.cachedData as? CachedGroupData { @@ -4087,6 +4089,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return $0.updatedPeer { _ in return renderedPeer }.updatedIsNotAccessible(isNotAccessible).updatedContactStatus(contactStatus).updatedHasBots(hasBots).updatedIsArchived(isArchived).updatedPeerIsMuted(peerIsMuted).updatedPeerDiscussionId(peerDiscussionId).updatedPeerGeoLocation(peerGeoLocation).updatedExplicitelyCanPinMessages(explicitelyCanPinMessages).updatedHasScheduledMessages(false).updatedCurrentSendAsPeerId(currentSendAsPeerId) + .updatedCopyProtectionEnabled(copyProtectionEnabled) }) if !strongSelf.didSetChatLocationInfoReady { strongSelf.didSetChatLocationInfoReady = true @@ -8715,7 +8718,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G interfaceState = interfaceState.withUpdatedHistoryScrollState(scrollState) } interfaceState = interfaceState.withUpdatedInputLanguage(self.chatDisplayNode.currentTextInputLanguage) - if interfaceState.composeInputState.inputText.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + if interfaceState.composeInputState.inputText.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && interfaceState.replyMessageId == nil { interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))) } let _ = ChatInterfaceState.update(engine: self.context.engine, peerId: peerId, threadId: threadId, { _ in diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index b297cf1a65..f1ccc7cf93 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -277,6 +277,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if attribute is ViewCountMessageAttribute{ return false } + if attribute is ForwardCountMessageAttribute { + return false + } + if attribute is ReactionsMessageAttribute { + return false + } return true }) diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift index 2bcb2e1bf1..77c4d2b6a4 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift @@ -17,6 +17,7 @@ import UniversalMediaPlayer import TelegramUniversalVideoContent import GalleryUI import WallpaperBackgroundNode +import InvisibleInkDustNode private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId) -> NSAttributedString? { return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false) @@ -24,6 +25,7 @@ private func attributedServiceMessageString(theme: ChatPresentationThemeData, st class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let labelNode: TextNode + private var dustNode: InvisibleInkDustNode? var backgroundNode: WallpaperBubbleBackgroundNode? var backgroundColorNode: ASDisplayNode let backgroundMaskNode: ASImageNode @@ -277,6 +279,25 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.labelNode.frame = labelFrame strongSelf.backgroundColorNode.backgroundColor = selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper) + if !labelLayout.spoilers.isEmpty { + let dustColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText + + let dustNode: InvisibleInkDustNode + if let current = strongSelf.dustNode { + dustNode = current + } else { + dustNode = InvisibleInkDustNode(textNode: nil) + dustNode.isUserInteractionEnabled = false + strongSelf.dustNode = dustNode + strongSelf.insertSubnode(dustNode, aboveSubnode: strongSelf.labelNode) + } + dustNode.frame = labelFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 1.0) + dustNode.update(size: dustNode.frame.size, color: dustColor, textColor: dustColor, rects: labelLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: labelLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }) + } else if let dustNode = strongSelf.dustNode { + dustNode.removeFromSupernode() + strongSelf.dustNode = nil + } + let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) if let (offset, image) = backgroundMaskImage { diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index f08b5e9318..803a16b6df 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -206,7 +206,7 @@ final class ChatMessageAccessibilityData { if let chatPeer = message.peers[item.message.id.peerId] { let authorName = message.author.flatMap(EnginePeer.init)?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) - let (_, _, messageText) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: [EngineMessage(message)], chatPeer: EngineRenderedPeer(peer: EnginePeer(chatPeer)), accountPeerId: item.context.account.peerId) + let (_, _, messageText, _) = chatListItemStrings(strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, messages: [EngineMessage(message)], chatPeer: EngineRenderedPeer(peer: EnginePeer(chatPeer)), accountPeerId: item.context.account.peerId) var text = messageText diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index eaf4fc3810..232b728e92 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -876,6 +876,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { self.votersNode.contentMode = .topLeft self.votersNode.contentsScale = UIScreenScale self.votersNode.displaysAsynchronously = false + self.votersNode.clipsToBounds = true var displaySolution: (() -> Void)? self.solutionButtonNode = SolutionButtonNode(pressed: { diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index 00586da75a..198b13235d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -106,7 +106,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } } if entities.count > 0 { - messageText = stringWithAppliedEntities(message.text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) + messageText = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) } else { messageText = NSAttributedString(string: textString, font: textFont, textColor: textColor) } diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 168ff392e3..b19d3c46d7 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -468,7 +468,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } let textColor = theme.chat.inputPanel.primaryTextColor if entities.count > 0 { - messageText = stringWithAppliedEntities(message.text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) + messageText = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) } else { messageText = NSAttributedString(string: foldLineBreaks(textString), font: textFont, textColor: textColor) } diff --git a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift index 0af6768655..e150c414d9 100644 --- a/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatQrCodeScreen.swift @@ -1597,9 +1597,9 @@ private class QrContentNode: ASDisplayNode, ContentNode { self.codeTextNode.attributedText = NSAttributedString(string: self.codeTextNode.attributedText?.string ?? "", font: Font.with(size: fontSize, design: .round, weight: .bold, traits: []), textColor: .black) - let codeBackgroundWidth = size.width - codeInset * 2.0 + let codeBackgroundWidth = min(300.0, size.width - codeInset * 2.0) let codeBackgroundHeight = floor(codeBackgroundWidth * 1.1) - let codeBackgroundFrame = CGRect(x: codeInset, y: topInset + floor((size.height - bottomInset - codeBackgroundHeight) / 2.0), width: codeBackgroundWidth, height: codeBackgroundHeight) + let codeBackgroundFrame = CGRect(x: floor((size.width - codeBackgroundWidth) / 2.0), y: topInset + floor((size.height - bottomInset - codeBackgroundHeight) / 2.0), width: codeBackgroundWidth, height: codeBackgroundHeight) transition.updateFrame(node: self.codeBackgroundNode, frame: codeBackgroundFrame) transition.updateFrame(node: self.codeForegroundNode, frame: codeBackgroundFrame) transition.updateFrame(node: self.codeMaskNode, frame: CGRect(origin: CGPoint(), size: codeBackgroundFrame.size)) diff --git a/submodules/TelegramUI/Sources/Pasteboard.swift b/submodules/TelegramUI/Sources/Pasteboard.swift index 8189c08a48..ee9c88ccc9 100644 --- a/submodules/TelegramUI/Sources/Pasteboard.swift +++ b/submodules/TelegramUI/Sources/Pasteboard.swift @@ -49,6 +49,9 @@ private func chatInputStateString(attributedString: NSAttributedString) -> NSAtt string.addAttribute(ChatTextInputAttributes.monospace, value: true as NSNumber, range: range) } } + if let value = attributes[.backgroundColor] as? UIColor, value.rgb == UIColor.gray.rgb { + string.addAttribute(ChatTextInputAttributes.spoiler, value: true as NSNumber, range: range) + } if let _ = attributes[.strikethroughStyle] { string.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: range) } diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index a832c52e02..9292ecc789 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -135,7 +135,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { } let textColor = strongSelf.theme.chat.inputPanel.primaryTextColor if entities.count > 0 { - messageText = stringWithAppliedEntities(message.text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) + messageText = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false) } else { messageText = NSAttributedString(string: text, font: textFont, textColor: isMedia ? strongSelf.theme.chat.inputPanel.secondaryTextColor : strongSelf.theme.chat.inputPanel.primaryTextColor) } diff --git a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift index 939be1acd5..b1a9870d83 100644 --- a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift +++ b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift @@ -69,7 +69,9 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti if nsString == nil { nsString = text as NSString } - if range.location + range.length > stringLength { + if range.location > stringLength { + continue + } else if range.location + range.length > stringLength { range.location = max(0, stringLength - range.length) range.length = stringLength - range.location } @@ -226,7 +228,11 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti } string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.BankCard), value: nsString!.substring(with: range), range: range) case .Spoiler: - string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true as NSNumber, range: range) + if external { + string.addAttribute(NSAttributedString.Key.backgroundColor, value: UIColor.gray, range: range) + } else { + string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true as NSNumber, range: range) + } case let .Custom(type): if type == ApplicationSpecificEntityType.Timecode { string.addAttribute(NSAttributedString.Key.foregroundColor, value: linkColor, range: range) diff --git a/submodules/Translate/Sources/Translate.swift b/submodules/Translate/Sources/Translate.swift index 83a88a5fcc..c61c501e06 100644 --- a/submodules/Translate/Sources/Translate.swift +++ b/submodules/Translate/Sources/Translate.swift @@ -11,11 +11,12 @@ private final class LinkHelperClass: NSObject { public var supportedTranslationLanguages = [ "en", "ar", - "zh", + "zh-Hans", + "zh-Hant", "fr", "de", "it", - "jp", + "ja", "ko", "pt", "ru",