diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index e58d2d0c61..cdc2d67764 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10773,3 +10773,26 @@ Sorry for the inconvenience."; "ChatList.PremiumXmasGiftText" = "Gift Telegram Premium for Christmas."; "ReassignBoost.DescriptionWithLink" = "To boost **%1$@**, reassign a previous boost or [gift Telegram Premium]() to a friend to get **%2$@** additional boosts."; + +"Channel.AdminLog.ChannelChangedNameColorAndIcon" = "%1$@ changed the channel name color and icon to %2$@ %3$@"; +"Channel.AdminLog.ChannelChangedNameColor" = "%1$@ changed the channel name color to %2$@"; +"Channel.AdminLog.ChannelChangedNameIcon" = "%1$@ changed the channel name icon to %2$@"; + +"Channel.AdminLog.ChannelChangedProfileColorAndIcon" = "%1$@ changed the channel profile color and icon to %2$@ %3$@"; +"Channel.AdminLog.ChannelChangedProfileColor" = "%1$@ changed the channel profile color to %2$@"; +"Channel.AdminLog.ChannelChangedProfileIcon" = "%1$@ changed the channel profile icon to %2$@"; +"Channel.AdminLog.ChannelRemovedProfileColorAndIcon" = "%1$@ changed the removed profile color and icon"; +"Channel.AdminLog.ChannelRemovedProfileColor" = "%1$@ removed profile color"; +"Channel.AdminLog.ChannelRemovedProfileIcon" = "%1$@ removed profile icon"; + +"Channel.AdminLog.ChannelUpdatedStatus" = "%1$@ changed the channel status to %2$@"; +"Channel.AdminLog.ChannelRemovedStatus" = "%1$@ removed the channel status"; + +"Channel.AdminLog.ChannelRemovedWallpaper" = "%1$@ removed wallpaper"; + +"Channel.AdminLog.ChannelChangedWallpaper" = "%1$@ set a new wallpaper"; + +"Channel.Appearance.UnsavedChangesAlertTitle" = "Unsaved Changes"; +"Channel.Appearance.UnsavedChangesAlertText" = "You have changed the channel appearance settings. Apply changes?"; +"Channel.Appearance.UnsavedChangesAlertDiscard" = "Discard"; +"Channel.Appearance.UnsavedChangesAlertApply" = "Apply"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 824f23cb8b..372ea55a43 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1335,19 +1335,19 @@ public class PeerNameColors: Equatable { public let secondary: UIColor? public let tertiary: UIColor? - init(main: UIColor, secondary: UIColor?, tertiary: UIColor?) { + public init(main: UIColor, secondary: UIColor?, tertiary: UIColor?) { self.main = main self.secondary = secondary self.tertiary = tertiary } - init(main: UIColor) { + public init(main: UIColor) { self.main = main self.secondary = nil self.tertiary = nil } - init?(colors: [UIColor]) { + public init?(colors: [UIColor]) { guard let first = colors.first else { return nil } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 7cac497f41..895adda249 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -969,6 +969,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var avatarTimerBadge: AvatarBadgeView? let pinnedIconNode: ASImageNode var secretIconNode: ASImageNode? + var verifiedIconView: ComponentHostView? + var verifiedIconComponent: EmojiStatusComponent? var credibilityIconView: ComponentHostView? var credibilityIconComponent: EmojiStatusComponent? let mutedIconNode: ASImageNode @@ -1152,6 +1154,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.textNode.visibilityRect = self.visibilityStatus ? CGRect.infinite : nil + if let verifiedIconView = self.verifiedIconView, let verifiedIconComponent = self.verifiedIconComponent { + let _ = verifiedIconView.update( + transition: .immediate, + component: AnyComponent(verifiedIconComponent.withVisibleForAnimations(self.visibilityStatus)), + environment: {}, + containerSize: verifiedIconView.bounds.size + ) + } if let credibilityIconView = self.credibilityIconView, let credibilityIconComponent = self.credibilityIconComponent { let _ = credibilityIconView.update( transition: .immediate, @@ -1761,6 +1771,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var currentPinnedIconImage: UIImage? var currentMutedIconImage: UIImage? var currentCredibilityIconContent: EmojiStatusComponent.Content? + var currentVerifiedIconContent: EmojiStatusComponent.Content? var currentSecretIconImage: UIImage? var currentForwardedIcon: UIImage? var currentStoryIcon: UIImage? @@ -2478,6 +2489,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else if peer.isFake { currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) } else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled { + if case .channel = peer, peer.isVerified { + currentVerifiedIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) + } + currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) } else if peer.isVerified { currentCredibilityIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) @@ -2494,6 +2509,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else if peer.isFake { currentCredibilityIconContent = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) } else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled { + if case .channel = peer, peer.isVerified { + currentVerifiedIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) + } + currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) } else if peer.isVerified { currentCredibilityIconContent = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) @@ -2505,7 +2524,24 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let currentSecretIconImage = currentSecretIconImage { titleIconsWidth += currentSecretIconImage.size.width + 2.0 } - if let currentCredibilityIconContent = currentCredibilityIconContent { + + if let currentVerifiedIconContent { + if titleIconsWidth.isZero { + titleIconsWidth += 4.0 + } else { + titleIconsWidth += 2.0 + } + switch currentVerifiedIconContent { + case let .text(_, string): + let textString = NSAttributedString(string: string, font: Font.bold(10.0), textColor: .black, paragraphAlignment: .center) + let stringRect = textString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil) + titleIconsWidth += floor(stringRect.width) + 11.0 + default: + titleIconsWidth += 8.0 + } + } + + if let currentCredibilityIconContent { if titleIconsWidth.isZero { titleIconsWidth += 4.0 } else { @@ -3556,7 +3592,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { lastLineRect = CGRect(origin: CGPoint(), size: titleLayout.size) } - if let currentCredibilityIconContent = currentCredibilityIconContent { + if let currentCredibilityIconContent { let credibilityIconView: ComponentHostView if let current = strongSelf.credibilityIconView { credibilityIconView = current @@ -3589,6 +3625,39 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { credibilityIconView.removeFromSuperview() } + if let currentVerifiedIconContent { + let verifiedIconView: ComponentHostView + if let current = strongSelf.verifiedIconView { + verifiedIconView = current + } else { + verifiedIconView = ComponentHostView() + strongSelf.verifiedIconView = verifiedIconView + strongSelf.mainContentContainerNode.view.addSubview(verifiedIconView) + } + + let verifiedIconComponent = EmojiStatusComponent( + context: item.context, + animationCache: item.interaction.animationCache, + animationRenderer: item.interaction.animationRenderer, + content: currentVerifiedIconContent, + isVisibleForAnimations: strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji, + action: nil + ) + strongSelf.verifiedIconComponent = verifiedIconComponent + + let iconSize = verifiedIconView.update( + transition: .immediate, + component: AnyComponent(verifiedIconComponent), + environment: {}, + containerSize: CGSize(width: 20.0, height: 20.0) + ) + transition.updateFrame(view: verifiedIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - iconSize.height / 2.0) - UIScreenPixel), size: iconSize)) + nextTitleIconOrigin += verifiedIconView.bounds.width + 4.0 + } else if let verifiedIconView = strongSelf.verifiedIconView { + strongSelf.verifiedIconView = nil + verifiedIconView.removeFromSuperview() + } + if let currentMutedIconImage = currentMutedIconImage { strongSelf.mutedIconNode.image = currentMutedIconImage strongSelf.mutedIconNode.isHidden = false diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index 4361932dd8..8045723169 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -404,6 +404,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { private let titleNode: TextNode private var credibilityIconView: ComponentHostView? private var credibilityIconComponent: EmojiStatusComponent? + private var verifiedIconView: ComponentHostView? + private var verifiedIconComponent: EmojiStatusComponent? private let statusNode: TextNode private var statusIconNode: ASImageNode? private var badgeBackgroundNode: ASImageNode? @@ -464,6 +466,14 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { containerSize: credibilityIconView.bounds.size ) } + if let verifiedIconView = self.verifiedIconView, let verifiedIconComponent = self.verifiedIconComponent { + let _ = verifiedIconView.update( + transition: .immediate, + component: AnyComponent(verifiedIconComponent.withVisibleForAnimations(self.visibilityStatus)), + environment: {}, + containerSize: verifiedIconView.bounds.size + ) + } if let avatarIconView = self.avatarIconView, let avatarIconComponent = self.avatarIconComponent { let _ = avatarIconView.update( transition: .immediate, @@ -692,6 +702,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: item.context.currentAppConfiguration.with { $0 }) var credibilityIcon: EmojiStatusComponent.Content? + var verifiedIcon: EmojiStatusComponent.Content? switch item.peer { case let .peer(peer, _): if let peer = peer, peer.id != item.context.account.peerId { @@ -700,6 +711,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } else if peer.isFake { credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_FakeAccount.uppercased()) } else if let emojiStatus = peer.emojiStatus { + if case .channel = peer, peer.isVerified { + verifiedIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) + } credibilityIcon = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 20.0, height: 20.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2)) } else if peer.isVerified { credibilityIcon = .verified(fillColor: item.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .compact) @@ -879,7 +893,18 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } var additionalTitleInset: CGFloat = 0.0 - if let credibilityIcon = credibilityIcon { + if let verifiedIcon { + additionalTitleInset += 3.0 + switch verifiedIcon { + case let .text(_, string): + let textString = NSAttributedString(string: string, font: Font.bold(10.0), textColor: .black, paragraphAlignment: .center) + let stringRect = textString.boundingRect(with: CGSize(width: 100.0, height: 16.0), options: .usesLineFragmentOrigin, context: nil) + additionalTitleInset += floor(stringRect.width) + 11.0 + default: + additionalTitleInset += 16.0 + } + } + if let credibilityIcon { additionalTitleInset += 3.0 switch credibilityIcon { case let .text(_, string): @@ -1193,7 +1218,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { } } - if let credibilityIcon = credibilityIcon { + var nextIconX: CGFloat = titleFrame.maxX + if let credibilityIcon { let animationCache = item.context.animationCache let animationRenderer = item.context.animationRenderer @@ -1224,12 +1250,53 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { containerSize: CGSize(width: 20.0, height: 20.0) ) - transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) + nextIconX += 4.0 + transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextIconX, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) + nextIconX += iconSize.width } else if let credibilityIconView = strongSelf.credibilityIconView { strongSelf.credibilityIconView = nil credibilityIconView.removeFromSuperview() } + if let verifiedIcon { + let animationCache = item.context.animationCache + let animationRenderer = item.context.animationRenderer + + let verifiedIconView: ComponentHostView + if let current = strongSelf.verifiedIconView { + verifiedIconView = current + } else { + verifiedIconView = ComponentHostView() + strongSelf.offsetContainerNode.view.addSubview(verifiedIconView) + strongSelf.verifiedIconView = verifiedIconView + } + + let verifiedIconComponent = EmojiStatusComponent( + context: item.context, + animationCache: animationCache, + animationRenderer: animationRenderer, + content: verifiedIcon, + isVisibleForAnimations: strongSelf.visibilityStatus, + action: nil, + emojiFileUpdated: nil + ) + strongSelf.verifiedIconComponent = verifiedIconComponent + + let iconSize = verifiedIconView.update( + transition: .immediate, + component: AnyComponent(verifiedIconComponent), + environment: {}, + containerSize: CGSize(width: 20.0, height: 20.0) + ) + + nextIconX += 4.0 + transition.updateFrame(view: verifiedIconView, frame: CGRect(origin: CGPoint(x: nextIconX, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) + nextIconX += iconSize.width + } else if let verifiedIconView = strongSelf.verifiedIconView { + strongSelf.verifiedIconView = nil + verifiedIconView.removeFromSuperview() + } + if let actionButtons = actionButtons { if strongSelf.actionButtonNodes == nil { var actionButtonNodes: [HighlightableButtonNode] = [] @@ -1430,11 +1497,19 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { self.statusNode.frame = statusFrame transition.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0)) + var nextIconX = titleFrame.maxX if let credibilityIconView = self.credibilityIconView { var iconFrame = credibilityIconView.frame - iconFrame.origin.x = titleFrame.maxX + 4.0 + iconFrame.origin.x = nextIconX + 4.0 + nextIconX += 4.0 + iconFrame.width transition.updateFrame(view: credibilityIconView, frame: iconFrame) } + if let verifiedIconView = self.verifiedIconView { + var iconFrame = verifiedIconView.frame + iconFrame.origin.x = nextIconX + 4.0 + nextIconX += 4.0 + iconFrame.width + transition.updateFrame(view: verifiedIconView, frame: iconFrame) + } if let badgeBackgroundNode = self.badgeBackgroundNode, let badgeTextNode = self.badgeTextNode { var badgeBackgroundFrame = badgeBackgroundNode.frame diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index 390142c661..9ab15c0373 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -858,10 +858,10 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview - let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: []))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true) - let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: []))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: [], additionalAttributes: nil))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: true) let headerItems: [ListViewItem] = [previewItem, dragItem] diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index b2e937db9e..58700fd3b3 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -1534,9 +1534,25 @@ final class HistoryViewLoadedState { author = updatedAuthor rebuild = true } + var associatedMessages = message.associatedMessages + for (id, associatedMessage) in message.associatedMessages { + var peers = associatedMessage.peers + var author = associatedMessage.author + for (peerId, _) in associatedMessage.peers { + if let updatedPeer = updatedPeers[peerId] { + peers[peerId] = updatedPeer + rebuild = true + } + } + if let authorValue = author, let updatedAuthor = updatedPeers[authorValue.id] { + author = updatedAuthor + rebuild = true + } + associatedMessages[id] = associatedMessage.withUpdatedPeers(peers).withUpdatedAuthor(author) + } if rebuild { - let updatedMessage = message.withUpdatedPeers(peers).withUpdatedAuthor(author) + let updatedMessage = message.withUpdatedPeers(peers).withUpdatedAuthor(author).withUpdatedAssociatedMessages(associatedMessages) return .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) } case .IntermediateMessageEntry: diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 610d03ff36..d4a392eaee 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -49,7 +49,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe case .messageActionScreenshotTaken: return TelegramMediaAction(action: .historyScreenshot) case let .messageActionCustomAction(message): - return TelegramMediaAction(action: .customText(text: message, entities: [])) + return TelegramMediaAction(action: .customText(text: message, entities: [], additionalAttributes: nil)) case let .messageActionBotAllowed(flags, domain, app): if let domain = domain { return TelegramMediaAction(action: .botDomainAccessGranted(domain: domain)) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index d482f29bb0..fa1aec05d7 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -1,4 +1,5 @@ import Postbox +import Foundation public enum PhoneCallDiscardReason: Int32 { case missed = 0 @@ -73,6 +74,18 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } } + public struct CustomTextAttributes: Equatable { + public var attributes: [(NSRange, NSAttributedString.Key, Any)] + + public init(attributes: [(NSRange, NSAttributedString.Key, Any)]) { + self.attributes = attributes + } + + public static func ==(lhs: CustomTextAttributes, rhs: CustomTextAttributes) -> Bool { + return true + } + } + case unknown case groupCreated(title: String) case addedMembers(peerIds: [PeerId]) @@ -89,7 +102,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case gameScore(gameId: Int64, score: Int32) case phoneCall(callId: Int64, discardReason: PhoneCallDiscardReason?, duration: Int32?, isVideo: Bool) case paymentSent(currency: String, totalAmount: Int64, invoiceSlug: String?, isRecurringInit: Bool, isRecurringUsed: Bool) - case customText(text: String, entities: [MessageTextEntity]) + case customText(text: String, entities: [MessageTextEntity], additionalAttributes: CustomTextAttributes?) case botDomainAccessGranted(domain: String) case botAppAccessGranted(appName: String?, type: BotSendMessageAccessGrantedType?) case botSentSecureValues(types: [SentSecureValueType]) @@ -152,7 +165,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case 15: self = .paymentSent(currency: decoder.decodeStringForKey("currency", orElse: ""), totalAmount: decoder.decodeInt64ForKey("ta", orElse: 0), invoiceSlug: decoder.decodeOptionalStringForKey("invoiceSlug"), isRecurringInit: decoder.decodeBoolForKey("isRecurringInit", orElse: false), isRecurringUsed: decoder.decodeBoolForKey("isRecurringUsed", orElse: false)) case 16: - self = .customText(text: decoder.decodeStringForKey("text", orElse: ""), entities: decoder.decodeObjectArrayWithDecoderForKey("ent")) + self = .customText(text: decoder.decodeStringForKey("text", orElse: ""), entities: decoder.decodeObjectArrayWithDecoderForKey("ent"), additionalAttributes: nil) case 17: self = .botDomainAccessGranted(domain: decoder.decodeStringForKey("do", orElse: "")) case 18: @@ -298,7 +311,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeNil(forKey: "d") } encoder.encodeInt32(isVideo ? 1 : 0, forKey: "vc") - case let .customText(text, entities): + case let .customText(text, entities, _): encoder.encodeInt32(16, forKey: "_rawValue") encoder.encodeString(text, forKey: "text") encoder.encodeObjectArray(entities, forKey: "ent") diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index fb98934964..718d9720e4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -87,7 +87,10 @@ public enum AdminLogEventAction { case pinTopic(prevInfo: EngineMessageHistoryThread.Info?, newInfo: EngineMessageHistoryThread.Info?) case toggleForum(isForum: Bool) case toggleAntiSpam(isEnabled: Bool) - case changeNameColor(prev: PeerNameColor, new: PeerNameColor) + case changeNameColor(prevColor: PeerNameColor, prevIcon: Int64?, newColor: PeerNameColor, newIcon: Int64?) + case changeProfileColor(prevColor: PeerNameColor?, prevIcon: Int64?, newColor: PeerNameColor?, newIcon: Int64?) + case changeWallpaper(prev: TelegramWallpaper?, new: TelegramWallpaper?) + case changeStatus(prev: PeerEmojiStatus?, new: PeerEmojiStatus?) } public enum ChannelAdminLogEventError { @@ -348,22 +351,69 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net case let .channelAdminLogEventActionToggleAntiSpam(newValue): action = .toggleAntiSpam(isEnabled: newValue == .boolTrue) case let .channelAdminLogEventActionChangePeerColor(prevValue, newValue): - let _ = prevValue - let _ = newValue - action = nil -// action = .changeNameColor(prev: PeerNameColor(rawValue: prevValue), new: PeerNameColor(rawValue: newValue)) + var prevColorIndex: Int32 + var prevEmojiId: Int64? + switch prevValue { + case let .peerColor(_, color, backgroundEmojiIdValue): + prevColorIndex = color ?? 0 + prevEmojiId = backgroundEmojiIdValue + } + + var newColorIndex: Int32 + var newEmojiId: Int64? + switch newValue { + case let .peerColor(_, color, backgroundEmojiIdValue): + newColorIndex = color ?? 0 + newEmojiId = backgroundEmojiIdValue + } + + action = .changeNameColor(prevColor: PeerNameColor(rawValue: prevColorIndex), prevIcon: prevEmojiId, newColor: PeerNameColor(rawValue: newColorIndex), newIcon: newEmojiId) case let .channelAdminLogEventActionChangeProfilePeerColor(prevValue, newValue): - let _ = prevValue - let _ = newValue - action = nil + var prevColorIndex: Int32? + var prevEmojiId: Int64? + switch prevValue { + case let .peerColor(_, color, backgroundEmojiIdValue): + prevColorIndex = color + prevEmojiId = backgroundEmojiIdValue + } + + var newColorIndex: Int32? + var newEmojiId: Int64? + switch newValue { + case let .peerColor(_, color, backgroundEmojiIdValue): + newColorIndex = color + newEmojiId = backgroundEmojiIdValue + } + + action = .changeProfileColor(prevColor: prevColorIndex.flatMap(PeerNameColor.init(rawValue:)), prevIcon: prevEmojiId, newColor: newColorIndex.flatMap(PeerNameColor.init(rawValue:)), newIcon: newEmojiId) case let .channelAdminLogEventActionChangeWallpaper(prevValue, newValue): - let _ = prevValue - let _ = newValue - action = nil + let prev: TelegramWallpaper? + if case let .wallPaperNoFile(_, _, settings) = prevValue { + if settings == nil { + prev = nil + } else if case let .wallPaperSettings(flags, _, _, _, _, _, _, _) = settings, flags == 0 { + prev = nil + } else { + prev = TelegramWallpaper(apiWallpaper: prevValue) + } + } else { + prev = TelegramWallpaper(apiWallpaper: prevValue) + } + let new: TelegramWallpaper? + if case let .wallPaperNoFile(_, _, settings) = newValue { + if settings == nil { + new = nil + } else if case let .wallPaperSettings(flags, _, _, _, _, _, _, _) = settings, flags == 0 { + new = nil + } else { + new = TelegramWallpaper(apiWallpaper: newValue) + } + } else { + new = TelegramWallpaper(apiWallpaper: newValue) + } + action = .changeWallpaper(prev: prev, new: new) case let .channelAdminLogEventActionChangeEmojiStatus(prevValue, newValue): - let _ = prevValue - let _ = newValue - action = nil + action = .changeStatus(prev: PeerEmojiStatus(apiStatus: prevValue), new: PeerEmojiStatus(apiStatus: newValue)) } let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) if let action = action { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index c9ada3511e..573637db61 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -718,6 +718,14 @@ public extension TelegramEngine { return _internal_updatePeerNameColorAndEmoji(account: self.account, peerId: peerId, nameColor: nameColor, backgroundEmojiId: backgroundEmojiId, profileColor: profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId) } + public func updatePeerNameColor(peerId: EnginePeer.Id, nameColor: PeerNameColor, backgroundEmojiId: Int64?) -> Signal { + return _internal_updatePeerNameColor(account: self.account, peerId: peerId, nameColor: nameColor, backgroundEmojiId: backgroundEmojiId) + } + + public func updatePeerProfileColor(peerId: EnginePeer.Id, profileColor: PeerNameColor?, profileBackgroundEmojiId: Int64?) -> Signal { + return _internal_updatePeerProfileColor(account: self.account, peerId: peerId, profileColor: profileColor, profileBackgroundEmojiId: profileBackgroundEmojiId) + } + public func updatePeerEmojiStatus(peerId: EnginePeer.Id, fileId: Int64?, expirationDate: Int32?) -> Signal { return _internal_updatePeerEmojiStatus(account: self.account, peerId: peerId, fileId: fileId, expirationDate: expirationDate) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift index 74a3fcb96d..c4ebfb1334 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdatePeerInfo.swift @@ -98,15 +98,21 @@ func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer. return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId) { if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) { - let flagsReplies: Int32 = (1 << 0) | (1 << 2) + var flagsReplies: Int32 = (1 << 2) + if backgroundEmojiId != nil { + flagsReplies |= 1 << 0 + } - var flagsProfile: Int32 = (1 << 0) | (1 << 1) + var flagsProfile: Int32 = (1 << 1) + if profileBackgroundEmojiId != nil { + flagsProfile |= 1 << 0 + } if profileColor != nil { flagsProfile |= (1 << 2) } return combineLatest( - account.network.request(Api.functions.channels.updateColor(flags: flagsReplies, channel: inputChannel, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId ?? 0)) + account.network.request(Api.functions.channels.updateColor(flags: flagsReplies, channel: inputChannel, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId)) |> map(Optional.init) |> `catch` { error -> Signal in if error.errorDescription.hasPrefix("CHAT_NOT_MODIFIED") { @@ -115,7 +121,7 @@ func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer. return .fail(error) } }, - account.network.request(Api.functions.channels.updateColor(flags: flagsProfile, channel: inputChannel, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId ?? 0)) + account.network.request(Api.functions.channels.updateColor(flags: flagsProfile, channel: inputChannel, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId)) |> map(Optional.init) |> `catch` { error -> Signal in if error.errorDescription.hasPrefix("CHAT_NOT_MODIFIED") { @@ -162,6 +168,105 @@ func _internal_updatePeerNameColorAndEmoji(account: Account, peerId: EnginePeer. |> switchToLatest } +func _internal_updatePeerNameColor(account: Account, peerId: EnginePeer.Id, nameColor: PeerNameColor, backgroundEmojiId: Int64?) -> Signal { + return account.postbox.transaction { transaction -> Signal in + if let peer = transaction.getPeer(peerId) { + if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) { + var flagsReplies: Int32 = (1 << 2) + if backgroundEmojiId != nil { + flagsReplies |= 1 << 0 + } + + return account.network.request(Api.functions.channels.updateColor(flags: flagsReplies, channel: inputChannel, color: nameColor.rawValue, backgroundEmojiId: backgroundEmojiId)) + |> map(Optional.init) + |> `catch` { error -> Signal in + if error.errorDescription.hasPrefix("CHAT_NOT_MODIFIED") { + return .single(nil) + } else { + return .fail(error) + } + } + |> mapError { error -> UpdatePeerNameColorAndEmojiError in + if error.errorDescription.hasPrefix("BOOSTS_REQUIRED") { + return .channelBoostRequired + } + return .generic + } + |> mapToSignal { repliesResult -> Signal in + if let repliesResult = repliesResult { + account.stateManager.addUpdates(repliesResult) + } + + return account.postbox.transaction { transaction -> Void in + if let repliesResult = repliesResult, let apiChat = apiUpdatesGroups(repliesResult).first { + let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [apiChat], users: []) + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers) + } + } + |> mapError { _ -> UpdatePeerNameColorAndEmojiError in } + } + } else { + return .fail(.generic) + } + } else { + return .fail(.generic) + } + } + |> castError(UpdatePeerNameColorAndEmojiError.self) + |> switchToLatest +} + +func _internal_updatePeerProfileColor(account: Account, peerId: EnginePeer.Id, profileColor: PeerNameColor?, profileBackgroundEmojiId: Int64?) -> Signal { + return account.postbox.transaction { transaction -> Signal in + if let peer = transaction.getPeer(peerId) { + if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) { + var flagsProfile: Int32 = (1 << 1) + if profileBackgroundEmojiId != nil { + flagsProfile |= 1 << 0 + } + if profileColor != nil { + flagsProfile |= (1 << 2) + } + + return account.network.request(Api.functions.channels.updateColor(flags: flagsProfile, channel: inputChannel, color: profileColor?.rawValue, backgroundEmojiId: profileBackgroundEmojiId)) + |> map(Optional.init) + |> `catch` { error -> Signal in + if error.errorDescription.hasPrefix("CHAT_NOT_MODIFIED") { + return .single(nil) + } else { + return .fail(error) + } + } + |> mapError { error -> UpdatePeerNameColorAndEmojiError in + if error.errorDescription.hasPrefix("BOOSTS_REQUIRED") { + return .channelBoostRequired + } + return .generic + } + |> mapToSignal { profileResult -> Signal in + if let profileResult = profileResult { + account.stateManager.addUpdates(profileResult) + } + + return account.postbox.transaction { transaction -> Void in + if let profileResult = profileResult, let apiChat = apiUpdatesGroups(profileResult).first { + let parsedPeers = AccumulatedPeers(transaction: transaction, chats: [apiChat], users: []) + updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers) + } + } + |> mapError { _ -> UpdatePeerNameColorAndEmojiError in } + } + } else { + return .fail(.generic) + } + } else { + return .fail(.generic) + } + } + |> castError(UpdatePeerNameColorAndEmojiError.self) + |> switchToLatest +} + public enum UpdatePeerEmojiStatusError { case generic } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 7df2ede693..2e9ef94238 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -625,8 +625,14 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(titleString._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: attributePeerIds)) } } - case let .customText(text, entities): - attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage()) + case let .customText(text, entities, additionalAttributes): + let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage())) + if let additionalAttributes { + for (range, key, value) in additionalAttributes.attributes { + mutableAttributedString.addAttribute(key, value: value, range: range) + } + } + attributedString = mutableAttributedString case let .botDomainAccessGranted(domain): attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).string, font: titleFont, textColor: primaryTextColor) case let .botAppAccessGranted(appName, type): @@ -733,7 +739,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, case let .topicCreated(title, iconColor, iconFileId): if forForumOverview { let maybeFileId = iconFileId ?? 0 - attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicCreated(".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicCreated(".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, custom: maybeFileId == 0 ? .topic(id: message.threadId ?? 0, info: EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } else { attributedString = NSAttributedString(string: strings.Notification_ForumTopicCreated, font: titleFont, textColor: primaryTextColor) } @@ -757,9 +763,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, maybeFileId = info.icon ?? 0 } if isHidden { - attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicHidden(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicHidden(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, custom: maybeFileId == 0 ? .topic(id: message.threadId ?? 0, info: EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } else { - attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicUnhidden(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicUnhidden(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, custom: maybeFileId == 0 ? .topic(id: message.threadId ?? 0, info: EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } } else { if isHidden { @@ -794,9 +800,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, maybeFileId = info.icon ?? 0 } if isClosed { - attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicClosed(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicClosed(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, custom: maybeFileId == 0 ? .topic(id: message.threadId ?? 0, info: EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } else { - attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicReopened(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_OverviewTopicReopened(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, custom: maybeFileId == 0 ? .topic(id: message.threadId ?? 0, info: EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } } else { if isClosed { @@ -834,7 +840,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicRenamedIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".", title)._tuple, body: bodyAttributes, argumentAttributes: [ 0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), - 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)]) + 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, custom: maybeFileId == 0 ? .topic(id: message.threadId ?? 0, info: EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)]) ]) } else { attributedString = NSAttributedString(string: strings.Notification_ForumTopicRenamed(title).string, font: titleFont, textColor: primaryTextColor) @@ -867,9 +873,9 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, title = info.title } if case let .user(user) = message.author { - attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".")._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChangedAuthor(EnginePeer.user(user).displayTitle(strings: strings, displayOrder: nameDisplayOrder), ".")._tuple, body: bodyAttributes, argumentAttributes: [0: peerMentionAttributes(primaryTextColor: primaryTextColor, peerId: user.id), 1: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, custom: maybeFileId == 0 ? .topic(id: message.threadId ?? 0, info: EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } else { - attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChanged(".")._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, topicInfo: maybeFileId == 0 ? (message.threadId ?? 0, EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) + attributedString = addAttributesToStringWithRanges(strings.Notification_ForumTopicIconChanged(".")._tuple, body: bodyAttributes, argumentAttributes: [0: MarkdownAttributeSet(font: titleFont, textColor: primaryTextColor, additionalAttributes: [ChatTextInputAttributes.customEmoji.rawValue: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: maybeFileId, file: nil, custom: maybeFileId == 0 ? .topic(id: message.threadId ?? 0, info: EngineMessageHistoryThread.Info(title: title, icon: nil, iconColor: iconColor)) : nil)])]) } } case let .suggestedProfilePhoto(image): diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift index eec6701260..39101581ce 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWallpaperBubbleContentNode/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -282,7 +282,10 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode } } } else { - if item.message.id.peerId.isGroupOrChannel { + if item.associatedData.isRecentActions { + let authorName = item.message.author.flatMap { EnginePeer($0).compactDisplayTitle } ?? "" + text = item.presentationData.strings.Channel_AdminLog_ChannelChangedWallpaper(authorName).string + } else if item.message.id.peerId.isGroupOrChannel { text = item.presentationData.strings.Notification_ChannelChangedWallpaper } else { text = item.presentationData.strings.Notification_ChangedWallpaper(peerName).string diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD index afcda60459..6869a58873 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/BUILD @@ -47,6 +47,7 @@ swift_library( "//submodules/TemporaryCachedPeerDataManager", "//submodules/UndoUI", "//submodules/WallpaperBackgroundNode", + "//submodules/TextFormat", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift index 0dea6e479e..dfb8fb018f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsHistoryTransition.swift @@ -9,6 +9,7 @@ import AccountContext import ChatControllerInteraction import ChatHistoryEntry import ChatMessageItemImpl +import TextFormat enum ChatRecentActionsEntryContentIndex: Int32 { case header = 0 @@ -56,6 +57,22 @@ private func appendAttributedText(text: PresentationStrings.FormattedString, gen string.append(text.string) } +private func appendAttributedText(text: PresentationStrings.FormattedString, additionalAttributes: inout [(NSRange, NSAttributedString.Key, Any)], generateEntities: (Int) -> ([MessageTextEntityType], [NSAttributedString.Key: Any]), to string: inout String, entities: inout [MessageTextEntity]) { + let nsString = string as NSString + for rangeItem in text.ranges { + let (types, additionalValues) = generateEntities(rangeItem.index) + for type in types { + entities.append(MessageTextEntity(range: (nsString.length + rangeItem.range.lowerBound) ..< (nsString.length + rangeItem.range.upperBound), type: type)) + } + let lowerBound = nsString.length + rangeItem.range.lowerBound + let range = NSRange(location: lowerBound, length: nsString.length + rangeItem.range.upperBound - lowerBound) + for (key, value) in additionalValues { + additionalAttributes.append((range, key, value)) + } + } + string.append(text.string) +} + private func appendAttributedText(text: String, withEntities: [MessageTextEntityType], to string: inout String, entities: inout [MessageTextEntity]) { for type in withEntities { entities.append(MessageTextEntity(range: string.count ..< (string.count + text.count), type: type)) @@ -146,7 +163,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: @@ -184,7 +201,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities) + let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: @@ -234,7 +251,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities) + let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: @@ -318,7 +335,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleSignatures(value): @@ -345,7 +362,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updatePinned(message): @@ -376,7 +393,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: @@ -415,7 +432,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -460,7 +477,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -503,7 +520,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -962,7 +979,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -992,7 +1009,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1079,7 +1096,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1152,7 +1169,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1179,7 +1196,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } else { - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1210,7 +1227,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1247,7 +1264,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1281,7 +1298,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1310,7 +1327,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1341,7 +1358,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1367,7 +1384,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1393,7 +1410,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1419,7 +1436,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1450,7 +1467,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1481,7 +1498,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1552,7 +1569,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1583,7 +1600,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1623,7 +1640,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) @@ -1651,7 +1668,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .sendMessage(message): @@ -1676,7 +1693,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.headerStableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: @@ -1713,7 +1730,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .deleteTopic(info): @@ -1734,7 +1751,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .editTopic(prevInfo, newInfo): @@ -1807,7 +1824,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .pinTopic(prevInfo, newInfo): @@ -1845,7 +1862,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { }, to: &text, entities: &entities) } - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleForum(isForum): @@ -1866,7 +1883,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleAntiSpam(isEnabled): @@ -1887,73 +1904,220 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { return [] }, to: &text, entities: &entities) - let action = TelegramMediaActionType.customText(text: text, entities: entities) + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) - - case let .changeNameColor(_, updatedValue): - var peers = SimpleDictionary() - var author: Peer? - if let peer = self.entry.peers[self.entry.event.peerId] { - author = peer - peers[peer.id] = peer - } + case let .changeNameColor(_, _, updatedColor, updatedIcon): + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + let authorTitle = author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" - var text: String = "" - var entities: [MessageTextEntity] = [] - - let _ = updatedValue + var text: String = "" + var entities: [MessageTextEntity] = [] + var additionalAttributes: [(NSRange, NSAttributedString.Key, Any)] = [] - let rawText = self.presentationData.strings.Channel_AdminLog_MessageChangedNameColorSet(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", "●") + if let updatedIcon { + let rawText = self.presentationData.strings.Channel_AdminLog_ChannelChangedNameColorAndIcon(authorTitle, ".", ".") - appendAttributedText(text: rawText, generateEntities: { index in + let colors = context.peerNameColors.get(updatedColor) + var colorList: [UInt32] = [] + colorList.append(colors.main.argb) + if let secondary = colors.secondary { + colorList.append(secondary.argb) + } + if let tertiary = colors.tertiary { + colorList.append(tertiary.argb) + } + + appendAttributedText(text: rawText, additionalAttributes: &additionalAttributes, generateEntities: { index in + if index == 0, let author = author { + return ([.TextMention(peerId: author.id)], [:]) + } else if index == 1 { + return ([], [ + ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .nameColors(colorList)) + ]) + } else if index == 2 { + return ([.CustomEmoji(stickerPack: nil, fileId: updatedIcon)], [:]) + } else { + return ([], [:]) + } + }, to: &text, entities: &entities) + } else { + let rawText = self.presentationData.strings.Channel_AdminLog_ChannelChangedNameColor(authorTitle, ".") + + let colors = context.peerNameColors.get(updatedColor) + var colorList: [UInt32] = [] + colorList.append(colors.main.argb) + if let secondary = colors.secondary { + colorList.append(secondary.argb) + } + if let tertiary = colors.tertiary { + colorList.append(tertiary.argb) + } + + appendAttributedText(text: rawText, additionalAttributes: &additionalAttributes, generateEntities: { index in + if index == 0, let author = author { + return ([.TextMention(peerId: author.id)], [:]) + } else if index == 1 { + return ([], [ + ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .nameColors(colorList)) + ]) + } else { + return ([], [:]) + } + }, to: &text, entities: &entities) + } + + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: TelegramMediaActionType.CustomTextAttributes(attributes: additionalAttributes)) + + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .changeProfileColor(_, _, updatedColor, updatedIcon): + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + let authorTitle = author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" + + var text: String = "" + var entities: [MessageTextEntity] = [] + var additionalAttributes: [(NSRange, NSAttributedString.Key, Any)] = [] + + if let updatedColor, let updatedIcon { + let rawText = self.presentationData.strings.Channel_AdminLog_ChannelChangedProfileColorAndIcon(authorTitle, ".", ".") + + let colors = context.peerNameColors.get(updatedColor) + var colorList: [UInt32] = [] + colorList.append(colors.main.argb) + if let secondary = colors.secondary { + colorList.append(secondary.argb) + } + if let tertiary = colors.tertiary { + colorList.append(tertiary.argb) + } + + appendAttributedText(text: rawText, additionalAttributes: &additionalAttributes, generateEntities: { index in + if index == 0, let author = author { + return ([.TextMention(peerId: author.id)], [:]) + } else if index == 1 { + return ([], [ + ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .nameColors(colorList)) + ]) + } else if index == 2 { + return ([.CustomEmoji(stickerPack: nil, fileId: updatedIcon)], [:]) + } else { + return ([], [:]) + } + }, to: &text, entities: &entities) + } else if let updatedColor { + let rawText = self.presentationData.strings.Channel_AdminLog_ChannelChangedProfileColor(authorTitle, ".") + + let colors = context.peerNameColors.get(updatedColor) + var colorList: [UInt32] = [] + colorList.append(colors.main.argb) + if let secondary = colors.secondary { + colorList.append(secondary.argb) + } + if let tertiary = colors.tertiary { + colorList.append(tertiary.argb) + } + + appendAttributedText(text: rawText, additionalAttributes: &additionalAttributes, generateEntities: { index in + if index == 0, let author = author { + return ([.TextMention(peerId: author.id)], [:]) + } else if index == 1 { + return ([], [ + ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .nameColors(colorList)) + ]) + } else { + return ([], [:]) + } + }, to: &text, entities: &entities) + } else { + let rawText = self.presentationData.strings.Channel_AdminLog_ChannelRemovedProfileColorAndIcon(authorTitle) + + appendAttributedText(text: rawText, additionalAttributes: &additionalAttributes, generateEntities: { index in + if index == 0, let author = author { + return ([.TextMention(peerId: author.id)], [:]) + } else { + return ([], [:]) + } + }, to: &text, entities: &entities) + } + + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: TelegramMediaActionType.CustomTextAttributes(attributes: additionalAttributes)) + + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .changeStatus(_, status): + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + + let authorTitle: String = author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" + + var text: String = "" + var entities: [MessageTextEntity] = [] + + if let status { + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_ChannelUpdatedStatus(authorTitle, "."), generateEntities: { index in if index == 0, let author = author { return [.TextMention(peerId: author.id)] } else if index == 1 { - return [.Bold] + return [.CustomEmoji(stickerPack: nil, fileId: status.fileId)] } return [] }, to: &text, entities: &entities) - - let action = TelegramMediaActionType.customText(text: text, entities: entities) - - let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) - return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) -// case let .changeBackgroundEmojiId(_, updatedValue): -// var peers = SimpleDictionary() -// var author: Peer? -// if let peer = self.entry.peers[self.entry.event.peerId] { -// author = peer -// peers[peer.id] = peer -// } -// -// var text: String = "" -// var entities: [MessageTextEntity] = [] -// -// if let updatedValue, updatedValue != 0 { -// appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageChangedBackgroundEmojiSet(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", "."), generateEntities: { index in -// if index == 0, let author = author { -// return [.TextMention(peerId: author.id)] -// } else if index == 1 { -// return [.CustomEmoji(stickerPack: nil, fileId: updatedValue)] -// } -// return [] -// }, to: &text, entities: &entities) -// } else { -// let rawText = self.presentationData.strings.Channel_AdminLog_MessageChangedBackgroundEmojiRemoved(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "") -// -// appendAttributedText(text: rawText, generateEntities: { index in -// if index == 0, let author = author { -// return [.TextMention(peerId: author.id)] -// } -// return [] -// }, to: &text, entities: &entities) -// } -// -// let action = TelegramMediaActionType.customText(text: text, entities: entities) -// -// let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) -// return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + } else { + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_ChannelRemovedStatus(authorTitle), generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + } + + let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) + + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .changeWallpaper(_, wallpaper): + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + + let authorTitle: String = author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" + + var text: String = "" + var entities: [MessageTextEntity] = [] + + let action: TelegramMediaActionType + if let wallpaper { + action = TelegramMediaActionType.setChatWallpaper(wallpaper: wallpaper, forBoth: false) + } else { + appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_ChannelRemovedWallpaper(authorTitle), generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } + return [] + }, to: &text, entities: &entities) + action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil) + } + + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } } } diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 98e17f2e40..d2f3799ef6 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -166,6 +166,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { public let titleLeftIconNode: ASImageNode public let titleRightIconNode: ASImageNode public let titleCredibilityIconView: ComponentHostView + public let titleVerifiedIconView: ComponentHostView public let activityNode: ChatTitleActivityNode private let button: HighlightTrackingButtonNode @@ -178,6 +179,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { private var titleLeftIcon: ChatTitleIcon = .none private var titleRightIcon: ChatTitleIcon = .none private var titleCredibilityIcon: ChatTitleCredibilityIcon = .none + private var titleVerifiedIcon: ChatTitleCredibilityIcon = .none private var presenceManager: PeerPresenceStatusManager? @@ -224,6 +226,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var titleLeftIcon: ChatTitleIcon = .none var titleRightIcon: ChatTitleIcon = .none var titleCredibilityIcon: ChatTitleCredibilityIcon = .none + var titleVerifiedIcon: ChatTitleCredibilityIcon = .none var isEnabled = true switch titleContent { case let .peer(peerView, customTitle, _, isScheduledMessages, isMuted, _, isEnabledValue): @@ -258,6 +261,9 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } else if peer.isScam { titleCredibilityIcon = .scam } else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled { + if peer is TelegramChannel, peer.isVerified { + titleVerifiedIcon = .verified + } titleCredibilityIcon = .emojiStatus(emojiStatus) } else if peer.isVerified { titleCredibilityIcon = .verified @@ -381,18 +387,11 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { if titleCredibilityIcon != self.titleCredibilityIcon { self.titleCredibilityIcon = titleCredibilityIcon - /*switch titleCredibilityIcon { - case .none: - self.titleCredibilityIconNode.image = nil - case .fake: - self.titleCredibilityIconNode.image = PresentationResourcesChatList.fakeIcon(titleTheme, strings: self.strings, type: .regular) - case .scam: - self.titleCredibilityIconNode.image = PresentationResourcesChatList.scamIcon(titleTheme, strings: self.strings, type: .regular) - case .verified: - self.titleCredibilityIconNode.image = PresentationResourcesChatList.verifiedIcon(titleTheme) - case .premium: - self.titleCredibilityIconNode.image = PresentationResourcesChatList.premiumIcon(titleTheme) - }*/ + updated = true + } + + if titleVerifiedIcon != self.titleVerifiedIcon { + self.titleVerifiedIcon = titleVerifiedIcon updated = true } @@ -696,6 +695,9 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.titleCredibilityIconView = ComponentHostView() self.titleCredibilityIconView.isUserInteractionEnabled = false + self.titleVerifiedIconView = ComponentHostView() + self.titleVerifiedIconView.isUserInteractionEnabled = false + self.activityNode = ChatTitleActivityNode() self.button = HighlightTrackingButtonNode() @@ -721,13 +723,16 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { strongSelf.titleTextNode.layer.removeAnimation(forKey: "opacity") strongSelf.activityNode.layer.removeAnimation(forKey: "opacity") strongSelf.titleCredibilityIconView.layer.removeAnimation(forKey: "opacity") + strongSelf.titleVerifiedIconView.layer.removeAnimation(forKey: "opacity") strongSelf.titleTextNode.alpha = 0.4 strongSelf.activityNode.alpha = 0.4 strongSelf.titleCredibilityIconView.alpha = 0.4 + strongSelf.titleVerifiedIconView.alpha = 0.4 } else { strongSelf.titleTextNode.alpha = 1.0 strongSelf.activityNode.alpha = 1.0 strongSelf.titleCredibilityIconView.alpha = 1.0 + strongSelf.titleVerifiedIconView.alpha = 1.0 strongSelf.titleTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) strongSelf.activityNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) } @@ -756,6 +761,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { let titleContent = self.titleContent self.titleCredibilityIcon = .none + self.titleVerifiedIcon = .none self.titleContent = titleContent let _ = self.updateStatus() @@ -774,6 +780,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var leftIconWidth: CGFloat = 0.0 var rightIconWidth: CGFloat = 0.0 var credibilityIconWidth: CGFloat = 0.0 + var verifiedIconWidth: CGFloat = 0.0 if let image = self.titleLeftIconNode.image { if self.titleLeftIconNode.supernode == nil { @@ -800,6 +807,22 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { titleCredibilityContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2)) } + let titleVerifiedContent: EmojiStatusComponent.Content + switch self.titleVerifiedIcon { + case .none: + titleVerifiedContent = .none + case .premium: + titleVerifiedContent = .premium(color: self.theme.list.itemAccentColor) + case .verified: + titleVerifiedContent = .verified(fillColor: self.theme.list.itemCheckColors.fillColor, foregroundColor: self.theme.list.itemCheckColors.foregroundColor, sizeType: .large) + case .fake: + titleVerifiedContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_FakeAccount.uppercased()) + case .scam: + titleVerifiedContent = .text(color: self.theme.chat.message.incoming.scamColor, string: self.strings.Message_ScamAccount.uppercased()) + case let .emojiStatus(emojiStatus): + titleVerifiedContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: self.theme.list.mediaPlaceholderColor, themeColor: self.theme.list.itemAccentColor, loopMode: .count(2)) + } + let titleCredibilitySize = self.titleCredibilityIconView.update( transition: .immediate, component: AnyComponent(EmojiStatusComponent( @@ -814,6 +837,20 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { containerSize: CGSize(width: 20.0, height: 20.0) ) + let titleVerifiedSize = self.titleVerifiedIconView.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: self.context, + animationCache: self.animationCache, + animationRenderer: self.animationRenderer, + content: titleVerifiedContent, + isVisibleForAnimations: true, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 20.0, height: 20.0) + ) + if self.titleCredibilityIcon != .none { self.titleTextNode.view.addSubview(self.titleCredibilityIconView) credibilityIconWidth = titleCredibilitySize.width + 3.0 @@ -823,6 +860,15 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } } + if self.titleVerifiedIcon != .none { + self.titleTextNode.view.addSubview(self.titleVerifiedIconView) + verifiedIconWidth = titleVerifiedSize.width + 3.0 + } else { + if self.titleVerifiedIconView.superview != nil { + self.titleVerifiedIconView.removeFromSuperview() + } + } + if let image = self.titleRightIconNode.image { if self.titleRightIconNode.supernode == nil { self.titleTextNode.addSubnode(self.titleRightIconNode) @@ -840,8 +886,9 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { let titleSideInset: CGFloat = 6.0 var titleFrame: CGRect if size.height > 40.0 { - var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), animated: titleTransition.isAnimated) + var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - verifiedIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), animated: titleTransition.isAnimated) titleSize.width += credibilityIconWidth + titleSize.width += verifiedIconWidth let activitySize = self.activityNode.updateLayout(CGSize(width: clearBounds.size.width - titleSideInset * 2.0, height: clearBounds.size.height), alignment: .center) let titleInfoSpacing: CGFloat = 0.0 @@ -874,30 +921,48 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: -image.size.width - 3.0 - UIScreenPixel, y: 4.0), size: image.size) } - self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.width - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) + var nextIconX: CGFloat = titleFrame.width + + self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: titleFrame.width - titleVerifiedSize.width, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) + nextIconX -= titleVerifiedSize.width + if !titleVerifiedSize.width.isZero { + nextIconX -= 2.0 + } + + self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) + nextIconX -= titleCredibilitySize.width if let image = self.titleRightIconNode.image { self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.width + 3.0 + UIScreenPixel, y: 6.0), size: image.size) } } else { - let titleSize = self.titleTextNode.updateLayout(size: CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height), animated: titleTransition.isAnimated) + let titleSize = self.titleTextNode.updateLayout(size: CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - credibilityIconWidth - verifiedIconWidth - rightIconWidth - titleSideInset * 2.0), height: size.height), animated: titleTransition.isAnimated) let activitySize = self.activityNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height), alignment: .center) let titleInfoSpacing: CGFloat = 8.0 - let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing + let combinedWidth = titleSize.width + leftIconWidth + credibilityIconWidth + verifiedIconWidth + rightIconWidth + activitySize.width + titleInfoSpacing titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize) titleTransition.updateFrameAdditiveToCenter(view: self.titleContainerView, frame: titleFrame) titleTransition.updateFrameAdditiveToCenter(node: self.titleTextNode, frame: CGRect(origin: CGPoint(), size: titleFrame.size)) - self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize) + self.activityNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + credibilityIconWidth + verifiedIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - activitySize.height) / 2.0)), size: activitySize) if let image = self.titleLeftIconNode.image { self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size) } - self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) + var nextIconX: CGFloat = titleFrame.maxX + + self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleVerifiedSize.width, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) + nextIconX -= titleVerifiedSize.width + if !titleVerifiedSize.width.isZero { + nextIconX -= 2.0 + } + + self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) + nextIconX -= titleCredibilitySize.width if let image = self.titleRightIconNode.image { self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width, y: titleFrame.minY + 6.0), size: image.size) diff --git a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift index f4f1011708..2674eb490d 100644 --- a/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift +++ b/submodules/TelegramUI/Components/EmojiTextAttachmentView/Sources/EmojiTextAttachmentView.swift @@ -144,6 +144,58 @@ public func animationCacheFetchFile(postbox: Postbox, userLocation: MediaResourc } } +private func generatePeerNameColorImage(nameColor: PeerNameColors.Colors, isDark: Bool, bounds: CGSize = CGSize(width: 40.0, height: 40.0), size: CGSize = CGSize(width: 40.0, height: 40.0)) -> UIImage? { + return generateImage(bounds, rotatedContext: { contextSize, context in + let bounds = CGRect(origin: CGPoint(), size: contextSize) + context.clear(bounds) + + let circleBounds = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - size.width) / 2.0), y: floorToScreenPixels((bounds.height - size.height) / 2.0)), size: size) + context.addEllipse(in: circleBounds) + context.clip() + + if let secondColor = nameColor.secondary { + var firstColor = nameColor.main + var secondColor = secondColor + if isDark, nameColor.tertiary == nil { + firstColor = secondColor + secondColor = nameColor.main + } + + context.setFillColor(secondColor.cgColor) + context.fill(circleBounds) + + if let thirdColor = nameColor.tertiary { + context.move(to: CGPoint(x: contextSize.width, y: 0.0)) + context.addLine(to: CGPoint(x: contextSize.width, y: contextSize.height)) + context.addLine(to: CGPoint(x: 0.0, y: contextSize.height)) + context.closePath() + context.setFillColor(firstColor.cgColor) + context.fillPath() + + context.setFillColor(thirdColor.cgColor) + context.translateBy(x: contextSize.width / 2.0, y: contextSize.height / 2.0) + context.rotate(by: .pi / 4.0) + + let rectSide = size.width / 40.0 * 18.0 + let rectCornerRadius = round(size.width / 40.0 * 4.0) + let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: -rectSide / 2.0, y: -rectSide / 2.0), size: CGSize(width: rectSide, height: rectSide)), cornerRadius: rectCornerRadius) + context.addPath(path.cgPath) + context.fillPath() + } else { + context.move(to: .zero) + context.addLine(to: CGPoint(x: contextSize.width, y: 0.0)) + context.addLine(to: CGPoint(x: 0.0, y: contextSize.height)) + context.closePath() + context.setFillColor(firstColor.cgColor) + context.fillPath() + } + } else { + context.setFillColor(nameColor.main.cgColor) + context.fill(circleBounds) + } + }) +} + public final class InlineStickerItemLayer: MultiAnimationRenderTarget { public enum Context: Equatable { public final class Custom: Equatable { @@ -317,8 +369,13 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { super.init() - if let topicInfo = emoji.topicInfo { - self.updateTopicInfo(topicInfo: topicInfo) + if let custom = emoji.custom { + switch custom { + case let .topic(id, info): + self.updateTopicInfo(topicInfo: (id, info)) + case let .nameColors(colors): + self.updateNameColors(colors: colors) + } } else if let file = file { self.updateFile(file: file, attemptSynchronousLoad: attemptSynchronousLoad) } else { @@ -467,6 +524,30 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget { } } + private func updateNameColors(colors: [UInt32]) { + if colors.isEmpty { + return + } + let main = UIColor(rgb: colors[0]) + var secondary: UIColor? + if colors.count >= 2 { + secondary = UIColor(rgb: colors[1]) + } + var tertiary: UIColor? + if colors.count >= 3 { + tertiary = UIColor(rgb: colors[2]) + } + let mappedColor = PeerNameColors.Colors(main: main, secondary: secondary, tertiary: tertiary) + + let image = generateImage(CGSize(width: 18.0, height: 18.0), contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + if let cgImage = generatePeerNameColorImage(nameColor: mappedColor, isDark: false, bounds: CGSize(width: 18.0, height: 18.0), size: CGSize(width: 16.0, height: 16.0))?.cgImage { + context.draw(cgImage, in: CGRect(origin: .zero, size: size)) + } + }) + self.contents = image?.cgImage + } + private func updateFile(file: TelegramMediaFile, attemptSynchronousLoad: Bool) { if self.file?.fileId == file.fileId { return diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index 54efd21ca2..0573c39edd 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -830,9 +830,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen } } - if peerId == context.account.peerId, case .peer = chatLocation { + /*if peerId == context.account.peerId, case .peer = chatLocation { availablePanes?.insert(.savedMessagesChats, at: 0) - } + }*/ } else { availablePanes = nil } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index 3571c93393..1f9f270ce1 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -108,10 +108,17 @@ final class PeerInfoHeaderNode: ASDisplayNode { let titleNodeContainer: ASDisplayNode let titleNodeRawContainer: ASDisplayNode let titleNode: MultiScaleTextNode + let titleCredibilityIconView: ComponentHostView var credibilityIconSize: CGSize? let titleExpandedCredibilityIconView: ComponentHostView var titleExpandedCredibilityIconSize: CGSize? + + let titleVerifiedIconView: ComponentHostView + var verifiedIconSize: CGSize? + let titleExpandedVerifiedIconView: ComponentHostView + var titleExpandedVerifiedIconSize: CGSize? + let subtitleNodeContainer: ASDisplayNode let subtitleNodeRawContainer: ASDisplayNode let subtitleNode: MultiScaleTextNode @@ -189,6 +196,12 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.titleExpandedCredibilityIconView = ComponentHostView() self.titleNode.stateNode(forKey: TitleNodeStateExpanded)?.view.addSubview(self.titleExpandedCredibilityIconView) + self.titleVerifiedIconView = ComponentHostView() + self.titleNode.stateNode(forKey: TitleNodeStateRegular)?.view.addSubview(self.titleVerifiedIconView) + + self.titleExpandedVerifiedIconView = ComponentHostView() + self.titleNode.stateNode(forKey: TitleNodeStateExpanded)?.view.addSubview(self.titleExpandedVerifiedIconView) + self.subtitleNodeContainer = ASDisplayNode() self.subtitleNodeRawContainer = ASDisplayNode() self.subtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded]) @@ -432,6 +445,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { } private var currentCredibilityIcon: CredibilityIcon? + private var currentVerifiedIcon: CredibilityIcon? private var currentPanelStatusData: PeerInfoStatusData? func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, threadData: MessageHistoryThreadData?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: (PeerInfoStatusData?, PeerInfoStatusData?, CGFloat?), isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, transition: ContainedViewLayoutTransition, additive: Bool, animateHeader: Bool) -> CGFloat { @@ -471,12 +485,16 @@ final class PeerInfoHeaderNode: ASDisplayNode { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 }) let credibilityIcon: CredibilityIcon + var verifiedIcon: CredibilityIcon = .none if let peer = peer { if peer.isFake { credibilityIcon = .fake } else if peer.isScam { credibilityIcon = .scam } else if let emojiStatus = peer.emojiStatus, !premiumConfiguration.isPremiumDisabled { + if peer is TelegramChannel, peer.isVerified { + verifiedIcon = .verified + } credibilityIcon = .emojiStatus(emojiStatus) } else if peer.isVerified { credibilityIcon = .verified @@ -714,8 +732,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { emojiExpandedStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: navigationContentsAccentColor, themeColor: navigationContentsAccentColor, loopMode: .forever) } - //let animateStatusIcon = !self.titleCredibilityIconView.bounds.isEmpty - let iconSize = self.titleCredibilityIconView.update( transition: Transition(navigationTransition), component: AnyComponent(EmojiStatusComponent( @@ -796,6 +812,73 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.titleExpandedCredibilityIconSize = expandedIconSize } + do { + self.currentVerifiedIcon = verifiedIcon + + var currentEmojiStatus: PeerEmojiStatus? + let emojiRegularStatusContent: EmojiStatusComponent.Content + let emojiExpandedStatusContent: EmojiStatusComponent.Content + switch verifiedIcon { + case .none: + emojiRegularStatusContent = .none + emojiExpandedStatusContent = .none + case .premium: + emojiRegularStatusContent = .premium(color: navigationContentsAccentColor) + emojiExpandedStatusContent = .premium(color: navigationContentsAccentColor) + case .verified: + emojiRegularStatusContent = .verified(fillColor: presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: presentationData.theme.list.itemCheckColors.foregroundColor, sizeType: .large) + emojiExpandedStatusContent = .verified(fillColor: navigationContentsAccentColor, foregroundColor: .clear, sizeType: .large) + case .fake: + emojiRegularStatusContent = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_FakeAccount.uppercased()) + emojiExpandedStatusContent = emojiRegularStatusContent + case .scam: + emojiRegularStatusContent = .text(color: presentationData.theme.chat.message.incoming.scamColor, string: presentationData.strings.Message_ScamAccount.uppercased()) + emojiExpandedStatusContent = emojiRegularStatusContent + case let .emojiStatus(emojiStatus): + currentEmojiStatus = emojiStatus + emojiRegularStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: presentationData.theme.list.mediaPlaceholderColor, themeColor: navigationContentsAccentColor, loopMode: .forever) + emojiExpandedStatusContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 80.0, height: 80.0), placeholderColor: navigationContentsAccentColor, themeColor: navigationContentsAccentColor, loopMode: .forever) + } + + let iconSize = self.titleVerifiedIconView.update( + transition: Transition(navigationTransition), + component: AnyComponent(EmojiStatusComponent( + context: self.context, + animationCache: self.animationCache, + animationRenderer: self.animationRenderer, + content: emojiRegularStatusContent, + isVisibleForAnimations: true, + useSharedAnimation: true, + action: nil, + emojiFileUpdated: nil + )), + environment: {}, + containerSize: CGSize(width: 34.0, height: 34.0) + ) + let expandedIconSize = self.titleExpandedVerifiedIconView.update( + transition: Transition(navigationTransition), + component: AnyComponent(EmojiStatusComponent( + context: self.context, + animationCache: self.animationCache, + animationRenderer: self.animationRenderer, + content: emojiExpandedStatusContent, + isVisibleForAnimations: true, + useSharedAnimation: true, + action: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.displayPremiumIntro?(strongSelf.titleExpandedVerifiedIconView, currentEmojiStatus, strongSelf.emojiStatusFileAndPackTitle.get(), true) + } + )), + environment: {}, + containerSize: CGSize(width: 34.0, height: 34.0) + ) + + self.verifiedIconSize = iconSize + self.titleExpandedVerifiedIconSize = expandedIconSize + } + self.navigationButtonContainer.updateContentsColor(backgroundContentColor: headerButtonBackgroundColor, contentsColor: navigationContentsAccentColor, canBeExpanded: navigationContentsCanBeExpanded, transition: navigationTransition) self.titleNode.updateTintColor(color: navigationContentsPrimaryColor, transition: navigationTransition) @@ -1128,16 +1211,35 @@ final class PeerInfoHeaderNode: ASDisplayNode { let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size var titleHorizontalOffset: CGFloat = 0.0 + var nextIconX: CGFloat = titleSize.width + var nextExpandedIconX: CGFloat = titleExpandedSize.width + if let credibilityIconSize = self.credibilityIconSize, let titleExpandedCredibilityIconSize = self.titleExpandedCredibilityIconSize { - titleHorizontalOffset = -(credibilityIconSize.width + 4.0) / 2.0 + titleHorizontalOffset += -(credibilityIconSize.width + 4.0) / 2.0 var collapsedTransitionOffset: CGFloat = 0.0 if let navigationTransition = self.navigationTransition { collapsedTransitionOffset = -10.0 * navigationTransition.fraction } - transition.updateFrame(view: self.titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleSize.width + 4.0 + collapsedTransitionOffset, y: floor((titleSize.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize)) - transition.updateFrame(view: self.titleExpandedCredibilityIconView, frame: CGRect(origin: CGPoint(x: titleExpandedSize.width + 4.0, y: floor((titleExpandedSize.height - titleExpandedCredibilityIconSize.height) / 2.0) + 1.0), size: titleExpandedCredibilityIconSize)) + transition.updateFrame(view: self.titleCredibilityIconView, frame: CGRect(origin: CGPoint(x: nextIconX + 4.0 + collapsedTransitionOffset, y: floor((titleSize.height - credibilityIconSize.height) / 2.0)), size: credibilityIconSize)) + nextIconX += 4.0 + credibilityIconSize.width + transition.updateFrame(view: self.titleExpandedCredibilityIconView, frame: CGRect(origin: CGPoint(x: nextExpandedIconX + 4.0, y: floor((titleExpandedSize.height - titleExpandedCredibilityIconSize.height) / 2.0) + 1.0), size: titleExpandedCredibilityIconSize)) + nextExpandedIconX += 4.0 + titleExpandedCredibilityIconSize.width + } + + if let verifiedIconSize = self.verifiedIconSize, let titleExpandedVerifiedIconSize = self.titleExpandedVerifiedIconSize { + titleHorizontalOffset += -(verifiedIconSize.width + 4.0) / 2.0 + + var collapsedTransitionOffset: CGFloat = 0.0 + if let navigationTransition = self.navigationTransition { + collapsedTransitionOffset = -10.0 * navigationTransition.fraction + } + + transition.updateFrame(view: self.titleVerifiedIconView, frame: CGRect(origin: CGPoint(x: nextIconX + 4.0 + collapsedTransitionOffset, y: floor((titleSize.height - verifiedIconSize.height) / 2.0)), size: verifiedIconSize)) + nextIconX += 4.0 + verifiedIconSize.width + transition.updateFrame(view: self.titleExpandedVerifiedIconView, frame: CGRect(origin: CGPoint(x: nextExpandedIconX + 4.0, y: floor((titleExpandedSize.height - titleExpandedVerifiedIconSize.height) / 2.0) + 1.0), size: titleExpandedVerifiedIconSize)) + nextExpandedIconX += 4.0 + titleExpandedVerifiedIconSize.width } var titleFrame: CGRect diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 7e0ef15460..72ebc9e0eb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -2257,6 +2257,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro private var translationState: ChatTranslationState? private var translationStateDisposable: Disposable? + private var boostStatus: ChannelBoostStatus? + private var boostStatusDisposable: Disposable? + private var expiringStoryList: PeerExpiringStoryListContext? private var expiringStoryListState: PeerExpiringStoryListContext.State? private var expiringStoryListDisposable: Disposable? @@ -4048,6 +4051,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro }) let _ = context.engine.peers.requestRecommendedChannels(peerId: peerId, forceUpdate: true).startStandalone() + + self.boostStatusDisposable = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + |> mapToSignal { peer -> Signal in + if case let .channel(channel) = peer, (channel.flags.contains(.isCreator) || channel.adminRights != nil) { + return context.engine.peers.getChannelBoostStatus(peerId: peerId) + } else { + return .single(nil) + } + } + |> deliverOnMainQueue).start(next: { [weak self] boostStatus in + guard let self else { + return + } + self.boostStatus = boostStatus + }) } if peerId.namespace == Namespaces.Peer.CloudChannel || peerId.namespace == Namespaces.Peer.CloudUser { @@ -4154,6 +4172,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro self.storyUploadProgressDisposable?.dispose() self.updateAvatarDisposable.dispose() self.joinChannelDisposable.dispose() + self.boostStatusDisposable?.dispose() } override func didLoad() { @@ -7196,7 +7215,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let controller = PeerNameColorScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, subject: .account) self.controller?.push(controller) } else if let peer = self.data?.peer, peer is TelegramChannel { - self.controller?.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId)) + self.controller?.push(ChannelAppearanceScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, boostStatus: self.boostStatus)) } } diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 8bf6cb481a..8d5e6de919 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -134,13 +134,16 @@ final class ChannelAppearanceScreenComponent: Component { let context: AccountContext let peerId: EnginePeer.Id + let boostStatus: ChannelBoostStatus? init( context: AccountContext, - peerId: EnginePeer.Id + peerId: EnginePeer.Id, + boostStatus: ChannelBoostStatus? ) { self.context = context self.peerId = peerId + self.boostStatus = boostStatus } static func ==(lhs: ChannelAppearanceScreenComponent, rhs: ChannelAppearanceScreenComponent) -> Bool { @@ -322,6 +325,33 @@ final class ChannelAppearanceScreenComponent: Component { } func attemptNavigation(complete: @escaping () -> Void) -> Bool { + guard let component = self.component, let resolvedState = self.resolveState() else { + return true + } + if self.isApplyingSettings { + return false + } + + if !resolvedState.changes.isEmpty { + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertTitle, text: presentationData.strings.Channel_Appearance_UnsavedChangesAlertText, actions: [ + TextAlertAction(type: .genericAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertDiscard, action: { [weak self] in + guard let self else { + return + } + self.environment?.controller()?.dismiss() + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Channel_Appearance_UnsavedChangesAlertApply, action: { [weak self] in + guard let self else { + return + } + self.applySettings() + }) + ]), in: .window(.root)) + + return false + } + return true } @@ -462,8 +492,15 @@ final class ChannelAppearanceScreenComponent: Component { } var signals: [Signal] = [] - if !resolvedState.changes.intersection([.nameColor, .profileColor, .replyFileId, .backgroundFileId]).isEmpty { - signals.append(component.context.engine.peers.updatePeerNameColorAndEmoji(peerId: component.peerId, nameColor: resolvedState.nameColor, backgroundEmojiId: resolvedState.replyFileId, profileColor: resolvedState.profileColor, profileBackgroundEmojiId: resolvedState.backgroundFileId) + if !resolvedState.changes.intersection([.nameColor, .replyFileId]).isEmpty { + signals.append(component.context.engine.peers.updatePeerNameColor(peerId: component.peerId, nameColor: resolvedState.nameColor, backgroundEmojiId: resolvedState.replyFileId) + |> ignoreValues + |> mapError { _ -> ApplyError in + return .generic + }) + } + if !resolvedState.changes.intersection([.profileColor, .backgroundFileId]).isEmpty { + signals.append(component.context.engine.peers.updatePeerProfileColor(peerId: component.peerId, profileColor: resolvedState.profileColor, profileBackgroundEmojiId: resolvedState.backgroundFileId) |> ignoreValues |> mapError { _ -> ApplyError in return .generic @@ -512,7 +549,17 @@ final class ChannelAppearanceScreenComponent: Component { guard let self else { return } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + let navigationController: NavigationController? = self.environment?.controller()?.navigationController as? NavigationController + self.environment?.controller()?.dismiss() + + if let lastController = navigationController?.viewControllers.last as? ViewController { + //TODO:localize + let tipController = UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: "Appearance settings have been updated.", cancel: nil, destructive: false), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false }) + lastController.present(tipController, in: .window(.root)) + } }) } @@ -733,8 +780,13 @@ final class ChannelAppearanceScreenComponent: Component { guard let self, let component = self.component else { return } - if self.contentsData == nil, case let .channel(channel) = contentsData.peer { - self.boostLevel = channel.approximateBoostLevel.flatMap(Int.init) + if self.contentsData == nil && self.boostStatus == nil { + if let boostStatus = component.boostStatus { + self.boostStatus = boostStatus + self.boostLevel = boostStatus.level + } else if case let .channel(channel) = contentsData.peer { + self.boostLevel = channel.approximateBoostLevel.flatMap(Int.init) + } } if self.contentsData == nil, let peerWallpaper = contentsData.peerWallpaper { for cloudTheme in contentsData.availableThemes { @@ -1448,13 +1500,15 @@ public class ChannelAppearanceScreen: ViewControllerComponentContainer { public init( context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, - peerId: EnginePeer.Id + peerId: EnginePeer.Id, + boostStatus: ChannelBoostStatus? ) { self.context = context super.init(context: context, component: ChannelAppearanceScreenComponent( context: context, - peerId: peerId + peerId: peerId, + boostStatus: boostStatus ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData) let presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift index 5a5e9811e0..84ecd5cac6 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift @@ -1633,7 +1633,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let attributedText = convertMarkdownToAttributes(NSAttributedString(string: serviceMessageText)) let entities = generateChatInputTextEntities(attributedText) - let message3 = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: attributedText.string, entities: entities))], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) + let message3 = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: attributedText.string, entities: entities, additionalAttributes: nil))], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, accountPeer: nil, isCentered: false)) } diff --git a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift index b165172c2c..5fa784ff84 100644 --- a/submodules/TextFormat/Sources/ChatTextInputAttributes.swift +++ b/submodules/TextFormat/Sources/ChatTextInputAttributes.swift @@ -277,16 +277,21 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { case topicInfo } + public enum Custom: Codable { + case topic(id: Int64, info: EngineMessageHistoryThread.Info) + case nameColors([UInt32]) + } + public let interactivelySelectedFromPackId: ItemCollectionId? public let fileId: Int64 public let file: TelegramMediaFile? - public let topicInfo: (Int64, EngineMessageHistoryThread.Info)? + public let custom: Custom? - public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, topicInfo: (Int64, EngineMessageHistoryThread.Info)? = nil) { + public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, custom: Custom? = nil) { self.interactivelySelectedFromPackId = interactivelySelectedFromPackId self.fileId = fileId self.file = file - self.topicInfo = topicInfo + self.custom = custom super.init() } @@ -296,11 +301,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { self.interactivelySelectedFromPackId = try container.decodeIfPresent(ItemCollectionId.self, forKey: .interactivelySelectedFromPackId) self.fileId = try container.decode(Int64.self, forKey: .fileId) self.file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) - if let topicId = try container.decodeIfPresent(Int64.self, forKey: .topicId), let topicInfo = try container.decodeIfPresent(EngineMessageHistoryThread.Info.self, forKey: .topicInfo) { - self.topicInfo = (topicId, topicInfo) - } else { - self.topicInfo = nil - } + self.custom = nil } public func encode(to encoder: Encoder) throws { @@ -308,16 +309,11 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable { try container.encodeIfPresent(self.interactivelySelectedFromPackId, forKey: .interactivelySelectedFromPackId) try container.encode(self.fileId, forKey: .fileId) try container.encodeIfPresent(self.file, forKey: .file) - if let (topicId, topicInfo) = self.topicInfo { - try container.encode(topicId, forKey: .topicId) - try container.encode(topicInfo, forKey: .topicInfo) - } } override public func isEqual(_ object: Any?) -> Bool { if let other = object as? ChatTextInputTextCustomEmojiAttribute { return self === other - //return self.stickerPack == other.stickerPack && self.fileId == other.fileId && self.file?.fileId == other.file?.fileId } else { return false } diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index 26dfa5c535..d63b3b5c3e 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -15,7 +15,7 @@ public enum UndoOverlayContent { case info(title: String?, text: String, timeout: Double?, customUndoText: String?) case emoji(name: String, text: String) case swipeToReply(title: String, text: String) - case actionSucceeded(title: String, text: String, cancel: String, destructive: Bool) + case actionSucceeded(title: String?, text: String, cancel: String?, destructive: Bool) case stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext) case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?) case chatAddedToFolder(chatTitle: String, folderTitle: String) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 17a66b8128..865ca15d61 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -262,10 +262,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural) - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + if let title { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + } self.textNode.attributedText = attributedText - displayUndo = true - undoText = cancel + if let cancel { + displayUndo = true + undoText = cancel + } else { + displayUndo = false + } self.originalRemainingSeconds = 5 case let .linkCopied(text): self.avatarNode = nil @@ -1341,7 +1347,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural) - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + if let title { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + } self.textNode.attributedText = attributedText default: break