Emoji in chat folders

This commit is contained in:
Isaac 2024-12-25 00:17:19 +08:00
parent 1d28e11879
commit 4bed1703a2
59 changed files with 962 additions and 389 deletions

View File

@ -13521,3 +13521,17 @@ Sorry for the inconvenience.";
"PeerInfo.VerificationInfo.Channel" = "This channel is verified as official by the representatives of Telegram."; "PeerInfo.VerificationInfo.Channel" = "This channel is verified as official by the representatives of Telegram.";
"PeerInfo.VerificationInfo.Group" = "This group is verified as official by the representatives of Telegram."; "PeerInfo.VerificationInfo.Group" = "This group is verified as official by the representatives of Telegram.";
"PeerInfo.VerificationInfo.URL" = "https://telegram.org/verify"; "PeerInfo.VerificationInfo.URL" = "https://telegram.org/verify";
"ChatList.ToastFolderMutedV2" = "All chats in {folder} are now muted";
"ChatList.ToastFolderUnmutedV2" = "All chats in {folder} are now unmuted";
"ChatList.AddedToFolderTooltipV2" = "{chat} has been added to folder {folder}";
"ChatList.RemovedFromFolderTooltipV2" = "{chat} has been removed from folder {folder}";
"FolderLinkScreen.TitleDescriptionDeselectedV2" = "Anyone with this link can add {folder} folder and the chats selected below.";
"FolderLinkScreen.TitleDescriptionSelectedV2" = "Anyone with this link can add {folder} folder and {chats} selected below.";
"Chat.NextChannelFolderSwipeProgressV2" = "Swipe up to go to the {folder} folder";
"Chat.NextChannelFolderSwipeActionV2" = "Release to go to the {folder} folder";
"FolderLinkPreview.TextAddChatsV2" = "Do you want to add {chats} to the\nfolder {folder}?";
"FolderLinkPreview.ToastLeftTitleV2" = "Folder {folder} deleted";
"FolderLinkPreview.ToastChatsAddedTitleV2" = "Folder {folder} Updated";
"FolderLinkPreview.ToastFolderAddedTitleV2" = "Folder {folder} Added";

View File

@ -413,7 +413,7 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
case monospace case monospace
case textMention(EnginePeer.Id) case textMention(EnginePeer.Id)
case textUrl(String) case textUrl(String)
case customEmoji(stickerPack: StickerPackReference?, fileId: Int64) case customEmoji(stickerPack: StickerPackReference?, fileId: Int64, enableAnimation: Bool)
case strikethrough case strikethrough
case underline case underline
case spoiler case spoiler
@ -440,7 +440,8 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
case 5: case 5:
let stickerPack = try container.decodeIfPresent(StickerPackReference.self, forKey: "s") let stickerPack = try container.decodeIfPresent(StickerPackReference.self, forKey: "s")
let fileId = try container.decode(Int64.self, forKey: "f") let fileId = try container.decode(Int64.self, forKey: "f")
self = .customEmoji(stickerPack: stickerPack, fileId: fileId) let enableAnimation = try container.decodeIfPresent(Bool.self, forKey: "ea") ?? true
self = .customEmoji(stickerPack: stickerPack, fileId: fileId, enableAnimation: enableAnimation)
case 6: case 6:
self = .strikethrough self = .strikethrough
case 7: case 7:
@ -474,10 +475,11 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
case let .textUrl(url): case let .textUrl(url):
try container.encode(4 as Int32, forKey: "t") try container.encode(4 as Int32, forKey: "t")
try container.encode(url, forKey: "url") try container.encode(url, forKey: "url")
case let .customEmoji(stickerPack, fileId): case let .customEmoji(stickerPack, fileId, enableAnimation):
try container.encode(5 as Int32, forKey: "t") try container.encode(5 as Int32, forKey: "t")
try container.encodeIfPresent(stickerPack, forKey: "s") try container.encodeIfPresent(stickerPack, forKey: "s")
try container.encode(fileId, forKey: "f") try container.encode(fileId, forKey: "f")
try container.encode(enableAnimation, forKey: "ea")
case .strikethrough: case .strikethrough:
try container.encode(6 as Int32, forKey: "t") try container.encode(6 as Int32, forKey: "t")
case .underline: case .underline:
@ -560,7 +562,7 @@ public struct ChatTextInputStateText: Codable, Equatable {
} else if key == ChatTextInputAttributes.textUrl, let value = value as? ChatTextInputTextUrlAttribute { } else if key == ChatTextInputAttributes.textUrl, let value = value as? ChatTextInputTextUrlAttribute {
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .textUrl(value.url), range: range.location ..< (range.location + range.length))) parsedAttributes.append(ChatTextInputStateTextAttribute(type: .textUrl(value.url), range: range.location ..< (range.location + range.length)))
} else if key == ChatTextInputAttributes.customEmoji, let value = value as? ChatTextInputTextCustomEmojiAttribute { } else if key == ChatTextInputAttributes.customEmoji, let value = value as? ChatTextInputTextCustomEmojiAttribute {
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: value.fileId), range: range.location ..< (range.location + range.length))) parsedAttributes.append(ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: value.fileId, enableAnimation: value.enableAnimation), range: range.location ..< (range.location + range.length)))
} else if key == ChatTextInputAttributes.strikethrough { } else if key == ChatTextInputAttributes.strikethrough {
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .strikethrough, range: range.location ..< (range.location + range.length))) parsedAttributes.append(ChatTextInputStateTextAttribute(type: .strikethrough, range: range.location ..< (range.location + range.length)))
} else if key == ChatTextInputAttributes.underline { } else if key == ChatTextInputAttributes.underline {
@ -618,8 +620,8 @@ public struct ChatTextInputStateText: Codable, Equatable {
result.addAttribute(ChatTextInputAttributes.textMention, value: ChatTextInputTextMentionAttribute(peerId: id), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) result.addAttribute(ChatTextInputAttributes.textMention, value: ChatTextInputTextMentionAttribute(peerId: id), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
case let .textUrl(url): case let .textUrl(url):
result.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) result.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
case let .customEmoji(_, fileId): case let .customEmoji(_, fileId, enableAnimation):
result.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) result.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil, enableAnimation: enableAnimation), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
case .strikethrough: case .strikethrough:
result.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count)) result.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
case .underline: case .underline:
@ -1204,3 +1206,40 @@ public protocol ChatHistoryListNode: ListView {
var contentPositionChanged: (ListViewVisibleContentOffset) -> Void { get set } var contentPositionChanged: (ListViewVisibleContentOffset) -> Void { get set }
} }
public extension ChatFolderTitle {
init(attributedString: NSAttributedString, enableAnimations: Bool) {
let inputStateText = ChatTextInputStateText(attributedText: attributedString)
self.init(text: inputStateText.text, entities: inputStateText.attributes.compactMap { attribute -> MessageTextEntity? in
if case let .customEmoji(_, fileId, _) = attribute.type {
return MessageTextEntity(range: attribute.range, type: .CustomEmoji(stickerPack: nil, fileId: fileId))
}
return nil
}, enableAnimations: enableAnimations)
}
var rawAttributedString: NSAttributedString {
let inputStateText = ChatTextInputStateText(text: self.text, attributes: self.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
if case let .CustomEmoji(_, fileId) = entity.type {
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: self.enableAnimations), range: entity.range)
}
return nil
})
return inputStateText.attributedText()
}
func attributedString(attributes: [NSAttributedString.Key: Any]) -> NSAttributedString {
let result = NSMutableAttributedString(attributedString: self.rawAttributedString)
result.addAttributes(attributes, range: NSRange(location: 0, length: result.length))
return result
}
func attributedString(font: UIFont, textColor: UIColor) -> NSAttributedString {
let result = NSMutableAttributedString(attributedString: self.rawAttributedString)
result.addAttributes([
.font: font,
.foregroundColor: textColor
], range: NSRange(location: 0, length: result.length))
return result
}
}

View File

@ -103,6 +103,7 @@ swift_library(
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem", "//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen", "//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
"//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen", "//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen",
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController", "//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
"//submodules/TelegramUI/Components/TextFieldComponent", "//submodules/TelegramUI/Components/TextFieldComponent",

View File

@ -223,8 +223,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
} }
|> deliverOnMainQueue).startStandalone(completed: { |> deliverOnMainQueue).startStandalone(completed: {
c?.dismiss(completion: { c?.dismiss(completion: {
//TODO:release chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatRemovedFromFolder(context: context, chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.rawAttributedString), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatRemovedFromFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
return false return false
}), in: .current) }), in: .current)
}) })
@ -274,8 +273,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
} }
let filterType = chatListFilterType(data) let filterType = chatListFilterType(data)
//TODO:release updatedItems.append(.action(ContextMenuActionItem(text: title.text, entities: title.entities, enableEntityAnimations: title.enableAnimations, icon: { theme in
updatedItems.append(.action(ContextMenuActionItem(text: title.text, icon: { theme in
let imageName: String let imageName: String
switch filterType { switch filterType {
case .generic: case .generic:
@ -339,8 +337,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
} }
return filters return filters
}).startStandalone() }).startStandalone()
//TODO:release chatListController?.present(UndoOverlayController( presentationData: presentationData, content: .chatAddedToFolder(context: context, chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.rawAttributedString), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatAddedToFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
return false return false
}), in: .current) }), in: .current)
}) })
@ -348,7 +345,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
} }
} }
c?.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil, animated: true) c?.setItems(.single(ContextController.Items(content: .list(updatedItems), context: context)), minHeight: nil, animated: true)
}))) })))
items.append(.separator) items.append(.separator)
} }

View File

@ -51,6 +51,7 @@ import PeerInfoStoryGridScreen
import ArchiveInfoScreen import ArchiveInfoScreen
import BirthdayPickerScreen import BirthdayPickerScreen
import OldChannelsController import OldChannelsController
import TextFormat
private final class ContextControllerContentSourceImpl: ContextControllerContentSource { private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController let controller: ViewController
@ -1453,7 +1454,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
} }
let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) let contextController = ContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController) strongSelf.presentInGlobalOverlay(contextController)
dismissPreviewingImpl = { [weak contextController] in dismissPreviewingImpl = { [weak contextController] in
@ -1526,7 +1527,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
} }
let contextController = ContextController(presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) let contextController = ContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController) strongSelf.presentInGlobalOverlay(contextController)
} }
} }
@ -1811,28 +1812,42 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return return
} }
//TODO:release
let iconColor: UIColor = .white let iconColor: UIColor = .white
let overlayController: UndoOverlayController let overlayController: UndoOverlayController
if !filterPeersAreMuted.areMuted { if !filterPeersAreMuted.areMuted {
let text = strongSelf.presentationData.strings.ChatList_ToastFolderMuted(title.text).string let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderMutedV2)
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ let folderNameRange = (text.string as NSString).range(of: "{folder}")
if folderNameRange.location != NSNotFound {
text.replaceCharacters(in: folderNameRange, with: "")
text.insert(title.attributedString(attributes: [
ChatTextInputAttributes.bold: true
]), at: folderNameRange.location)
}
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universalWithEntities(context: strongSelf.context, animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor, "Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor, "Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor "Line.Group 1.Stroke 1": iconColor
], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
} else { } else {
//TODO:release let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderUnmutedV2)
let text = strongSelf.presentationData.strings.ChatList_ToastFolderUnmuted(title.text).string let folderNameRange = (text.string as NSString).range(of: "{folder}")
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ if folderNameRange.location != NSNotFound {
text.replaceCharacters(in: folderNameRange, with: "")
text.insert(title.attributedString(attributes: [
ChatTextInputAttributes.bold: true
]), at: folderNameRange.location)
}
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universalWithEntities(context: strongSelf.context, animation: "anim_profileunmute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor, "Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor, "Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor "Line.Group 1.Stroke 1": iconColor
], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }) ], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
} }
strongSelf.present(overlayController, in: .current) strongSelf.present(overlayController, in: .current)
}) })
@ -4736,7 +4751,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let text = strongSelf.presentationData.strings.ChatList_DeletedThreads(Int32(threadIds.count)) let text = strongSelf.presentationData.strings.ChatList_DeletedThreads(Int32(threadIds.count))
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: text), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return false return false
} }
@ -4842,7 +4857,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count)) let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count))
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: text), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return false return false
} }
@ -4914,7 +4929,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count)) let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count))
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: text), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return false return false
} }
@ -5274,7 +5289,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return true return true
}) })
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: strongSelf.presentationData.strings.Undo_ChatCleared, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: strongSelf.presentationData.strings.Undo_ChatCleared), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return false return false
} }
@ -5496,7 +5511,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let statusText = self.presentationData.strings.Undo_DeletedTopic let statusText = self.presentationData.strings.Undo_DeletedTopic
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
guard let self else { guard let self else {
return false return false
} }
@ -5793,7 +5808,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return true return true
}) })
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return false return false
} }
@ -5933,7 +5948,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
badge = ContextMenuActionBadge(value: "\(item.1)", color: item.2 ? .accent : .inactive) badge = ContextMenuActionBadge(value: "\(item.1)", color: item.2 ? .accent : .inactive)
} }
} }
//TODO:release
items.append(.action(ContextMenuActionItem(text: title.text, entities: title.entities, enableEntityAnimations: title.enableAnimations, badge: badge, icon: { theme in items.append(.action(ContextMenuActionItem(text: title.text, entities: title.entities, enableEntityAnimations: title.enableAnimations, badge: badge, icon: { theme in
let imageName: String let imageName: String
if isDisabled { if isDisabled {
@ -6347,9 +6361,9 @@ private final class ChatListLocationContext {
let peerView = Promise<PeerView>() let peerView = Promise<PeerView>()
peerView.set(context.account.viewTracker.peerView(peerId)) peerView.set(context.account.viewTracker.peerView(peerId))
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil) var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
let recentOnlineSignal: Signal<Int32?, NoError> = peerView.get() let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView.get()
|> map { view -> Bool? in |> map { view -> Bool? in
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
if case .broadcast = peer.info { if case .broadcast = peer.info {
@ -6364,17 +6378,21 @@ private final class ChatListLocationContext {
} }
} }
|> distinctUntilChanged |> distinctUntilChanged
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in |> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
if let isLarge = isLarge { if let isLarge = isLarge {
if isLarge { if isLarge {
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init) |> map { value -> (total: Int32?, recent: Int32?) in
return (nil, value)
}
} else { } else {
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init) |> map { value -> (total: Int32?, recent: Int32?) in
return (value.total, value.recent)
}
} }
} else { } else {
return .single(nil) return .single((nil, nil))
} }
} }
onlineMemberCount = recentOnlineSignal onlineMemberCount = recentOnlineSignal
@ -6783,7 +6801,7 @@ private final class ChatListLocationContext {
private func updateForum( private func updateForum(
peerId: EnginePeer.Id, peerId: EnginePeer.Id,
peerView: PeerView, peerView: PeerView,
onlineMemberCount: Int32?, onlineMemberCount: (total: Int32?, recent: Int32?),
stateAndFilterId: (state: ChatListNodeState, filterId: Int32?), stateAndFilterId: (state: ChatListNodeState, filterId: Int32?),
presentationData: PresentationData presentationData: PresentationData
) { ) {

View File

@ -35,6 +35,7 @@ private final class ChatListFilterPresetControllerArguments {
let context: AccountContext let context: AccountContext
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void
let updateName: (ChatFolderTitle) -> Void let updateName: (ChatFolderTitle) -> Void
let toggleNameInputMode: () -> Void
let toggleNameAnimations: () -> Void let toggleNameAnimations: () -> Void
let openAddIncludePeer: () -> Void let openAddIncludePeer: () -> Void
let openAddExcludePeer: () -> Void let openAddExcludePeer: () -> Void
@ -58,6 +59,7 @@ private final class ChatListFilterPresetControllerArguments {
context: AccountContext, context: AccountContext,
updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void, updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void,
updateName: @escaping (ChatFolderTitle) -> Void, updateName: @escaping (ChatFolderTitle) -> Void,
toggleNameInputMode: @escaping () -> Void,
toggleNameAnimations: @escaping () -> Void, toggleNameAnimations: @escaping () -> Void,
openAddIncludePeer: @escaping () -> Void, openAddIncludePeer: @escaping () -> Void,
openAddExcludePeer: @escaping () -> Void, openAddExcludePeer: @escaping () -> Void,
@ -80,6 +82,7 @@ private final class ChatListFilterPresetControllerArguments {
self.context = context self.context = context
self.updateState = updateState self.updateState = updateState
self.updateName = updateName self.updateName = updateName
self.toggleNameInputMode = toggleNameInputMode
self.toggleNameAnimations = toggleNameAnimations self.toggleNameAnimations = toggleNameAnimations
self.openAddIncludePeer = openAddIncludePeer self.openAddIncludePeer = openAddIncludePeer
self.openAddExcludePeer = openAddExcludePeer self.openAddExcludePeer = openAddExcludePeer
@ -228,7 +231,7 @@ private enum ChatListFilterRevealedItemId: Equatable {
private enum ChatListFilterPresetEntry: ItemListNodeEntry { private enum ChatListFilterPresetEntry: ItemListNodeEntry {
case screenHeader case screenHeader
case nameHeader(title: String, enableAnimations: Bool) case nameHeader(title: String, enableAnimations: Bool?)
case name(placeholder: String, value: NSAttributedString, inputMode: ListComposePollOptionComponent.InputMode?, enableAnimations: Bool) case name(placeholder: String, value: NSAttributedString, inputMode: ListComposePollOptionComponent.InputMode?, enableAnimations: Bool)
case includePeersHeader(String) case includePeersHeader(String)
case addIncludePeer(title: String) case addIncludePeer(title: String)
@ -376,7 +379,11 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: "", animation: .newFolder, sectionId: self.section) return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: "", animation: .newFolder, sectionId: self.section)
case let .nameHeader(title, enableAnimations): case let .nameHeader(title, enableAnimations):
//TODO:localize //TODO:localize
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, actionText: enableAnimations ? "Disable Animations" : "Enable Animations", action: { var actionText: String?
if let enableAnimations {
actionText = enableAnimations ? "Disable Animations" : "Enable Animations"
}
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, actionText: actionText, action: {
arguments.toggleNameAnimations() arguments.toggleNameAnimations()
}, sectionId: self.section) }, sectionId: self.section)
case let .name(placeholder, value, inputMode, enableAnimations): case let .name(placeholder, value, inputMode, enableAnimations):
@ -393,15 +400,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.updateName(ChatFolderTitle(attributedString: value, enableAnimations: true)) arguments.updateName(ChatFolderTitle(attributedString: value, enableAnimations: true))
}, },
toggleInputMode: { toggleInputMode: {
arguments.updateState { current in arguments.toggleNameInputMode()
var state = current
if state.nameInputMode == .emoji {
state.nameInputMode = .keyboard
} else {
state.nameInputMode = .emoji
}
return state
}
} }
) )
case .includePeersHeader(let text), .excludePeersHeader(let text): case .includePeersHeader(let text), .excludePeersHeader(let text):
@ -545,37 +544,6 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
} }
} }
extension ChatFolderTitle {
init(attributedString: NSAttributedString, enableAnimations: Bool) {
let inputStateText = ChatTextInputStateText(attributedText: attributedString)
self.init(text: inputStateText.text, entities: inputStateText.attributes.compactMap { attribute -> MessageTextEntity? in
if case let .customEmoji(_, fileId) = attribute.type {
return MessageTextEntity(range: attribute.range, type: .CustomEmoji(stickerPack: nil, fileId: fileId))
}
return nil
}, enableAnimations: enableAnimations)
}
var rawAttributedString: NSAttributedString {
let inputStateText = ChatTextInputStateText(text: self.text, attributes: self.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
if case let .CustomEmoji(_, fileId) = entity.type {
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId), range: entity.range)
}
return nil
})
return inputStateText.attributedText()
}
func attributedString(font: UIFont, textColor: UIColor) -> NSAttributedString {
let result = NSMutableAttributedString(attributedString: self.rawAttributedString)
result.addAttributes([
.font: font,
.foregroundColor: textColor
], range: NSRange(location: 0, length: result.length))
return result
}
}
private struct ChatListFilterPresetControllerState: Equatable { private struct ChatListFilterPresetControllerState: Equatable {
var name: ChatFolderTitle var name: ChatFolderTitle
var changedName: Bool var changedName: Bool
@ -624,7 +592,7 @@ private func chatListFilterPresetControllerEntries(context: AccountContext, pres
entries.append(.screenHeader) entries.append(.screenHeader)
} }
entries.append(.nameHeader(title: presentationData.strings.ChatListFolder_NameSectionHeader, enableAnimations: state.name.enableAnimations)) entries.append(.nameHeader(title: presentationData.strings.ChatListFolder_NameSectionHeader, enableAnimations: state.name.entities.isEmpty ? nil : state.name.enableAnimations))
entries.append(.name(placeholder: presentationData.strings.ChatListFolder_NamePlaceholder, value: state.name.rawAttributedString, inputMode: state.nameInputMode, enableAnimations: state.name.enableAnimations)) entries.append(.name(placeholder: presentationData.strings.ChatListFolder_NamePlaceholder, value: state.name.rawAttributedString, inputMode: state.nameInputMode, enableAnimations: state.name.enableAnimations))
entries.append(.includePeersHeader(presentationData.strings.ChatListFolder_IncludedSectionHeader)) entries.append(.includePeersHeader(presentationData.strings.ChatListFolder_IncludedSectionHeader))
@ -1584,6 +1552,18 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
} }
} }
}, },
toggleNameInputMode: {
updateState { current in
var state = current
if state.nameInputMode == .emoji {
state.nameInputMode = .keyboard
} else {
state.nameInputMode = .emoji
}
return state
}
focusOnNameImpl?()
},
toggleNameAnimations: { toggleNameAnimations: {
updateState { current in updateState { current in
var name = current.name var name = current.name
@ -2145,7 +2125,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
return return
} }
controller.forEachItemNode { itemNode in controller.forEachItemNode { itemNode in
if let itemNode = itemNode as? ItemListSingleLineInputItemNode { if let itemNode = itemNode as? ItemListFilterTitleInputItemNode {
itemNode.focus() itemNode.focus()
} }
} }

View File

@ -16,7 +16,7 @@ import ChatFolderLinkPreviewScreen
private final class ChatListFilterPresetListControllerArguments { private final class ChatListFilterPresetListControllerArguments {
let context: AccountContext let context: AccountContext
let addSuggestedPressed: (String, ChatListFilterData) -> Void let addSuggestedPressed: (ChatFolderTitle, ChatListFilterData) -> Void
let openPreset: (ChatListFilter) -> Void let openPreset: (ChatListFilter) -> Void
let addNew: () -> Void let addNew: () -> Void
let setItemWithRevealedOptions: (Int32?, Int32?) -> Void let setItemWithRevealedOptions: (Int32?, Int32?) -> Void
@ -24,7 +24,7 @@ private final class ChatListFilterPresetListControllerArguments {
let updateDisplayTags: (Bool) -> Void let updateDisplayTags: (Bool) -> Void
let updateDisplayTagsLocked: () -> Void let updateDisplayTagsLocked: () -> Void
init(context: AccountContext, addSuggestedPressed: @escaping (String, ChatListFilterData) -> Void, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void, updateDisplayTags: @escaping (Bool) -> Void, updateDisplayTagsLocked: @escaping () -> Void) { init(context: AccountContext, addSuggestedPressed: @escaping (ChatFolderTitle, ChatListFilterData) -> Void, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void, updateDisplayTags: @escaping (Bool) -> Void, updateDisplayTagsLocked: @escaping () -> Void) {
self.context = context self.context = context
self.addSuggestedPressed = addSuggestedPressed self.addSuggestedPressed = addSuggestedPressed
self.openPreset = openPreset self.openPreset = openPreset
@ -92,10 +92,10 @@ private struct PresetIndex: Equatable {
private enum ChatListFilterPresetListEntry: ItemListNodeEntry { private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
case screenHeader(String) case screenHeader(String)
case suggestedListHeader(String) case suggestedListHeader(String)
case suggestedPreset(index: PresetIndex, title: String, label: String, preset: ChatListFilterData) case suggestedPreset(index: PresetIndex, title: ChatFolderTitle, label: String, preset: ChatListFilterData)
case suggestedAddCustom(String) case suggestedAddCustom(String)
case listHeader(String) case listHeader(String)
case preset(index: PresetIndex, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool, isAllChats: Bool, isDisabled: Bool, displayTags: Bool) case preset(index: PresetIndex, title: ChatFolderTitle, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool, isAllChats: Bool, isDisabled: Bool, displayTags: Bool)
case addItem(text: String, isEditing: Bool) case addItem(text: String, isEditing: Bool)
case listFooter(String) case listFooter(String)
case displayTags(Bool?) case displayTags(Bool?)
@ -176,7 +176,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
case let .suggestedListHeader(text): case let .suggestedListHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .suggestedPreset(_, title, label, preset): case let .suggestedPreset(_, title, label, preset):
return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, title: title, label: label, sectionId: self.section, style: .blocks, installAction: { return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: {
arguments.addSuggestedPressed(title, preset) arguments.addSuggestedPressed(title, preset)
}, tag: nil) }, tag: nil)
case let .suggestedAddCustom(text): case let .suggestedAddCustom(text):
@ -194,7 +194,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
} }
} }
return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title, label: label, tagColor: resolvedColor, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: { return ChatListFilterPresetListItem(context: arguments.context, presentationData: presentationData, preset: preset, title: title, label: label, tagColor: resolvedColor, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: {
if isDisabled { if isDisabled {
arguments.addNew() arguments.addNew()
} else { } else {
@ -285,12 +285,11 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
var folderCount = 0 var folderCount = 0
for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) { for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) {
if case .allChats = filter { if case .allChats = filter {
entries.append(.preset(index: PresetIndex(value: entries.count), title: "", label: "", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: false, isEditing: state.isEditing, isAllChats: true, isDisabled: false, displayTags: effectiveDisplayTags == true)) entries.append(.preset(index: PresetIndex(value: entries.count), title: ChatFolderTitle(text: "", entities: [], enableAnimations: true), label: "", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: false, isEditing: state.isEditing, isAllChats: true, isDisabled: false, displayTags: effectiveDisplayTags == true))
} }
if case let .filter(_, title, _, _) = filter { if case let .filter(_, title, _, _) = filter {
folderCount += 1 folderCount += 1
//TODO:release entries.append(.preset(index: PresetIndex(value: entries.count), title: title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing, isAllChats: false, isDisabled: !isPremium && folderCount > limits.maxFoldersCount, displayTags: effectiveDisplayTags == true))
entries.append(.preset(index: PresetIndex(value: entries.count), title: title.text, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing, isAllChats: false, isDisabled: !isPremium && folderCount > limits.maxFoldersCount, displayTags: effectiveDisplayTags == true))
} }
} }
@ -300,8 +299,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
if !filteredSuggestedFilters.isEmpty && actualFilters.count < limits.maxFoldersCount { if !filteredSuggestedFilters.isEmpty && actualFilters.count < limits.maxFoldersCount {
entries.append(.suggestedListHeader(presentationData.strings.ChatListFolderSettings_RecommendedFoldersSection)) entries.append(.suggestedListHeader(presentationData.strings.ChatListFolderSettings_RecommendedFoldersSection))
for filter in filteredSuggestedFilters { for filter in filteredSuggestedFilters {
//TODO:release entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title, label: filter.description, preset: filter.data))
entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title.text, label: filter.description, preset: filter.data))
} }
if filters.isEmpty { if filters.isEmpty {
entries.append(.suggestedAddCustom(presentationData.strings.ChatListFolderSettings_RecommendedNewFolder)) entries.append(.suggestedAddCustom(presentationData.strings.ChatListFolderSettings_RecommendedNewFolder))
@ -389,8 +387,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
var filters = filters var filters = filters
let id = context.engine.peers.generateNewChatListFilterId(filters: filters) let id = context.engine.peers.generateNewChatListFilterId(filters: filters)
//TODO:release filters.append(.filter(id: id, title: title, emoticon: nil, data: data))
filters.append(.filter(id: id, title: ChatFolderTitle(text: title, entities: [], enableAnimations: true), emoticon: nil, data: data))
return filters return filters
} }
|> deliverOnMainQueue).start(next: { _ in |> deliverOnMainQueue).start(next: { _ in

View File

@ -7,6 +7,8 @@ import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import ItemListUI import ItemListUI
import TelegramUIPreferences import TelegramUIPreferences
import AccountContext
import TextNodeWithEntities
struct ChatListFilterPresetListItemEditing: Equatable { struct ChatListFilterPresetListItemEditing: Equatable {
let editable: Bool let editable: Bool
@ -15,9 +17,10 @@ struct ChatListFilterPresetListItemEditing: Equatable {
} }
final class ChatListFilterPresetListItem: ListViewItem, ItemListItem { final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
let context: AccountContext
let presentationData: ItemListPresentationData let presentationData: ItemListPresentationData
let preset: ChatListFilter let preset: ChatListFilter
let title: String let title: ChatFolderTitle
let label: String let label: String
let tagColor: UIColor? let tagColor: UIColor?
let editing: ChatListFilterPresetListItemEditing let editing: ChatListFilterPresetListItemEditing
@ -31,9 +34,10 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
let remove: () -> Void let remove: () -> Void
init( init(
context: AccountContext,
presentationData: ItemListPresentationData, presentationData: ItemListPresentationData,
preset: ChatListFilter, preset: ChatListFilter,
title: String, title: ChatFolderTitle,
label: String, label: String,
tagColor: UIColor?, tagColor: UIColor?,
editing: ChatListFilterPresetListItemEditing, editing: ChatListFilterPresetListItemEditing,
@ -46,6 +50,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void,
remove: @escaping () -> Void remove: @escaping () -> Void
) { ) {
self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.preset = preset self.preset = preset
self.title = title self.title = title
@ -124,7 +129,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
return self.containerNode return self.containerNode
} }
private let titleNode: TextNode private let titleNode: TextNodeWithEntities
private let labelNode: TextNode private let labelNode: TextNode
private let arrowNode: ASImageNode private let arrowNode: ASImageNode
private let sharedIconNode: ASImageNode private let sharedIconNode: ASImageNode
@ -145,6 +150,15 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
return true return true
} }
override var visibility: ListViewItemNodeVisibility {
didSet {
if self.visibility != oldValue {
let enableAnimations = self.item?.title.enableAnimations ?? true
self.titleNode.visibilityRect = (self.visibility == ListViewItemNodeVisibility.none || !enableAnimations) ? CGRect.zero : CGRect.infinite
}
}
}
init() { init() {
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true self.backgroundNode.isLayerBacked = true
@ -160,10 +174,11 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
self.maskNode = ASImageNode() self.maskNode = ASImageNode()
self.maskNode.isUserInteractionEnabled = false self.maskNode.isUserInteractionEnabled = false
self.titleNode = TextNode() self.titleNode = TextNodeWithEntities()
self.titleNode.isUserInteractionEnabled = false self.titleNode.textNode.isUserInteractionEnabled = false
self.titleNode.contentMode = .left self.titleNode.textNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale self.titleNode.textNode.contentsScale = UIScreen.main.scale
self.titleNode.resetEmojiToFirstFrameAutomatically = true
self.labelNode = TextNode() self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false self.labelNode.isUserInteractionEnabled = false
@ -186,7 +201,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.titleNode) self.containerNode.addSubnode(self.titleNode.textNode)
self.containerNode.addSubnode(self.labelNode) self.containerNode.addSubnode(self.labelNode)
self.containerNode.addSubnode(self.arrowNode) self.containerNode.addSubnode(self.arrowNode)
self.containerNode.addSubnode(self.sharedIconNode) self.containerNode.addSubnode(self.sharedIconNode)
@ -199,7 +214,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
} }
func asyncLayout() -> (_ item: ChatListFilterPresetListItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { func asyncLayout() -> (_ item: ChatListFilterPresetListItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNodeWithEntities.asyncLayout(self.titleNode)
let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode) let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode) let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
@ -230,7 +245,11 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
} }
let titleAttributedString = NSMutableAttributedString() let titleAttributedString = NSMutableAttributedString()
titleAttributedString.append(NSAttributedString(string: item.isAllChats ? item.presentationData.strings.ChatList_FolderAllChats : item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)) if item.isAllChats {
titleAttributedString.append(NSAttributedString(string: item.presentationData.strings.ChatList_FolderAllChats, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor))
} else {
titleAttributedString.append(item.title.attributedString(font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor))
}
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)? var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)? var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
@ -337,9 +356,18 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
} }
strongSelf.editableControlNode?.isHidden = !item.canBeDeleted strongSelf.editableControlNode?.isHidden = !item.canBeDeleted
let _ = titleApply() let _ = titleApply(TextNodeWithEntities.Arguments(
context: item.context,
cache: item.context.animationCache,
renderer: item.context.animationRenderer,
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor,
attemptSynchronous: true
))
let _ = labelApply() let _ = labelApply()
let enableAnimations = item.title.enableAnimations
strongSelf.titleNode.visibilityRect = (strongSelf.visibility == ListViewItemNodeVisibility.none || !enableAnimations) ? CGRect.zero : CGRect.infinite
if strongSelf.backgroundNode.supernode == nil { if strongSelf.backgroundNode.supernode == nil {
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
} }
@ -385,7 +413,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))) transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size)) transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size))
let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: 11.0), size: labelLayout.size) let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: 11.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame strongSelf.labelNode.frame = labelFrame
@ -542,7 +570,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
editingOffset = 0.0 editingOffset = 0.0
} }
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + offset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size)) transition.updateFrame(node: self.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + offset + editingOffset, y: self.titleNode.textNode.frame.minY), size: self.titleNode.textNode.bounds.size))
var labelFrame = self.labelNode.frame var labelFrame = self.labelNode.frame
labelFrame.origin.x = params.width - rightArrowInset - labelFrame.width + revealOffset labelFrame.origin.x = params.width - rightArrowInset - labelFrame.width + revealOffset

View File

@ -109,11 +109,13 @@ private final class ItemNode: ASDisplayNode {
self.titleNode = ImmediateTextNodeWithEntities() self.titleNode = ImmediateTextNodeWithEntities()
self.titleNode.displaysAsynchronously = false self.titleNode.displaysAsynchronously = false
self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
self.titleNode.resetEmojiToFirstFrameAutomatically = true
self.titleActiveNode = ImmediateTextNodeWithEntities() self.titleActiveNode = ImmediateTextNodeWithEntities()
self.titleActiveNode.displaysAsynchronously = false self.titleActiveNode.displaysAsynchronously = false
self.titleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) self.titleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
self.titleActiveNode.alpha = 0.0 self.titleActiveNode.alpha = 0.0
self.titleActiveNode.resetEmojiToFirstFrameAutomatically = true
self.shortTitleContainer = ASDisplayNode() self.shortTitleContainer = ASDisplayNode()
@ -121,12 +123,14 @@ private final class ItemNode: ASDisplayNode {
self.shortTitleNode.displaysAsynchronously = false self.shortTitleNode.displaysAsynchronously = false
self.shortTitleNode.alpha = 0.0 self.shortTitleNode.alpha = 0.0
self.shortTitleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) self.shortTitleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
self.shortTitleNode.resetEmojiToFirstFrameAutomatically = true
self.shortTitleActiveNode = ImmediateTextNodeWithEntities() self.shortTitleActiveNode = ImmediateTextNodeWithEntities()
self.shortTitleActiveNode.displaysAsynchronously = false self.shortTitleActiveNode.displaysAsynchronously = false
self.shortTitleActiveNode.alpha = 0.0 self.shortTitleActiveNode.alpha = 0.0
self.shortTitleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0) self.shortTitleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
self.shortTitleActiveNode.alpha = 0.0 self.shortTitleActiveNode.alpha = 0.0
self.shortTitleActiveNode.resetEmojiToFirstFrameAutomatically = true
self.badgeContainerNode = ASDisplayNode() self.badgeContainerNode = ASDisplayNode()

View File

@ -239,7 +239,8 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
cache: item.context.animationCache, cache: item.context.animationCache,
renderer: item.context.animationRenderer, renderer: item.context.animationRenderer,
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor,
attemptSynchronous: true attemptSynchronous: true,
emojiOffset: CGPoint(x: 0.0, y: -1.0)
)) ))
let badgeSideInset: CGFloat = 4.0 let badgeSideInset: CGFloat = 4.0
let badgeBackgroundSize: CGSize let badgeBackgroundSize: CGSize
@ -248,7 +249,7 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
} else { } else {
badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + badgeLayoutAndApply.0.size.width, height: badgeLayoutAndApply.0.size.height + 3.0) badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + badgeLayoutAndApply.0.size.width, height: badgeLayoutAndApply.0.size.height + 3.0)
} }
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + badgeSpacing, y: strongSelf.titleNode.frame.minY - UIScreenPixel + floorToScreenPixels((strongSelf.titleNode.bounds.height - badgeBackgroundSize.height) * 0.5)), size: badgeBackgroundSize) let badgeBackgroundFrame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + badgeSpacing, y: strongSelf.titleNode.frame.minY + floorToScreenPixels((strongSelf.titleNode.bounds.height - badgeBackgroundSize.height) * 0.5)), size: badgeBackgroundSize)
let badgeBackgroundLayer: SimpleLayer let badgeBackgroundLayer: SimpleLayer
if let current = strongSelf.badgeBackgroundLayer { if let current = strongSelf.badgeBackgroundLayer {
@ -262,6 +263,7 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
if strongSelf.badgeTextNode !== badgeTextNode { if strongSelf.badgeTextNode !== badgeTextNode {
strongSelf.badgeTextNode?.textNode.removeFromSupernode() strongSelf.badgeTextNode?.textNode.removeFromSupernode()
strongSelf.badgeTextNode = badgeTextNode strongSelf.badgeTextNode = badgeTextNode
badgeTextNode.resetEmojiToFirstFrameAutomatically = true
strongSelf.addSubnode(badgeTextNode.textNode) strongSelf.addSubnode(badgeTextNode.textNode)
} }

View File

@ -197,12 +197,12 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
} else if case let .user(user) = primaryPeer { } else if case let .user(user) = primaryPeer {
let servicePeer = isServicePeer(primaryPeer._asPeer()) let servicePeer = isServicePeer(primaryPeer._asPeer())
if user.flags.contains(.isSupport) && !servicePeer { if user.flags.contains(.isSupport) && !servicePeer {
status = .custom(string: strings.Bot_GenericSupportStatus, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Bot_GenericSupportStatus), multiline: false, isActive: false, icon: nil)
} else if let _ = user.botInfo { } else if let _ = user.botInfo {
if let subscriberCount = user.subscriberCount { if let subscriberCount = user.subscriberCount {
status = .custom(string: strings.Conversation_StatusBotSubscribers(subscriberCount), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Conversation_StatusBotSubscribers(subscriberCount)), multiline: false, isActive: false, icon: nil)
} else { } else {
status = .custom(string: strings.Bot_GenericBotStatus, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Bot_GenericBotStatus), multiline: false, isActive: false, icon: nil)
} }
} else if user.id != context.account.peerId && !servicePeer { } else if user.id != context.account.peerId && !servicePeer {
let presence = peer.presence ?? TelegramUserPresence(status: .none, lastActivity: 0) let presence = peer.presence ?? TelegramUserPresence(status: .none, lastActivity: 0)
@ -211,19 +211,19 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
status = .none status = .none
} }
} else if case let .legacyGroup(group) = primaryPeer { } else if case let .legacyGroup(group) = primaryPeer {
status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(group.participantCount)), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.GroupInfo_ParticipantCount(Int32(group.participantCount))), multiline: false, isActive: false, icon: nil)
} else if case let .channel(channel) = primaryPeer { } else if case let .channel(channel) = primaryPeer {
if case .group = channel.info { if case .group = channel.info {
if let count = peer.subpeerSummary?.count, count > 0 { if let count = peer.subpeerSummary?.count, count > 0 {
status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(count)), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.GroupInfo_ParticipantCount(Int32(count))), multiline: false, isActive: false, icon: nil)
} else { } else {
status = .custom(string: strings.Group_Status, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Group_Status), multiline: false, isActive: false, icon: nil)
} }
} else { } else {
if let count = peer.subpeerSummary?.count, count > 0 { if let count = peer.subpeerSummary?.count, count > 0 {
status = .custom(string: strings.Conversation_StatusSubscribers(Int32(count)), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Conversation_StatusSubscribers(Int32(count))), multiline: false, isActive: false, icon: nil)
} else { } else {
status = .custom(string: strings.Channel_Status, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Channel_Status), multiline: false, isActive: false, icon: nil)
} }
} }
} else { } else {
@ -870,9 +870,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
var status: ContactsPeerItemStatus = .none var status: ContactsPeerItemStatus = .none
if case let .user(user) = primaryPeer, let _ = user.botInfo, !primaryPeer.id.isVerificationCodes { if case let .user(user) = primaryPeer, let _ = user.botInfo, !primaryPeer.id.isVerificationCodes {
if let subscriberCount = user.subscriberCount { if let subscriberCount = user.subscriberCount {
status = .custom(string: presentationData.strings.Conversation_StatusBotSubscribers(subscriberCount), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: presentationData.strings.Conversation_StatusBotSubscribers(subscriberCount)), multiline: false, isActive: false, icon: nil)
} else { } else {
status = .custom(string: presentationData.strings.Bot_GenericBotStatus, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: presentationData.strings.Bot_GenericBotStatus), multiline: false, isActive: false, icon: nil)
} }
} }

View File

@ -238,6 +238,7 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
backspaceKeyAction: nil, backspaceKeyAction: nil,
selection: nil, selection: nil,
inputMode: item.inputMode, inputMode: item.inputMode,
alwaysDisplayInputModeSelector: true,
toggleInputMode: { [weak self] in toggleInputMode: { [weak self] in
guard let self else { guard let self else {
return return
@ -268,6 +269,9 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
} }
public func focus() { public func focus() {
if let textFieldView = self.textField.view as? ListComposePollOptionComponent.View {
textFieldView.activateInput()
}
} }
public func selectAll() { public func selectAll() {

View File

@ -28,6 +28,7 @@ import EmojiStatusComponent
import AvatarVideoNode import AvatarVideoNode
import AppBundle import AppBundle
import MultilineTextComponent import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import ShimmerEffect import ShimmerEffect
public enum ChatListItemContent { public enum ChatListItemContent {
@ -82,10 +83,10 @@ public enum ChatListItemContent {
public struct Tag: Equatable { public struct Tag: Equatable {
public var id: Int32 public var id: Int32
public var title: String public var title: ChatFolderTitle
public var colorId: Int32 public var colorId: Int32
public init(id: Int32, title: String, colorId: Int32) { public init(id: Int32, title: ChatFolderTitle, colorId: Int32) {
self.id = id self.id = id
self.title = title self.title = title
self.colorId = colorId self.colorId = colorId
@ -269,6 +270,8 @@ private final class ChatListItemTagListComponent: Component {
let backgroundView: UIImageView let backgroundView: UIImageView
let title = ComponentView<Empty>() let title = ComponentView<Empty>()
private var currentTitle: ChatFolderTitle?
override init(frame: CGRect) { override init(frame: CGRect) {
self.backgroundView = UIImageView(image: tagBackgroundImage) self.backgroundView = UIImageView(image: tagBackgroundImage)
@ -281,11 +284,20 @@ private final class ChatListItemTagListComponent: Component {
preconditionFailure() preconditionFailure()
} }
func update(context: AccountContext, title: String, backgroundColor: UIColor, foregroundColor: UIColor, sizeFactor: CGFloat) -> CGSize { func update(context: AccountContext, title: ChatFolderTitle, backgroundColor: UIColor, foregroundColor: UIColor, sizeFactor: CGFloat) -> CGSize {
self.currentTitle = title
let titleValue = ChatFolderTitle(text: title.text.isEmpty ? " " : title.text, entities: title.entities, enableAnimations: title.enableAnimations)
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextWithEntitiesComponent(
text: .plain(NSAttributedString(string: title.isEmpty ? " " : title, font: Font.semibold(floor(11.0 * sizeFactor)), textColor: foregroundColor)) context: context,
animationCache: context.animationCache,
animationRenderer: context.animationRenderer,
placeholderColor: foregroundColor.withMultipliedAlpha(0.1),
text: .plain(titleValue.attributedString(font: Font.semibold(floor(11.0 * sizeFactor)), textColor: foregroundColor)),
manualVisibilityControl: true,
resetAnimationsOnVisibilityChange: true
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: 100.0, height: 100.0) containerSize: CGSize(width: 100.0, height: 100.0)
@ -309,11 +321,30 @@ private final class ChatListItemTagListComponent: Component {
return backgroundSize return backgroundSize
} }
func updateVisibility(_ isVisible: Bool) {
guard let currentTitle = self.currentTitle else {
return
}
if let titleView = self.title.view as? MultilineTextWithEntitiesComponent.View {
titleView.updateVisibility(isVisible && currentTitle.enableAnimations)
}
}
} }
final class View: UIView { final class View: UIView {
private var itemViews: [Int32: ItemView] = [:] private var itemViews: [Int32: ItemView] = [:]
var isVisible: Bool = false {
didSet {
if self.isVisible != oldValue {
for (_, itemView) in self.itemViews {
itemView.updateVisibility(self.isVisible)
}
}
}
}
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
} }
@ -332,13 +363,13 @@ private final class ChatListItemTagListComponent: Component {
} }
let itemId: Int32 let itemId: Int32
let itemTitle: String let itemTitle: ChatFolderTitle
let itemBackgroundColor: UIColor let itemBackgroundColor: UIColor
let itemForegroundColor: UIColor let itemForegroundColor: UIColor
if validIds.count >= 3 { if validIds.count >= 3 {
itemId = Int32.max itemId = Int32.max
itemTitle = "+\(component.tags.count - validIds.count)" itemTitle = ChatFolderTitle(text: "+\(component.tags.count - validIds.count)", entities: [], enableAnimations: true)
itemForegroundColor = component.theme.chatList.dateTextColor itemForegroundColor = component.theme.chatList.dateTextColor
itemBackgroundColor = itemForegroundColor.withMultipliedAlpha(0.1) itemBackgroundColor = itemForegroundColor.withMultipliedAlpha(0.1)
} else { } else {
@ -347,7 +378,7 @@ private final class ChatListItemTagListComponent: Component {
let tagColor = PeerNameColor(rawValue: tag.colorId) let tagColor = PeerNameColor(rawValue: tag.colorId)
let resolvedColor = component.context.peerNameColors.getChatFolderTag(tagColor, dark: component.theme.overallDarkAppearance) let resolvedColor = component.context.peerNameColors.getChatFolderTag(tagColor, dark: component.theme.overallDarkAppearance)
itemTitle = tag.title.uppercased() itemTitle = ChatFolderTitle(text: tag.title.text.uppercased(), entities: tag.title.entities, enableAnimations: tag.title.enableAnimations)
itemBackgroundColor = resolvedColor.main.withMultipliedAlpha(0.1) itemBackgroundColor = resolvedColor.main.withMultipliedAlpha(0.1)
itemForegroundColor = resolvedColor.main itemForegroundColor = resolvedColor.main
} }
@ -364,6 +395,7 @@ private final class ChatListItemTagListComponent: Component {
let itemSize = itemView.update(context: component.context, title: itemTitle, backgroundColor: itemBackgroundColor, foregroundColor: itemForegroundColor, sizeFactor: component.sizeFactor) let itemSize = itemView.update(context: component.context, title: itemTitle, backgroundColor: itemBackgroundColor, foregroundColor: itemForegroundColor, sizeFactor: component.sizeFactor)
let itemFrame = CGRect(origin: CGPoint(x: nextX, y: 0.0), size: itemSize) let itemFrame = CGRect(origin: CGPoint(x: nextX, y: 0.0), size: itemSize)
itemView.frame = itemFrame itemView.frame = itemFrame
itemView.updateVisibility(self.isVisible)
validIds.append(itemId) validIds.append(itemId)
nextX += itemSize.width nextX += itemSize.width
@ -1451,6 +1483,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
) )
} }
self.authorNode.visibilityStatus = self.visibilityStatus self.authorNode.visibilityStatus = self.visibilityStatus
if let itemTagListView = self.itemTagList?.view as? ChatListItemTagListComponent.View {
itemTagListView.isVisible = self.visibilityStatus
}
} }
} }
} }
@ -4243,13 +4279,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
environment: {}, environment: {},
containerSize: itemTagListFrame.size containerSize: itemTagListFrame.size
) )
if let itemTagListView = itemTagList.view { if let itemTagListView = itemTagList.view as? ChatListItemTagListComponent.View {
if itemTagListView.superview == nil { if itemTagListView.superview == nil {
itemTagListView.isUserInteractionEnabled = false itemTagListView.isUserInteractionEnabled = false
strongSelf.mainContentContainerNode.view.addSubview(itemTagListView) strongSelf.mainContentContainerNode.view.addSubview(itemTagListView)
} }
itemTagListTransition.updateFrame(view: itemTagListView, frame: itemTagListFrame) itemTagListTransition.updateFrame(view: itemTagListView, frame: itemTagListFrame)
itemTagListView.isVisible = strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji
} }
} else { } else {
if let itemTagList = strongSelf.itemTagList { if let itemTagList = strongSelf.itemTagList {

View File

@ -4276,33 +4276,32 @@ public final class ChatListNode: ListView {
} }
} }
private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: PresentationStrings, peer: EnginePeer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, autoremoveTimeout: Int32?) -> (String, Bool, Bool, ContactsPeerItemStatus.Icon?)? { private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: PresentationStrings, peer: EnginePeer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, autoremoveTimeout: Int32?) -> (NSAttributedString, Bool, Bool, ContactsPeerItemStatus.Icon?)? {
if accountPeerId == peer.id { if accountPeerId == peer.id {
return nil return nil
} }
if displayAutoremoveTimeout { if displayAutoremoveTimeout {
if let autoremoveTimeout = autoremoveTimeout { if let autoremoveTimeout = autoremoveTimeout {
return (strings.ChatList_LabelAutodeleteAfter(timeIntervalString(strings: strings, value: autoremoveTimeout, usage: .afterTime)).string, false, true, .autoremove) return (NSAttributedString(string: strings.ChatList_LabelAutodeleteAfter(timeIntervalString(strings: strings, value: autoremoveTimeout, usage: .afterTime)).string), false, true, .autoremove)
} else { } else {
return (strings.ChatList_LabelAutodeleteDisabled, false, false, .autoremove) return (NSAttributedString(string: strings.ChatList_LabelAutodeleteDisabled), false, false, .autoremove)
} }
} }
if let chatListFilters = chatListFilters { if let chatListFilters = chatListFilters {
var result = "" let result = NSMutableAttributedString(string: "")
for case let .filter(_, title, _, data) in chatListFilters { for case let .filter(_, title, _, data) in chatListFilters {
let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId) let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) { if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
if !result.isEmpty { if result.length != 0 {
result.append(", ") result.append(NSAttributedString(string: ", "))
} }
//TODO:release result.append(title.rawAttributedString)
result.append(title.text)
} }
} }
if result.isEmpty { if result.length == 0 {
return nil return nil
} else { } else {
return (result, true, false, nil) return (result, true, false, nil)
@ -4313,30 +4312,30 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres
return nil return nil
} else if case let .user(user) = peer { } else if case let .user(user) = peer {
if user.botInfo != nil || user.flags.contains(.isSupport) { if user.botInfo != nil || user.flags.contains(.isSupport) {
return (strings.ChatList_PeerTypeBot, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeBot), false, false, nil)
} else { } else {
if isContact { if isContact {
return (strings.ChatList_PeerTypeContact, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeContact), false, false, nil)
} else { } else {
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeNonContactUser), false, false, nil)
} }
} }
} else if case .secretChat = peer { } else if case .secretChat = peer {
if isContact { if isContact {
return (strings.ChatList_PeerTypeContact, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeContact), false, false, nil)
} else { } else {
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeNonContactUser), false, false, nil)
} }
} else if case .legacyGroup = peer { } else if case .legacyGroup = peer {
return (strings.ChatList_PeerTypeGroup, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeGroup), false, false, nil)
} else if case let .channel(channel) = peer { } else if case let .channel(channel) = peer {
if case .group = channel.info { if case .group = channel.info {
return (strings.ChatList_PeerTypeGroup, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeGroup), false, false, nil)
} else { } else {
return (strings.ChatList_PeerTypeChannel, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeChannel), false, false, nil)
} }
} }
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil) return (NSAttributedString(string: strings.ChatList_PeerTypeNonContactUser), false, false, nil)
} }
public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer { public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
@ -4425,10 +4424,9 @@ func chatListItemTags(location: ChatListControllerLocation, accountPeerId: Engin
if data.color != nil { if data.color != nil {
let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId) let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
if predicate.pinnedPeerIds.contains(peer.id) || predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) { if predicate.pinnedPeerIds.contains(peer.id) || predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
//TODO:release
result.append(ChatListItemContent.Tag( result.append(ChatListItemContent.Tag(
id: id, id: id,
title: title.text, title: title,
colorId: data.color?.rawValue ?? PeerNameColor.blue.rawValue colorId: data.color?.rawValue ?? PeerNameColor.blue.rawValue
)) ))
} }

View File

@ -31,6 +31,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
public let textStroke: (UIColor, CGFloat)? public let textStroke: (UIColor, CGFloat)?
public let highlightColor: UIColor? public let highlightColor: UIColor?
public let handleSpoilers: Bool public let handleSpoilers: Bool
public let manualVisibilityControl: Bool
public let resetAnimationsOnVisibilityChange: Bool
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
@ -52,6 +54,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
textStroke: (UIColor, CGFloat)? = nil, textStroke: (UIColor, CGFloat)? = nil,
highlightColor: UIColor? = nil, highlightColor: UIColor? = nil,
handleSpoilers: Bool = false, handleSpoilers: Bool = false,
manualVisibilityControl: Bool = false,
resetAnimationsOnVisibilityChange: Bool = false,
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil, highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil, tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil,
longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil
@ -73,6 +77,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
self.highlightColor = highlightColor self.highlightColor = highlightColor
self.highlightAction = highlightAction self.highlightAction = highlightAction
self.handleSpoilers = handleSpoilers self.handleSpoilers = handleSpoilers
self.manualVisibilityControl = manualVisibilityControl
self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange
self.tapAction = tapAction self.tapAction = tapAction
self.longTapAction = longTapAction self.longTapAction = longTapAction
} }
@ -105,6 +111,12 @@ public final class MultilineTextWithEntitiesComponent: Component {
if lhs.handleSpoilers != rhs.handleSpoilers { if lhs.handleSpoilers != rhs.handleSpoilers {
return false return false
} }
if lhs.manualVisibilityControl != rhs.manualVisibilityControl {
return false
}
if lhs.resetAnimationsOnVisibilityChange != rhs.resetAnimationsOnVisibilityChange {
return false
}
if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor { if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor {
if !lhsTextShadowColor.isEqual(rhsTextShadowColor) { if !lhsTextShadowColor.isEqual(rhsTextShadowColor) {
return false return false
@ -151,6 +163,10 @@ public final class MultilineTextWithEntitiesComponent: Component {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
public func updateVisibility(_ isVisible: Bool) {
self.textNode.visibility = isVisible
}
public func update(component: MultilineTextWithEntitiesComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize { public func update(component: MultilineTextWithEntitiesComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
let attributedString: NSAttributedString let attributedString: NSAttributedString
switch component.text { switch component.text {
@ -176,6 +192,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
self.textNode.highlightAttributeAction = component.highlightAction self.textNode.highlightAttributeAction = component.highlightAction
self.textNode.tapAttributeAction = component.tapAction self.textNode.tapAttributeAction = component.tapAction
self.textNode.longTapAttributeAction = component.longTapAction self.textNode.longTapAttributeAction = component.longTapAction
self.textNode.resetEmojiToFirstFrameAutomatically = component.resetAnimationsOnVisibilityChange
if case let .curve(duration, _) = transition.animation, let previousText = previousText, previousText != attributedString.string { if case let .curve(duration, _) = transition.animation, let previousText = previousText, previousText != attributedString.string {
if let snapshotView = self.snapshotContentTree() { if let snapshotView = self.snapshotContentTree() {
@ -189,7 +207,9 @@ public final class MultilineTextWithEntitiesComponent: Component {
} }
} }
self.textNode.visibility = true if !component.manualVisibilityControl {
self.textNode.visibility = true
}
if let context = component.context, let animationCache = component.animationCache, let animationRenderer = component.animationRenderer, let placeholderColor = component.placeholderColor { if let context = component.context, let animationCache = component.animationCache, let animationRenderer = component.animationRenderer, let placeholderColor = component.placeholderColor {
self.textNode.arguments = TextNodeWithEntities.Arguments( self.textNode.arguments = TextNodeWithEntities.Arguments(
context: context, context: context,

View File

@ -82,6 +82,7 @@ public final class ListComposePollOptionComponent: Component {
public let backspaceKeyAction: (() -> Void)? public let backspaceKeyAction: (() -> Void)?
public let selection: Selection? public let selection: Selection?
public let inputMode: InputMode? public let inputMode: InputMode?
public let alwaysDisplayInputModeSelector: Bool
public let toggleInputMode: (() -> Void)? public let toggleInputMode: (() -> Void)?
public let tag: AnyObject? public let tag: AnyObject?
@ -100,6 +101,7 @@ public final class ListComposePollOptionComponent: Component {
backspaceKeyAction: (() -> Void)?, backspaceKeyAction: (() -> Void)?,
selection: Selection?, selection: Selection?,
inputMode: InputMode?, inputMode: InputMode?,
alwaysDisplayInputModeSelector: Bool = false,
toggleInputMode: (() -> Void)?, toggleInputMode: (() -> Void)?,
tag: AnyObject? = nil tag: AnyObject? = nil
) { ) {
@ -117,6 +119,7 @@ public final class ListComposePollOptionComponent: Component {
self.backspaceKeyAction = backspaceKeyAction self.backspaceKeyAction = backspaceKeyAction
self.selection = selection self.selection = selection
self.inputMode = inputMode self.inputMode = inputMode
self.alwaysDisplayInputModeSelector = alwaysDisplayInputModeSelector
self.toggleInputMode = toggleInputMode self.toggleInputMode = toggleInputMode
self.tag = tag self.tag = tag
} }
@ -158,6 +161,9 @@ public final class ListComposePollOptionComponent: Component {
if lhs.inputMode != rhs.inputMode { if lhs.inputMode != rhs.inputMode {
return false return false
} }
if lhs.alwaysDisplayInputModeSelector != rhs.alwaysDisplayInputModeSelector {
return false
}
return true return true
} }
@ -490,15 +496,17 @@ public final class ListComposePollOptionComponent: Component {
ComponentTransition.immediate.setScale(view: modeSelectorView, scale: 0.001) ComponentTransition.immediate.setScale(view: modeSelectorView, scale: 0.001)
} }
if playAnimation, let animationView = modeSelectorView.contentView as? LottieComponent.View { if let animationView = modeSelectorView.contentView as? LottieComponent.View {
animationView.playOnce() if playAnimation {
animationView.playOnce()
}
} }
modeSelectorTransition.setPosition(view: modeSelectorView, position: modeSelectorFrame.center) modeSelectorTransition.setPosition(view: modeSelectorView, position: modeSelectorFrame.center)
modeSelectorTransition.setBounds(view: modeSelectorView, bounds: CGRect(origin: CGPoint(), size: modeSelectorFrame.size)) modeSelectorTransition.setBounds(view: modeSelectorView, bounds: CGRect(origin: CGPoint(), size: modeSelectorFrame.size))
if let externalState = component.externalState { if let externalState = component.externalState {
let displaySelector = externalState.isEditing let displaySelector = externalState.isEditing || component.alwaysDisplayInputModeSelector
alphaTransition.setAlpha(view: modeSelectorView, alpha: displaySelector ? 1.0 : 0.0) alphaTransition.setAlpha(view: modeSelectorView, alpha: displaySelector ? 1.0 : 0.0)
alphaTransition.setScale(view: modeSelectorView, scale: displaySelector ? 1.0 : 0.001) alphaTransition.setScale(view: modeSelectorView, scale: displaySelector ? 1.0 : 0.001)

View File

@ -158,19 +158,19 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
if let _ = peer as? TelegramUser { if let _ = peer as? TelegramUser {
status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat) status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat)
} else if let group = peer as? TelegramGroup { } else if let group = peer as? TelegramGroup {
status = .custom(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount))), multiline: false, isActive: false, icon: nil)
} else if let channel = peer as? TelegramChannel { } else if let channel = peer as? TelegramChannel {
if case .group = channel.info { if case .group = channel.info {
if let participantCount = participantCount, participantCount != 0 { if let participantCount = participantCount, participantCount != 0 {
status = .custom(string: strings.Conversation_StatusMembers(participantCount), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(participantCount)), multiline: false, isActive: false, icon: nil)
} else { } else {
status = .custom(string: strings.Group_Status, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Group_Status), multiline: false, isActive: false, icon: nil)
} }
} else { } else {
if let participantCount = participantCount, participantCount != 0 { if let participantCount = participantCount, participantCount != 0 {
status = .custom(string: strings.Conversation_StatusSubscribers(participantCount), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Conversation_StatusSubscribers(participantCount)), multiline: false, isActive: false, icon: nil)
} else { } else {
status = .custom(string: strings.Channel_Status, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Channel_Status), multiline: false, isActive: false, icon: nil)
} }
} }
} else { } else {
@ -220,7 +220,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
let text: String let text: String
text = presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyData.count)) text = presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyData.count))
status = .custom(string: text, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: text), multiline: false, isActive: false, icon: nil)
} }
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in

View File

@ -674,7 +674,7 @@ public class ContactsController: ViewController {
let text = self.presentationData.strings.ContactList_DeletedContacts(Int32(peerIds.count)) let text = self.presentationData.strings.ContactList_DeletedContacts(Int32(peerIds.count))
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: text), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
guard let self else { guard let self else {
return false return false
} }

View File

@ -52,7 +52,7 @@ private enum InviteContactsEntry: Comparable, Identifiable {
case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder): case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder):
let status: ContactsPeerItemStatus let status: ContactsPeerItemStatus
if count != 0 { if count != 0 {
status = .custom(string: strings.Contacts_ImportersCount(count), multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: strings.Contacts_ImportersCount(count)), multiline: false, isActive: false, icon: nil)
} else { } else {
status = .none status = .none
} }

View File

@ -32,6 +32,8 @@ swift_library(
"//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/MoreButtonNode", "//submodules/MoreButtonNode",
"//submodules/TextFormat",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -21,6 +21,8 @@ import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import EmojiStatusComponent import EmojiStatusComponent
import MoreButtonNode import MoreButtonNode
import TextFormat
import TextNodeWithEntities
public final class ContactItemHighlighting { public final class ContactItemHighlighting {
public var chatLocation: ChatLocation? public var chatLocation: ChatLocation?
@ -39,7 +41,7 @@ public enum ContactsPeerItemStatus {
case none case none
case presence(EnginePeer.Presence, PresentationDateTimeFormat) case presence(EnginePeer.Presence, PresentationDateTimeFormat)
case addressName(String) case addressName(String)
case custom(string: String, multiline: Bool, isActive: Bool, icon: Icon?) case custom(string: NSAttributedString, multiline: Bool, isActive: Bool, icon: Icon?)
} }
public enum ContactsPeerItemSelection: Equatable { public enum ContactsPeerItemSelection: Equatable {
@ -437,7 +439,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
private var credibilityIconComponent: EmojiStatusComponent? private var credibilityIconComponent: EmojiStatusComponent?
private var verifiedIconView: ComponentHostView<Empty>? private var verifiedIconView: ComponentHostView<Empty>?
private var verifiedIconComponent: EmojiStatusComponent? private var verifiedIconComponent: EmojiStatusComponent?
public let statusNode: TextNode public let statusNode: TextNodeWithEntities
private var statusIconNode: ASImageNode? private var statusIconNode: ASImageNode?
private var badgeBackgroundNode: ASImageNode? private var badgeBackgroundNode: ASImageNode?
private var badgeTextNode: TextNode? private var badgeTextNode: TextNode?
@ -519,6 +521,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
containerSize: avatarIconView.bounds.size containerSize: avatarIconView.bounds.size
) )
} }
self.statusNode.visibilityRect = self.visibilityStatus == false ? CGRect.zero : CGRect.infinite
} }
} }
} }
@ -554,7 +557,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
self.avatarNode.isLayerBacked = false self.avatarNode.isLayerBacked = false
self.titleNode = TextNode() self.titleNode = TextNode()
self.statusNode = TextNode() self.statusNode = TextNodeWithEntities()
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false) super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
@ -575,7 +578,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
self.avatarNodeContainer.addSubnode(self.avatarNode) self.avatarNodeContainer.addSubnode(self.avatarNode)
self.offsetContainerNode.addSubnode(self.avatarNodeContainer) self.offsetContainerNode.addSubnode(self.avatarNodeContainer)
self.offsetContainerNode.addSubnode(self.titleNode) self.offsetContainerNode.addSubnode(self.titleNode)
self.offsetContainerNode.addSubnode(self.statusNode) self.offsetContainerNode.addSubnode(self.statusNode.textNode)
self.addSubnode(self.maskNode) self.addSubnode(self.maskNode)
@ -708,7 +711,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
public func asyncLayout() -> (_ item: ContactsPeerItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (Bool, Bool) -> Void)) { public func asyncLayout() -> (_ item: ContactsPeerItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (Bool, Bool) -> Void)) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeStatusLayout = TextNode.asyncLayout(self.statusNode) let makeStatusLayout = TextNodeWithEntities.asyncLayout(self.statusNode)
let currentSelectionNode = self.selectionNode let currentSelectionNode = self.selectionNode
let makeBadgeTextLayout = TextNode.asyncLayout(self.badgeTextNode) let makeBadgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
@ -939,7 +942,24 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
statusAttributedString = NSAttributedString(string: suffix, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) statusAttributedString = NSAttributedString(string: suffix, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
} }
case let .custom(text, multiline, isActive, icon): case let .custom(text, multiline, isActive, icon):
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor) let statusAttributedStringValue = NSMutableAttributedString(string: text.string)
statusAttributedStringValue.addAttribute(.font, value: statusFont, range: NSRange(location: 0, length: statusAttributedStringValue.length))
statusAttributedStringValue.addAttribute(.foregroundColor, value: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor, range: NSRange(location: 0, length: statusAttributedStringValue.length))
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
for (key, value) in attributes {
if key == ChatTextInputAttributes.bold {
statusAttributedStringValue.addAttribute(.font, value: Font.semibold(14.0), range: range)
} else if key == ChatTextInputAttributes.italic {
statusAttributedStringValue.addAttribute(.font, value: Font.italic(14.0), range: range)
} else if key == ChatTextInputAttributes.monospace {
statusAttributedStringValue.addAttribute(.font, value: Font.monospace(14.0), range: range)
} else {
statusAttributedStringValue.addAttribute(key, value: value, range: range)
}
}
})
statusAttributedString = statusAttributedStringValue
statusIcon = icon statusIcon = icon
statusIsActive = isActive statusIsActive = isActive
multilineStatus = multiline multilineStatus = multiline
@ -964,7 +984,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
switch item.status { switch item.status {
case let .custom(text, multiline, isActive, icon): case let .custom(text, multiline, isActive, icon):
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor) let statusAttributedStringValue = NSMutableAttributedString(string: "")
statusAttributedStringValue.addAttribute(.font, value: statusFont, range: NSRange(location: 0, length: text.length))
statusAttributedStringValue.addAttribute(.foregroundColor, value: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor, range: NSRange(location: 0, length: text.length))
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
for (key, value) in attributes {
if key == ChatTextInputAttributes.bold {
statusAttributedStringValue.addAttribute(.font, value: Font.semibold(14.0), range: range)
} else if key == ChatTextInputAttributes.italic {
statusAttributedStringValue.addAttribute(.font, value: Font.italic(14.0), range: range)
} else if key == ChatTextInputAttributes.monospace {
statusAttributedStringValue.addAttribute(.font, value: Font.monospace(14.0), range: range)
} else {
statusAttributedStringValue.addAttribute(key, value: value, range: range)
}
}
})
statusAttributedString = statusAttributedStringValue
multilineStatus = multiline multilineStatus = multiline
statusIsActive = isActive statusIsActive = isActive
statusIcon = icon statusIcon = icon
@ -1395,17 +1431,24 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame) transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
strongSelf.titleNode.alpha = item.enabled ? 1.0 : 0.4 strongSelf.titleNode.alpha = item.enabled ? 1.0 : 0.4
strongSelf.statusNode.alpha = item.enabled ? 1.0 : 1.0 strongSelf.statusNode.textNode.alpha = item.enabled ? 1.0 : 1.0
let _ = statusApply() strongSelf.statusNode.visibilityRect = strongSelf.visibilityStatus == false ? CGRect.zero : CGRect.infinite
let _ = statusApply(TextNodeWithEntities.Arguments(
context: item.context,
cache: item.context.animationCache,
renderer: item.context.animationRenderer,
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor,
attemptSynchronous: false
))
var statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY - 1.0), size: statusLayout.size) var statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY - 1.0), size: statusLayout.size)
if let statusIconImage { if let statusIconImage {
statusFrame.origin.x += statusIconImage.size.width + 1.0 statusFrame.origin.x += statusIconImage.size.width + 1.0
} }
let previousStatusFrame = strongSelf.statusNode.frame let previousStatusFrame = strongSelf.statusNode.textNode.frame
strongSelf.statusNode.frame = statusFrame strongSelf.statusNode.textNode.frame = statusFrame
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0)) transition.animatePositionAdditive(node: strongSelf.statusNode.textNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
if let statusIconImage { if let statusIconImage {
let statusIconNode: ASImageNode let statusIconNode: ASImageNode
@ -1413,7 +1456,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
statusIconNode = current statusIconNode = current
} else { } else {
statusIconNode = ASImageNode() statusIconNode = ASImageNode()
strongSelf.statusNode.addSubnode(statusIconNode) strongSelf.statusNode.textNode.addSubnode(statusIconNode)
} }
statusIconNode.image = statusIconImage statusIconNode.image = statusIconImage
statusIconNode.frame = CGRect(origin: CGPoint(x: -statusIconImage.size.width - 1.0, y: floor((statusFrame.height - statusIconImage.size.height) / 2.0) + 1.0), size: statusIconImage.size) statusIconNode.frame = CGRect(origin: CGPoint(x: -statusIconImage.size.width - 1.0, y: floor((statusFrame.height - statusIconImage.size.height) / 2.0) + 1.0), size: statusIconImage.size)

View File

@ -317,7 +317,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
if !self.item.entities.isEmpty { if !self.item.entities.isEmpty {
let inputStateText = ChatTextInputStateText(text: self.item.text, attributes: self.item.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in let inputStateText = ChatTextInputStateText(text: self.item.text, attributes: self.item.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
if case let .CustomEmoji(_, fileId) = entity.type { if case let .CustomEmoji(_, fileId) = entity.type {
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId), range: entity.range) return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: true), range: entity.range)
} }
return nil return nil
}) })

View File

@ -62,7 +62,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case peer(EnginePeer.Id) case peer(EnginePeer.Id)
} }
case header(String) case header(NSAttributedString)
case mainLinkHeader(String) case mainLinkHeader(String)
case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool) case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool)
@ -225,13 +225,13 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
private func folderInviteLinkListControllerEntries( private func folderInviteLinkListControllerEntries(
presentationData: PresentationData, presentationData: PresentationData,
state: FolderInviteLinkListControllerState, state: FolderInviteLinkListControllerState,
title: String, title: ChatFolderTitle,
allPeers: [EnginePeer] allPeers: [EnginePeer]
) -> [InviteLinksListEntry] { ) -> [InviteLinksListEntry] {
var entries: [InviteLinksListEntry] = [] var entries: [InviteLinksListEntry] = []
var infoString: String? var infoString: String?
let chatCountString: String let chatCountString: NSAttributedString
let peersHeaderString: String let peersHeaderString: String
let canShareChats = !allPeers.allSatisfy({ !canShareLinkToPeer(peer: $0) }) let canShareChats = !allPeers.allSatisfy({ !canShareLinkToPeer(peer: $0) })
@ -241,16 +241,36 @@ private func folderInviteLinkListControllerEntries(
if !canShareChats { if !canShareChats {
infoString = presentationData.strings.FolderLinkScreen_TitleDescriptionUnavailable infoString = presentationData.strings.FolderLinkScreen_TitleDescriptionUnavailable
chatCountString = presentationData.strings.FolderLinkScreen_ChatCountHeaderUnavailable chatCountString = NSAttributedString(string: presentationData.strings.FolderLinkScreen_ChatCountHeaderUnavailable)
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderUnavailable peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderUnavailable
} else if state.selectedPeerIds.isEmpty { } else if state.selectedPeerIds.isEmpty {
chatCountString = presentationData.strings.FolderLinkScreen_TitleDescriptionDeselected(title).string let chatCountStringValue = NSMutableAttributedString(string: presentationData.strings.FolderLinkScreen_TitleDescriptionDeselectedV2)
let folderRange = (chatCountStringValue.string as NSString).range(of: "{folder}")
if folderRange.location != NSNotFound {
chatCountStringValue.replaceCharacters(in: folderRange, with: "")
chatCountStringValue.insert(title.rawAttributedString, at: folderRange.location)
}
chatCountString = chatCountStringValue
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeader peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeader
if allPeers.count > 1 { if allPeers.count > 1 {
selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
} }
} else { } else {
chatCountString = presentationData.strings.FolderLinkScreen_TitleDescriptionSelected(title, presentationData.strings.FolderLinkScreen_TitleDescriptionSelectedCount(Int32(state.selectedPeerIds.count))).string let chatCountStringValue = NSMutableAttributedString(string: presentationData.strings.FolderLinkScreen_TitleDescriptionSelectedV2)
let folderRange = (chatCountStringValue.string as NSString).range(of: "{folder}")
if folderRange.location != NSNotFound {
chatCountStringValue.replaceCharacters(in: folderRange, with: "")
chatCountStringValue.insert(title.rawAttributedString, at: folderRange.location)
}
let chatsRange = (chatCountStringValue.string as NSString).range(of: "{chats}")
if chatsRange.location != NSNotFound {
chatCountStringValue.replaceCharacters(in: chatsRange, with: "")
let countValue = presentationData.strings.FolderLinkScreen_TitleDescriptionSelectedCount(Int32(state.selectedPeerIds.count))
chatCountStringValue.insert(NSAttributedString(string: countValue), at: chatsRange.location)
}
chatCountString = chatCountStringValue
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderSelected(Int32(state.selectedPeerIds.count)) peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderSelected(Int32(state.selectedPeerIds.count))
if allPeers.count > 1 { if allPeers.count > 1 {
selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
@ -699,11 +719,10 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: doneButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: doneButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
//TODO:release
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: folderInviteLinkListControllerEntries( let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: folderInviteLinkListControllerEntries(
presentationData: presentationData, presentationData: presentationData,
state: state, state: state,
title: filterTitle.text, title: filterTitle,
allPeers: allPeers allPeers: allPeers
), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges) ), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges)

View File

@ -11,18 +11,19 @@ import TelegramAnimatedStickerNode
import AccountContext import AccountContext
import Markdown import Markdown
import TextFormat import TextFormat
import TextNodeWithEntities
public class InviteLinkHeaderItem: ListViewItem, ItemListItem { public class InviteLinkHeaderItem: ListViewItem, ItemListItem {
public let context: AccountContext public let context: AccountContext
public let theme: PresentationTheme public let theme: PresentationTheme
public let title: String? public let title: String?
public let text: String public let text: NSAttributedString
public let animationName: String public let animationName: String
public let hideOnSmallScreens: Bool public let hideOnSmallScreens: Bool
public let sectionId: ItemListSectionId public let sectionId: ItemListSectionId
public let linkAction: ((ItemListTextItemLinkAction) -> Void)? public let linkAction: ((ItemListTextItemLinkAction) -> Void)?
public init(context: AccountContext, theme: PresentationTheme, title: String? = nil, text: String, animationName: String, hideOnSmallScreens: Bool = false, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil) { public init(context: AccountContext, theme: PresentationTheme, title: String? = nil, text: NSAttributedString, animationName: String, hideOnSmallScreens: Bool = false, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.title = title self.title = title
@ -75,7 +76,7 @@ private let textFont = Font.regular(14.0)
class InviteLinkHeaderItemNode: ListViewItemNode { class InviteLinkHeaderItemNode: ListViewItemNode {
private let titleNode: TextNode private let titleNode: TextNode
private let textNode: TextNode private let textNode: TextNodeWithEntities
private var animationNode: AnimatedStickerNode private var animationNode: AnimatedStickerNode
private var item: InviteLinkHeaderItem? private var item: InviteLinkHeaderItem?
@ -86,17 +87,17 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
self.titleNode.contentMode = .left self.titleNode.contentMode = .left
self.titleNode.contentsScale = UIScreen.main.scale self.titleNode.contentsScale = UIScreen.main.scale
self.textNode = TextNode() self.textNode = TextNodeWithEntities()
self.textNode.isUserInteractionEnabled = false self.textNode.textNode.isUserInteractionEnabled = false
self.textNode.contentMode = .left self.textNode.textNode.contentMode = .left
self.textNode.contentsScale = UIScreen.main.scale self.textNode.textNode.contentsScale = UIScreen.main.scale
self.animationNode = DefaultAnimatedStickerNodeImpl() self.animationNode = DefaultAnimatedStickerNodeImpl()
super.init(layerBacked: false, dynamicBounce: false) super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.textNode) self.addSubnode(self.textNode.textNode)
self.addSubnode(self.animationNode) self.addSubnode(self.animationNode)
} }
@ -112,7 +113,7 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
func asyncLayout() -> (_ item: InviteLinkHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { func asyncLayout() -> (_ item: InviteLinkHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode)
return { item, params, neighbors in return { item, params, neighbors in
let leftInset: CGFloat = 24.0 + params.leftInset let leftInset: CGFloat = 24.0 + params.leftInset
@ -131,9 +132,22 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
let attributedTitle = NSAttributedString(string: item.title ?? "", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) let attributedTitle = NSAttributedString(string: item.title ?? "", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: item.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: Font.semibold(14.0), textColor: item.theme.list.freeTextColor), link: MarkdownAttributeSet(font: textFont, textColor: item.theme.list.itemAccentColor), linkAttribute: { contents in let attributedText = NSMutableAttributedString(string: item.text.string)
return (TelegramTextAttributes.URL, contents) attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: attributedText.length))
})) attributedText.addAttribute(.foregroundColor, value: item.theme.list.freeTextColor, range: NSRange(location: 0, length: attributedText.length))
item.text.enumerateAttributes(in: NSRange(location: 0, length: item.text.length), using: { attributes, range, _ in
for (key, value) in attributes {
if key == ChatTextInputAttributes.bold {
attributedText.addAttribute(.font, value: Font.semibold(14.0), range: range)
} else if key == ChatTextInputAttributes.italic {
attributedText.addAttribute(.font, value: Font.italic(14.0), range: range)
} else if key == ChatTextInputAttributes.monospace {
attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range)
} else {
attributedText.addAttribute(key, value: value, range: range)
}
}
})
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
@ -168,8 +182,15 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
origin += titleLayout.size.height + spacing origin += titleLayout.size.height + spacing
} }
let _ = textApply() let _ = textApply(TextNodeWithEntities.Arguments(
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: origin), size: textLayout.size) context: item.context,
cache: item.context.animationCache,
renderer: item.context.animationRenderer,
placeholderColor: item.theme.list.mediaPlaceholderColor,
attemptSynchronous: true
))
strongSelf.textNode.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: origin), size: textLayout.size)
strongSelf.textNode.visibilityRect = .infinite
} }
}) })
} }
@ -189,9 +210,9 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture { switch gesture {
case .tap: case .tap:
let textFrame = self.textNode.frame let textFrame = self.textNode.textNode.frame
if let item = self.item, textFrame.contains(location) { if let item = self.item, textFrame.contains(location) {
if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: location.x - textFrame.minX, y: location.y - textFrame.minY)) { if let (_, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: location.x - textFrame.minX, y: location.y - textFrame.minY)) {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
item.linkAction?(.tap(url)) item.linkAction?(.tap(url))
} }

View File

@ -56,7 +56,7 @@ private enum InviteLinksListSection: Int32 {
} }
private enum InviteLinksListEntry: ItemListNodeEntry { private enum InviteLinksListEntry: ItemListNodeEntry {
case header(PresentationTheme, String) case header(PresentationTheme, NSAttributedString)
case mainLinkHeader(PresentationTheme, String) case mainLinkHeader(PresentationTheme, String)
case mainLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool) case mainLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool)
@ -278,7 +278,7 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
} else { } else {
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
} }
entries.append(.header(presentationData.theme, helpText)) entries.append(.header(presentationData.theme, NSAttributedString(string: helpText)))
} }
let mainInvite: ExportedInvitation? let mainInvite: ExportedInvitation?

View File

@ -100,7 +100,7 @@ private enum InviteRequestsEntry: ItemListNodeEntry {
let arguments = arguments as! InviteRequestsControllerArguments let arguments = arguments as! InviteRequestsControllerArguments
switch self { switch self {
case let .header(theme, text): case let .header(theme, text):
return InviteLinkHeaderItem(context: arguments.context, theme: theme, text: text, animationName: "Requests", sectionId: self.section, linkAction: { _ in return InviteLinkHeaderItem(context: arguments.context, theme: theme, text: NSAttributedString(string: text), animationName: "Requests", sectionId: self.section, linkAction: { _ in
arguments.openLinks() arguments.openLinks()
}) })
case let .requestsHeader(_, text): case let .requestsHeader(_, text):

View File

@ -112,7 +112,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
let arguments = arguments as! PaymentMethodListScreenArguments let arguments = arguments as! PaymentMethodListScreenArguments
switch self { switch self {
case let .header(text): case let .header(text):
return InviteLinkHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animationName: "Invite", sectionId: self.section) return InviteLinkHeaderItem(context: arguments.context, theme: presentationData.theme, text: NSAttributedString(string: text), animationName: "Invite", sectionId: self.section)
case let .methodsHeader(text): case let .methodsHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .addMethod(text): case let .addMethod(text):

View File

@ -158,7 +158,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable {
case let .participant(participant, label, revealActions, revealed, enabled): case let .participant(participant, label, revealActions, revealed, enabled):
let status: ContactsPeerItemStatus let status: ContactsPeerItemStatus
if let label = label { if let label = label {
status = .custom(string: label, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: label), multiline: false, isActive: false, icon: nil)
} else if let presence = participant.presences[participant.peer.id], self.addIcon { } else if let presence = participant.presences[participant.peer.id], self.addIcon {
status = .presence(EnginePeer.Presence(presence), dateTimeFormat) status = .presence(EnginePeer.Presence(presence), dateTimeFormat)
} else { } else {

View File

@ -129,7 +129,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
case let .peer(_, participant, editing, label, enabled, isChannel, isContact): case let .peer(_, participant, editing, label, enabled, isChannel, isContact):
let status: ContactsPeerItemStatus let status: ContactsPeerItemStatus
if let label = label { if let label = label {
status = .custom(string: label, multiline: false, isActive: false, icon: nil) status = .custom(string: NSAttributedString(string: label), multiline: false, isActive: false, icon: nil)
} else if participant.peer.id != context.account.peerId { } else if participant.peer.id != context.account.peerId {
let presence = participant.presences[participant.peer.id] ?? TelegramUserPresence(status: .none, lastActivity: 0) let presence = participant.presences[participant.peer.id] ?? TelegramUserPresence(status: .none, lastActivity: 0)
status = .presence(EnginePeer.Presence(presence), presentationData.dateTimeFormat) status = .presence(EnginePeer.Presence(presence), presentationData.dateTimeFormat)

View File

@ -117,7 +117,7 @@ private enum DeleteAccountDataEntry: ItemListNodeEntry, Equatable {
let arguments = arguments as! DeleteAccountDataArguments let arguments = arguments as! DeleteAccountDataArguments
switch self { switch self {
case let .header(theme, animation, title, text, hideOnSmallScreens): case let .header(theme, animation, title, text, hideOnSmallScreens):
return InviteLinkHeaderItem(context: arguments.context, theme: theme, title: title, text: text, animationName: animation, hideOnSmallScreens: hideOnSmallScreens, sectionId: self.section, linkAction: nil) return InviteLinkHeaderItem(context: arguments.context, theme: theme, title: title, text: NSAttributedString(string: text), animationName: animation, hideOnSmallScreens: hideOnSmallScreens, sectionId: self.section, linkAction: nil)
case let .peers(_, peers): case let .peers(_, peers):
return DeleteAccountPeersItem(context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, peers: peers, sectionId: self.section) return DeleteAccountPeersItem(context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, peers: peers, sectionId: self.section)
case let .info(_, text): case let .info(_, text):

View File

@ -234,8 +234,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) } dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) }
dict[-712374074] = { return Api.Dialog.parse_dialog($0) } dict[-712374074] = { return Api.Dialog.parse_dialog($0) }
dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) } dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) }
dict[1605718587] = { return Api.DialogFilter.parse_dialogFilter($0) } dict[-1438177711] = { return Api.DialogFilter.parse_dialogFilter($0) }
dict[-1612542300] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) } dict[-1772913705] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) }
dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) } dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) }
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) } dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) } dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) }
@ -582,7 +582,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) }
dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) }
dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
dict[139818551] = { return Api.MessageAction.parse_messageActionStarGift($0) } dict[-1253342558] = { return Api.MessageAction.parse_messageActionStarGift($0) }
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) }
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }
dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) }
@ -1225,7 +1225,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1044107055] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultAdsHidden($0) } dict[1044107055] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultAdsHidden($0) }
dict[-2073059774] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultChooseOption($0) } dict[-2073059774] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultChooseOption($0) }
dict[-1384544183] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultReported($0) } dict[-1384544183] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultReported($0) }
dict[500007837] = { return Api.chatlists.ChatlistInvite.parse_chatlistInvite($0) } dict[-250687953] = { return Api.chatlists.ChatlistInvite.parse_chatlistInvite($0) }
dict[-91752871] = { return Api.chatlists.ChatlistInvite.parse_chatlistInviteAlready($0) } dict[-91752871] = { return Api.chatlists.ChatlistInvite.parse_chatlistInviteAlready($0) }
dict[-1816295539] = { return Api.chatlists.ChatlistUpdates.parse_chatlistUpdates($0) } dict[-1816295539] = { return Api.chatlists.ChatlistUpdates.parse_chatlistUpdates($0) }
dict[283567014] = { return Api.chatlists.ExportedChatlistInvite.parse_exportedChatlistInvite($0) } dict[283567014] = { return Api.chatlists.ExportedChatlistInvite.parse_exportedChatlistInvite($0) }

View File

@ -373,7 +373,7 @@ public extension Api {
case messageActionSetChatTheme(emoticon: String) case messageActionSetChatTheme(emoticon: String)
case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper)
case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?)
case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?) case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, canExportAt: Int32?)
case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionSuggestProfilePhoto(photo: Api.Photo)
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?)
@ -719,14 +719,15 @@ public extension Api {
serializeInt32(period, buffer: buffer, boxed: false) serializeInt32(period, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)}
break break
case .messageActionStarGift(let flags, let gift, let message, let convertStars): case .messageActionStarGift(let flags, let gift, let message, let convertStars, let canExportAt):
if boxed { if boxed {
buffer.appendInt32(139818551) buffer.appendInt32(-1253342558)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
gift.serialize(buffer, true) gift.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)}
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(convertStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {serializeInt64(convertStars!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(canExportAt!, buffer: buffer, boxed: false)}
break break
case .messageActionSuggestProfilePhoto(let photo): case .messageActionSuggestProfilePhoto(let photo):
if boxed { if boxed {
@ -853,8 +854,8 @@ public extension Api {
return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)]) return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)])
case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom):
return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)])
case .messageActionStarGift(let flags, let gift, let message, let convertStars): case .messageActionStarGift(let flags, let gift, let message, let convertStars, let canExportAt):
return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any)]) return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("canExportAt", canExportAt as Any)])
case .messageActionSuggestProfilePhoto(let photo): case .messageActionSuggestProfilePhoto(let photo):
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
@ -1515,12 +1516,15 @@ public extension Api {
} } } }
var _4: Int64? var _4: Int64?
if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() } if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() }
var _5: Int32?
if Int(_1!) & Int(1 << 7) != 0 {_5 = reader.readInt32() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = (Int(_1!) & Int(1 << 7) == 0) || _5 != nil
return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4) if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, canExportAt: _5)
} }
else { else {
return nil return nil

View File

@ -476,17 +476,17 @@ public extension Api.channels {
} }
public extension Api.chatlists { public extension Api.chatlists {
enum ChatlistInvite: TypeConstructorDescription { enum ChatlistInvite: TypeConstructorDescription {
case chatlistInvite(flags: Int32, title: String, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) case chatlistInvite(flags: Int32, title: Api.TextWithEntities, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
case chatlistInviteAlready(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User]) case chatlistInviteAlready(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users): case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users):
if boxed { if boxed {
buffer.appendInt32(500007837) buffer.appendInt32(-250687953)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false) title.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
buffer.appendInt32(Int32(peers.count)) buffer.appendInt32(Int32(peers.count))
@ -545,8 +545,10 @@ public extension Api.chatlists {
public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? { public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? {
var _1: Int32? var _1: Int32?
_1 = reader.readInt32() _1 = reader.readInt32()
var _2: String? var _2: Api.TextWithEntities?
_2 = parseString(reader) if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
}
var _3: String? var _3: String?
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) } if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
var _4: [Api.Peer]? var _4: [Api.Peer]?

View File

@ -1166,19 +1166,19 @@ public extension Api {
} }
public extension Api { public extension Api {
enum DialogFilter: TypeConstructorDescription { enum DialogFilter: TypeConstructorDescription {
case dialogFilter(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer]) case dialogFilter(flags: Int32, id: Int32, title: Api.TextWithEntities, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer])
case dialogFilterChatlist(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer]) case dialogFilterChatlist(flags: Int32, id: Int32, title: Api.TextWithEntities, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer])
case dialogFilterDefault case dialogFilterDefault
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers): case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers):
if boxed { if boxed {
buffer.appendInt32(1605718587) buffer.appendInt32(-1438177711)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false) title.serialize(buffer, true)
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -1199,11 +1199,11 @@ public extension Api {
break break
case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers): case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers):
if boxed { if boxed {
buffer.appendInt32(-1612542300) buffer.appendInt32(-1772913705)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(id, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false) title.serialize(buffer, true)
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -1242,8 +1242,10 @@ public extension Api {
_1 = reader.readInt32() _1 = reader.readInt32()
var _2: Int32? var _2: Int32?
_2 = reader.readInt32() _2 = reader.readInt32()
var _3: String? var _3: Api.TextWithEntities?
_3 = parseString(reader) if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
}
var _4: String? var _4: String?
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) } if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
var _5: Int32? var _5: Int32?
@ -1280,8 +1282,10 @@ public extension Api {
_1 = reader.readInt32() _1 = reader.readInt32()
var _2: Int32? var _2: Int32?
_2 = reader.readInt32() _2 = reader.readInt32()
var _3: String? var _3: Api.TextWithEntities?
_3 = parseString(reader) if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
}
var _4: String? var _4: String?
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) } if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
var _5: Int32? var _5: Int32?

View File

@ -171,7 +171,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId)) return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId))
case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId): case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId):
return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId))) return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)))
case let .messageActionStarGift(flags, apiGift, message, convertStars): case let .messageActionStarGift(flags, apiGift, message, convertStars, _):
let text: String? let text: String?
let entities: [MessageTextEntity]? let entities: [MessageTextEntity]?
switch message { switch message {

View File

@ -342,9 +342,17 @@ extension ChatListFilter {
case .dialogFilterDefault: case .dialogFilterDefault:
self = .allChats self = .allChats
case let .dialogFilter(flags, id, title, emoticon, color, pinnedPeers, includePeers, excludePeers): case let .dialogFilter(flags, id, title, emoticon, color, pinnedPeers, includePeers, excludePeers):
let titleText: String
let titleEntities: [MessageTextEntity]
switch title {
case let .textWithEntities(text, entities):
titleText = text
titleEntities = messageTextEntitiesFromApiEntities(entities)
}
let disableTitleAnimations = (flags & (1 << 28)) != 0
self = .filter( self = .filter(
id: id, id: id,
title: ChatFolderTitle(text: title, entities: [], enableAnimations: true), title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimations),
emoticon: emoticon, emoticon: emoticon,
data: ChatListFilterData( data: ChatListFilterData(
isShared: false, isShared: false,
@ -392,9 +400,18 @@ extension ChatListFilter {
) )
) )
case let .dialogFilterChatlist(flags, id, title, emoticon, color, pinnedPeers, includePeers): case let .dialogFilterChatlist(flags, id, title, emoticon, color, pinnedPeers, includePeers):
let titleText: String
let titleEntities: [MessageTextEntity]
switch title {
case let .textWithEntities(text, entities):
titleText = text
titleEntities = messageTextEntitiesFromApiEntities(entities)
}
let disableTitleAnimations = (flags & (1 << 28)) != 0
self = .filter( self = .filter(
id: id, id: id,
title: ChatFolderTitle(text: title, entities: [], enableAnimations: true), title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimations),
emoticon: emoticon, emoticon: emoticon,
data: ChatListFilterData( data: ChatListFilterData(
isShared: true, isShared: true,
@ -446,7 +463,10 @@ extension ChatListFilter {
if data.color != nil { if data.color != nil {
flags |= 1 << 27 flags |= 1 << 27
} }
return .dialogFilterChatlist(flags: flags, id: id, title: title.text, emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in if !title.enableAnimations {
flags |= 1 << 28
}
return .dialogFilterChatlist(flags: flags, id: id, title: .textWithEntities(text: title.text, entities: apiEntitiesFromMessageTextEntities(title.entities, associatedPeers: SimpleDictionary())), emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer) return transaction.getPeer(peerId).flatMap(apiInputPeer)
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in }, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
if data.includePeers.pinnedPeers.contains(peerId) { if data.includePeers.pinnedPeers.contains(peerId) {
@ -472,7 +492,10 @@ extension ChatListFilter {
if data.color != nil { if data.color != nil {
flags |= 1 << 27 flags |= 1 << 27
} }
return .dialogFilter(flags: flags, id: id, title: title.text, emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in if !title.enableAnimations {
flags |= 1 << 28
}
return .dialogFilter(flags: flags, id: id, title: .textWithEntities(text: title.text, entities: apiEntitiesFromMessageTextEntities(title.entities, associatedPeers: SimpleDictionary())), emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer) return transaction.getPeer(peerId).flatMap(apiInputPeer)
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in }, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
if data.includePeers.pinnedPeers.contains(peerId) { if data.includePeers.pinnedPeers.contains(peerId) {

View File

@ -273,9 +273,11 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in |> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
return account.postbox.transaction { transaction -> ChatFolderLinkContents in return account.postbox.transaction { transaction -> ChatFolderLinkContents in
switch result { switch result {
case let .chatlistInvite(_, title, emoticon, peers, chats, users): case let .chatlistInvite(flags, title, emoticon, peers, chats, users):
let _ = emoticon let _ = emoticon
let disableTitleAnimation = (flags & (1 << 1)) != 0
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
var memberCounts: [PeerId: Int] = [:] var memberCounts: [PeerId: Int] = [:]
@ -301,7 +303,15 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
} }
} }
return ChatFolderLinkContents(localFilterId: nil, title: ChatFolderTitle(text: title, entities: [], enableAnimations: true), peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts) let titleText: String
let titleEntities: [MessageTextEntity]
switch title {
case let .textWithEntities(text, entities):
titleText = text
titleEntities = messageTextEntitiesFromApiEntities(entities)
}
return ChatFolderLinkContents(localFilterId: nil, title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimation), peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
case let .chatlistInviteAlready(filterId, missingPeers, alreadyPeers, chats, users): case let .chatlistInviteAlready(filterId, missingPeers, alreadyPeers, chats, users):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
var memberCounts: [PeerId: Int] = [:] var memberCounts: [PeerId: Int] = [:]

View File

@ -22,6 +22,7 @@ swift_library(
"//submodules/WallpaperBackgroundNode", "//submodules/WallpaperBackgroundNode",
"//submodules/TelegramPresentationData", "//submodules/TelegramPresentationData",
"//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -11,6 +11,7 @@ import Markdown
import WallpaperBackgroundNode import WallpaperBackgroundNode
import EmojiStatusComponent import EmojiStatusComponent
import TelegramPresentationData import TelegramPresentationData
import TextNodeWithEntities
final class BlurredRoundedRectangle: Component { final class BlurredRoundedRectangle: Component {
let color: UIColor let color: UIColor
@ -1210,21 +1211,42 @@ public final class ChatOverscrollControl: CombinedComponent {
} }
public final class ChatInputPanelOverscrollNode: ASDisplayNode { public final class ChatInputPanelOverscrollNode: ASDisplayNode {
public let text: (String, [(Int, NSRange)]) public let text: NSAttributedString
public let priority: Int public let priority: Int
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNodeWithEntities
public init(text: (String, [(Int, NSRange)]), color: UIColor, priority: Int) { public init(context: AccountContext, text: NSAttributedString, color: UIColor, priority: Int) {
self.text = text self.text = text
self.priority = priority self.priority = priority
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNodeWithEntities()
super.init() super.init()
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: color) let attributedText = NSMutableAttributedString(string: text.string)
let bold = MarkdownAttributeSet(font: Font.bold(14.0), textColor: color) attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: text.length))
attributedText.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: text.length))
self.titleNode.attributedText = addAttributesToStringWithRanges(text, body: body, argumentAttributes: [0: bold]) text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
for (key, value) in attributes {
if key == ChatTextInputAttributes.bold {
attributedText.addAttribute(.font, value: Font.bold(14.0), range: range)
} else if key == ChatTextInputAttributes.italic {
attributedText.addAttribute(.font, value: Font.italic(14.0), range: range)
} else if key == ChatTextInputAttributes.monospace {
attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range)
} else {
attributedText.addAttribute(key, value: value, range: range)
}
}
})
self.titleNode.attributedText = attributedText
self.titleNode.visibility = true
self.titleNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: color.withMultipliedAlpha(0.1),
attemptSynchronous: true
)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
} }

View File

@ -35,6 +35,7 @@ swift_library(
"//submodules/PremiumUI", "//submodules/PremiumUI",
"//submodules/QrCodeUI", "//submodules/QrCodeUI",
"//submodules/InviteLinksUI", "//submodules/InviteLinksUI",
"//submodules/Components/MultilineTextWithEntitiesComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -7,6 +7,7 @@ import AccountContext
import MultilineTextComponent import MultilineTextComponent
import TelegramPresentationData import TelegramPresentationData
import TelegramCore import TelegramCore
import MultilineTextWithEntitiesComponent
final class BadgeComponent: Component { final class BadgeComponent: Component {
let fillColor: UIColor let fillColor: UIColor
@ -84,17 +85,20 @@ final class BadgeComponent: Component {
} }
final class ChatFolderLinkHeaderComponent: Component { final class ChatFolderLinkHeaderComponent: Component {
let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let title: ChatFolderTitle let title: ChatFolderTitle
let badge: String? let badge: String?
init( init(
context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
strings: PresentationStrings, strings: PresentationStrings,
title: ChatFolderTitle, title: ChatFolderTitle,
badge: String? badge: String?
) { ) {
self.context = context
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.title = title self.title = title
@ -213,10 +217,16 @@ final class ChatFolderLinkHeaderComponent: Component {
} }
contentWidth += spacing contentWidth += spacing
//TODO:release
let titleSize = self.title.update( let titleSize = self.title.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(Text(text: component.title.text, font: Font.semibold(17.0), color: component.theme.list.itemAccentColor)), component: AnyComponent(MultilineTextWithEntitiesComponent(
context: component.context,
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer,
placeholderColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.1),
text: .plain(component.title.attributedString(font: Font.semibold(17.0), textColor: component.theme.list.itemAccentColor)),
manualVisibilityControl: false
)),
environment: {}, environment: {},
containerSize: CGSize(width: 200.0, height: 100.0) containerSize: CGSize(width: 200.0, height: 100.0)
) )

View File

@ -10,6 +10,7 @@ import TelegramPresentationData
import AccountContext import AccountContext
import TelegramCore import TelegramCore
import MultilineTextComponent import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import SolidRoundedButtonComponent import SolidRoundedButtonComponent
import PresentationDataUtils import PresentationDataUtils
import Markdown import Markdown
@ -437,6 +438,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let topIconSize = self.topIcon.update( let topIconSize = self.topIcon.update(
transition: contentTransition, transition: contentTransition,
component: AnyComponent(ChatFolderLinkHeaderComponent( component: AnyComponent(ChatFolderLinkHeaderComponent(
context: component.context,
theme: environment.theme, theme: environment.theme,
strings: environment.strings, strings: environment.strings,
title: component.linkContents?.title ?? ChatFolderTitle(text: "Folder", entities: [], enableAnimations: true), title: component.linkContents?.title ?? ChatFolderTitle(text: "Folder", entities: [], enableAnimations: true),
@ -457,37 +459,53 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
contentHeight += topIconSize.height contentHeight += topIconSize.height
contentHeight += 20.0 contentHeight += 20.0
let text: String let text: NSAttributedString
if case .linkList = component.subject { if case .linkList = component.subject {
text = environment.strings.FolderLinkPreview_TextLinkList text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextLinkList)
} else if let linkContents = component.linkContents { } else if let linkContents = component.linkContents {
if case .remove = component.subject { if case .remove = component.subject {
text = environment.strings.FolderLinkPreview_TextRemoveFolder text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextRemoveFolder, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
} else if allChatsAdded { } else if allChatsAdded {
text = environment.strings.FolderLinkPreview_TextAllAdded text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextAllAdded, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
} else if linkContents.localFilterId == nil { } else if linkContents.localFilterId == nil {
text = environment.strings.FolderLinkPreview_TextAddFolder text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextAddFolder, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
} else { } else if let title = linkContents.title {
let chatCountString: String = environment.strings.FolderLinkPreview_TextAddChatsCount(Int32(canAddChatCount)) let chatCountString: String = environment.strings.FolderLinkPreview_TextAddChatsCount(Int32(canAddChatCount))
//TODO:release
text = environment.strings.FolderLinkPreview_TextAddChats(chatCountString, linkContents.title?.text ?? "").string let textValue = NSMutableAttributedString(string: environment.strings.FolderLinkPreview_TextAddChatsV2)
textValue.addAttributes([
.font: Font.regular(15.0),
.foregroundColor: environment.theme.list.freeTextColor
], range: NSRange(location: 0, length: textValue.length))
let folderRange = (textValue.string as NSString).range(of: "{folder}")
if folderRange.location != NSNotFound {
textValue.replaceCharacters(in: folderRange, with: "")
textValue.insert(title.attributedString(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor), at: folderRange.location)
}
let chatsRange = (textValue.string as NSString).range(of: "{chats}")
if chatsRange.location != NSNotFound {
textValue.replaceCharacters(in: chatsRange, with: "")
textValue.insert(NSAttributedString(string: chatCountString, font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor), at: chatsRange.location)
}
text = textValue
} else {
text = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
} }
} else { } else {
text = " " text = NSAttributedString(string: " ")
} }
let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
let bold = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor)
let descriptionTextSize = self.descriptionText.update( let descriptionTextSize = self.descriptionText.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextWithEntitiesComponent(
text: .markdown(text: text, attributes: MarkdownAttributes( context: component.context,
body: body, animationCache: component.context.animationCache,
bold: bold, animationRenderer: component.context.animationRenderer,
link: body, placeholderColor: environment.theme.list.freeTextColor.withMultipliedAlpha(0.1),
linkAttribute: { _ in nil } text: .plain(text),
)),
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)), )),
@ -982,8 +1000,12 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
disposable.add(component.context.account.postbox.addHiddenChatIds(peerIds: Array(self.selectedItems))) disposable.add(component.context.account.postbox.addHiddenChatIds(peerIds: Array(self.selectedItems)))
disposable.add(component.context.account.viewTracker.addHiddenChatListFilterIds([folderId])) disposable.add(component.context.account.viewTracker.addHiddenChatListFilterIds([folderId]))
//TODO:release let folderTitle: ChatFolderTitle
let folderTitle = linkContents.title?.text ?? "" if let title = linkContents.title {
folderTitle = title
} else {
folderTitle = ChatFolderTitle(text: "", entities: [], enableAnimations: true)
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
@ -1015,11 +1037,18 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
} }
} }
let undoText = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastLeftTitleV2)
let folderRange = (undoText.string as NSString).range(of: "{folder}")
if folderRange.location != NSNotFound {
undoText.replaceCharacters(in: folderRange, with: "")
undoText.insert(folderTitle.rawAttributedString, at: folderRange.location)
}
let context = component.context let context = component.context
let selectedItems = self.selectedItems let selectedItems = self.selectedItems
let undoOverlayController = UndoOverlayController( let undoOverlayController = UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .removedChat(title: presentationData.strings.FolderLinkPreview_ToastLeftTitle(folderTitle).string, text: additionalText), content: .removedChat(context: component.context, title: undoText, text: additionalText),
elevatedLayout: false, elevatedLayout: false,
action: { value in action: { value in
if case .commit = value { if case .commit = value {
@ -1112,8 +1141,14 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
} }
if isUpdates { if isUpdates {
//TODO:release let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitleV2)
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitle(result.title.text).string, text: presentationData.strings.FolderLinkPreview_ToastChatsAddedText(Int32(result.newChatCount)), customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) let folderRange = (titleString.string as NSString).range(of: "{folder}")
if folderRange.location != NSNotFound {
titleString.replaceCharacters(in: folderRange, with: "")
titleString.insert(result.title.rawAttributedString, at: folderRange.location)
}
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: titleString, text: NSAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedText(Int32(result.newChatCount))), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
} else if result.newChatCount != 0 { } else if result.newChatCount != 0 {
let animationBackgroundColor: UIColor let animationBackgroundColor: UIColor
if presentationData.theme.overallDarkAppearance { if presentationData.theme.overallDarkAppearance {
@ -1121,8 +1156,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
} else { } else {
animationBackgroundColor = UIColor(rgb: 0x474747) animationBackgroundColor = UIColor(rgb: 0x474747)
} }
//TODO:release
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitle(result.title.text).string, text: presentationData.strings.FolderLinkPreview_ToastFolderAddedText(Int32(result.newChatCount)), customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitleV2)
let folderRange = (titleString.string as NSString).range(of: "{folder}")
if folderRange.location != NSNotFound {
titleString.replaceCharacters(in: folderRange, with: "")
titleString.insert(result.title.rawAttributedString, at: folderRange.location)
}
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: titleString, text: NSAttributedString(string: presentationData.strings.FolderLinkPreview_ToastFolderAddedText(Int32(result.newChatCount))), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
} else { } else {
let animationBackgroundColor: UIColor let animationBackgroundColor: UIColor
if presentationData.theme.overallDarkAppearance { if presentationData.theme.overallDarkAppearance {
@ -1130,8 +1172,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
} else { } else {
animationBackgroundColor = UIColor(rgb: 0x474747) animationBackgroundColor = UIColor(rgb: 0x474747)
} }
//TODO:release
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitle(result.title.text).string, text: "", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current) let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitleV2)
let folderRange = (titleString.string as NSString).range(of: "{folder}")
if folderRange.location != NSNotFound {
titleString.replaceCharacters(in: folderRange, with: "")
titleString.insert(result.title.rawAttributedString, at: folderRange.location)
}
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: titleString, text: NSAttributedString(string: ""), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
} }
}) })
} }
@ -1310,7 +1359,6 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
}) })
let navigationController = controller.navigationController let navigationController = controller.navigationController
//TODO:release
controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: { _ in }, presentController: { [weak navigationController] c in controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: { _ in }, presentController: { [weak navigationController] c in
(navigationController?.topViewController as? ViewController)?.present(c, in: .window(.root)) (navigationController?.topViewController as? ViewController)?.present(c, in: .window(.root))
})) }))

View File

@ -90,7 +90,7 @@ public enum ChatTitleContent: Equatable {
case replies case replies
} }
case peer(peerView: PeerData, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool) case peer(peerView: PeerData, customTitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool)
case replyThread(type: ReplyThreadType, count: Int) case replyThread(type: ReplyThreadType, count: Int)
case custom(String, String?, Bool) case custom(String, String?, Bool)
@ -104,7 +104,7 @@ public enum ChatTitleContent: Equatable {
if customTitle != rhsCustomTitle { if customTitle != rhsCustomTitle {
return false return false
} }
if onlineMemberCount != rhsOnlineMemberCount { if onlineMemberCount.0 != rhsOnlineMemberCount.0 || onlineMemberCount.1 != rhsOnlineMemberCount.1 {
return false return false
} }
if isScheduledMessages != rhsIsScheduledMessages { if isScheduledMessages != rhsIsScheduledMessages {
@ -616,7 +616,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
if channel.flags.contains(.isForum), customTitle != nil { if channel.flags.contains(.isForum), customTitle != nil {
let string = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) let string = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
state = .info(string, .generic) state = .info(string, .generic)
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount { } else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = onlineMemberCount.total ?? cachedChannelData.participantsSummary.memberCount {
if memberCount == 0 { if memberCount == 0 {
let string: NSAttributedString let string: NSAttributedString
if case .group = channel.info { if case .group = channel.info {
@ -626,7 +626,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
} }
state = .info(string, .generic) state = .info(string, .generic)
} else { } else {
if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 { if case .group = channel.info, let onlineMemberCount = onlineMemberCount.recent, onlineMemberCount > 1 {
let string = NSMutableAttributedString() let string = NSMutableAttributedString()
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)) string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))

View File

@ -382,6 +382,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
} }
} }
public var enableAnimation: Bool = true
public weak var mirrorLayer: CALayer? { public weak var mirrorLayer: CALayer? {
didSet { didSet {
if let mirrorLayer = self.mirrorLayer { if let mirrorLayer = self.mirrorLayer {

View File

@ -330,7 +330,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS
} }
let context = self.context let context = self.context
let undoController = UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: self.presentationData.strings.SavedMessages_SubChatDeleted, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in let undoController = UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: self.presentationData.strings.SavedMessages_SubChatDeleted), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
if value == .commit { if value == .commit {
let _ = context.engine.messages.clearHistoryInteractively(peerId: context.account.peerId, threadId: peer.id.toInt64(), type: .forLocalPeer).startStandalone(completed: { let _ = context.engine.messages.clearHistoryInteractively(peerId: context.account.peerId, threadId: peer.id.toInt64(), type: .forLocalPeer).startStandalone(completed: {
guard let self else { guard let self else {

View File

@ -1659,7 +1659,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
) )
} }
case let .group(groupId): case let .group(groupId):
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil) var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
if peerId.namespace == Namespaces.Peer.CloudChannel { if peerId.namespace == Namespaces.Peer.CloudChannel {
onlineMemberCount = context.account.viewTracker.peerView(groupId, updateData: false) onlineMemberCount = context.account.viewTracker.peerView(groupId, updateData: false)
|> map { view -> Bool? in |> map { view -> Bool? in
@ -1676,17 +1676,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
} }
} }
|> distinctUntilChanged |> distinctUntilChanged
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in |> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
if let isLarge = isLarge { if let isLarge = isLarge {
if isLarge { if isLarge {
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init) |> map { value -> (total: Int32?, recent: Int32?) in
return (nil, value)
}
} else { } else {
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init) |> map { value -> (total: Int32?, recent: Int32?) in
return (value.total, value.recent)
}
} }
} else { } else {
return .single(nil) return .single((nil, nil))
} }
} }
} }
@ -1695,9 +1699,11 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
context.account.viewTracker.peerView(groupId, updateData: false), context.account.viewTracker.peerView(groupId, updateData: false),
onlineMemberCount onlineMemberCount
) )
|> map { peerView, onlineMemberCount -> PeerInfoStatusData? in |> map { peerView, memberCountData -> PeerInfoStatusData? in
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount { let (preciseTotalMemberCount, onlineMemberCount) = memberCountData
if let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = preciseTotalMemberCount ?? cachedChannelData.participantsSummary.memberCount {
if let onlineMemberCount, onlineMemberCount > 1 {
var string = "" var string = ""
string.append("\(strings.Conversation_StatusMembers(Int32(memberCount))), ") string.append("\(strings.Conversation_StatusMembers(Int32(memberCount))), ")

View File

@ -172,7 +172,7 @@ private enum OldChannelsEntry: ItemListNodeEntry {
case let .peersHeader(title): case let .peersHeader(title):
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
case let .peer(_, peer, selected): case let .peer(_, peer, selected):
return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
arguments.togglePeer(peer.peer.id, true) arguments.togglePeer(peer.peer.id, true)
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil) }, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
} }

View File

@ -154,7 +154,7 @@ private enum OldChannelsSearchEntry: Comparable, Identifiable {
func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem { func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem {
switch self { switch self {
case let .peer(_, peer, selected): case let .peer(_, peer, selected):
return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
interaction.togglePeer(peer.peer.id) interaction.togglePeer(peer.peer.id)
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil) }, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
} }

View File

@ -171,7 +171,7 @@ final class PeerSelectionLoadingView: UIView {
context: context, context: context,
peerMode: .peer, peerMode: .peer,
peer: .peer(peer: peer1, chatPeer: peer1), peer: .peer(peer: peer1, chatPeer: peer1),
status: .custom(string: "status", multiline: false, isActive: false, icon: nil), status: .custom(string: NSAttributedString(string: "status"), multiline: false, isActive: false, icon: nil),
badge: nil, badge: nil,
requiresPremiumForMessaging: false, requiresPremiumForMessaging: false,
enabled: true, enabled: true,
@ -242,7 +242,7 @@ final class PeerSelectionLoadingView: UIView {
let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY) let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 100.0) fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 100.0)
let textFrame = itemNodes[sampleIndex].statusNode.frame.offsetBy(dx: 0.0, dy: currentY) let textFrame = itemNodes[sampleIndex].statusNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY)
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 40.0) fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 40.0)
context.setBlendMode(.normal) context.setBlendMode(.normal)

View File

@ -119,7 +119,7 @@ final class PeerSelectionScreenComponent: Component {
context: listNode.context, context: listNode.context,
peerMode: .peer, peerMode: .peer,
peer: .peer(peer: peer, chatPeer: peer), peer: .peer(peer: peer, chatPeer: peer),
status: .custom(string: statusText, multiline: false, isActive: false, icon: nil), status: .custom(string: NSAttributedString(string: statusText), multiline: false, isActive: false, icon: nil),
badge: nil, badge: nil,
requiresPremiumForMessaging: false, requiresPremiumForMessaging: false,
enabled: true, enabled: true,

View File

@ -20,11 +20,13 @@ private final class InlineStickerItem: Hashable {
let emoji: ChatTextInputTextCustomEmojiAttribute let emoji: ChatTextInputTextCustomEmojiAttribute
let file: TelegramMediaFile? let file: TelegramMediaFile?
let fontSize: CGFloat let fontSize: CGFloat
let enableAnimation: Bool
init(emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, fontSize: CGFloat) { init(emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, fontSize: CGFloat, enableAnimation: Bool) {
self.emoji = emoji self.emoji = emoji
self.file = file self.file = file
self.fontSize = fontSize self.fontSize = fontSize
self.enableAnimation = enableAnimation
} }
func hash(into hasher: inout Hasher) { func hash(into hasher: inout Hasher) {
@ -42,6 +44,9 @@ private final class InlineStickerItem: Hashable {
if lhs.fontSize != rhs.fontSize { if lhs.fontSize != rhs.fontSize {
return false return false
} }
if lhs.enableAnimation != rhs.enableAnimation {
return false
}
return true return true
} }
} }
@ -65,19 +70,25 @@ public final class TextNodeWithEntities {
public let renderer: MultiAnimationRenderer public let renderer: MultiAnimationRenderer
public let placeholderColor: UIColor public let placeholderColor: UIColor
public let attemptSynchronous: Bool public let attemptSynchronous: Bool
public let emojiOffset: CGPoint
public let fontSizeNorm: CGFloat
public init( public init(
context: AccountContext, context: AccountContext,
cache: AnimationCache, cache: AnimationCache,
renderer: MultiAnimationRenderer, renderer: MultiAnimationRenderer,
placeholderColor: UIColor, placeholderColor: UIColor,
attemptSynchronous: Bool attemptSynchronous: Bool,
emojiOffset: CGPoint = CGPoint(),
fontSizeNorm: CGFloat = 17.0
) { ) {
self.context = context self.context = context
self.cache = cache self.cache = cache
self.renderer = renderer self.renderer = renderer
self.placeholderColor = placeholderColor self.placeholderColor = placeholderColor
self.attemptSynchronous = attemptSynchronous self.attemptSynchronous = attemptSynchronous
self.emojiOffset = emojiOffset
self.fontSizeNorm = fontSizeNorm
} }
public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments { public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments {
@ -86,7 +97,8 @@ public final class TextNodeWithEntities {
cache: self.cache, cache: self.cache,
renderer: self.renderer, renderer: self.renderer,
placeholderColor: color, placeholderColor: color,
attemptSynchronous: self.attemptSynchronous attemptSynchronous: self.attemptSynchronous,
emojiOffset: self.emojiOffset
) )
} }
} }
@ -96,6 +108,8 @@ public final class TextNodeWithEntities {
private var enableLooping: Bool = true private var enableLooping: Bool = true
public var resetEmojiToFirstFrameAutomatically: Bool = false
public var visibilityRect: CGRect? { public var visibilityRect: CGRect? {
didSet { didSet {
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibilityRect { if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibilityRect {
@ -110,7 +124,13 @@ public final class TextNodeWithEntities {
} else { } else {
isItemVisible = false isItemVisible = false
} }
itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible let isVisibleForAnimations = self.enableLooping && isItemVisible && itemLayer.enableAnimation
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
itemLayer.isVisibleForAnimations = isVisibleForAnimations
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
itemLayer.reloadAnimation()
}
}
} }
} }
} }
@ -141,7 +161,7 @@ public final class TextNodeWithEntities {
let replacementRange = NSRange(location: 0, length: updatedSubstring.length) let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange) updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange) updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize, enableAnimation: value.enableAnimation), range: replacementRange)
updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange) updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange)
originalTextId += 1 originalTextId += 1
@ -197,7 +217,7 @@ public final class TextNodeWithEntities {
if let maybeNode = maybeNode { if let maybeNode = maybeNode {
if let applyArguments = applyArguments { if let applyArguments = applyArguments {
maybeNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false) maybeNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, emojiOffset: applyArguments.emojiOffset, fontSizeNorm: applyArguments.fontSizeNorm)
} }
return maybeNode return maybeNode
@ -205,7 +225,7 @@ public final class TextNodeWithEntities {
let resultNode = TextNodeWithEntities(textNode: result) let resultNode = TextNodeWithEntities(textNode: result)
if let applyArguments = applyArguments { if let applyArguments = applyArguments {
resultNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false) resultNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, emojiOffset: applyArguments.emojiOffset, fontSizeNorm: applyArguments.fontSizeNorm)
} }
return resultNode return resultNode
@ -222,7 +242,7 @@ public final class TextNodeWithEntities {
} }
} }
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool) { private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool, emojiOffset: CGPoint, fontSizeNorm: CGFloat) {
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
var nextIndexById: [Int64: Int] = [:] var nextIndexById: [Int64: Int] = [:]
@ -241,9 +261,9 @@ public final class TextNodeWithEntities {
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index) let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
validIds.append(id) validIds.append(id)
let itemSize = floorToScreenPixels(stickerItem.fontSize * 24.0 / 17.0) let itemSize = floorToScreenPixels(stickerItem.fontSize * 24.0 / fontSizeNorm)
var itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 1.0).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0) var itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left + emojiOffset.x, dy: textLayout.insets.top + 1.0 + emojiOffset.y).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x) itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y) itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
@ -256,8 +276,13 @@ public final class TextNodeWithEntities {
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor) itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)
self.inlineStickerItemLayers[id] = itemLayer self.inlineStickerItemLayers[id] = itemLayer
self.textNode.layer.addSublayer(itemLayer) self.textNode.layer.addSublayer(itemLayer)
}
itemLayer.isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame) itemLayer.enableAnimation = stickerItem.enableAnimation
let isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame) && itemLayer.enableAnimation
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
itemLayer.reloadAnimation()
}
} }
itemLayer.frame = itemFrame itemLayer.frame = itemFrame
@ -301,12 +326,19 @@ public class ImmediateTextNodeWithEntities: TextNode {
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:] private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:]
public private(set) var dustNode: InvisibleInkDustNode? public private(set) var dustNode: InvisibleInkDustNode?
public var resetEmojiToFirstFrameAutomatically: Bool = false
public var visibility: Bool = false { public var visibility: Bool = false {
didSet { didSet {
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility { if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility {
for (_, itemLayer) in self.inlineStickerItemLayers { for (_, itemLayer) in self.inlineStickerItemLayers {
let isItemVisible: Bool = self.visibility let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
itemLayer.isVisibleForAnimations = isVisibleForAnimations
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
itemLayer.reloadAnimation()
}
}
} }
} }
} }
@ -375,7 +407,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
let replacementRange = NSRange(location: 0, length: updatedSubstring.length) let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange) updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange) updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize, enableAnimation: value.enableAnimation), range: replacementRange)
updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange) updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange)
originalTextId += 1 originalTextId += 1
@ -437,7 +469,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
var enableAnimations = true var enableAnimations = true
if let arguments = self.arguments { if let arguments = self.arguments {
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor) self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
enableAnimations = arguments.context.sharedContext.energyUsageSettings.fullTranslucency enableAnimations = arguments.context.sharedContext.energyUsageSettings.fullTranslucency
} }
self.updateSpoilers(enableAnimations: enableAnimations, textLayout: layout) self.updateSpoilers(enableAnimations: enableAnimations, textLayout: layout)
@ -450,7 +482,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
return layout.size return layout.size
} }
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor) { private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, fontSizeNorm: CGFloat) {
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
var nextIndexById: [Int64: Int] = [:] var nextIndexById: [Int64: Int] = [:]
@ -469,7 +501,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index) let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
validIds.append(id) validIds.append(id)
let itemSide = floor(stickerItem.fontSize * 24.0 / 17.0) let itemSide = floor(stickerItem.fontSize * 24.0 / fontSizeNorm)
var itemSize = CGSize(width: itemSide, height: itemSide) var itemSize = CGSize(width: itemSide, height: itemSide)
if let file = stickerItem.file, let customItemLayout = self.customItemLayout { if let file = stickerItem.file, let customItemLayout = self.customItemLayout {
itemSize = customItemLayout(itemSize, file) itemSize = customItemLayout(itemSize, file)
@ -486,8 +518,15 @@ public class ImmediateTextNodeWithEntities: TextNode {
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: false, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor) itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: false, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)
self.inlineStickerItemLayers[id] = itemLayer self.inlineStickerItemLayers[id] = itemLayer
self.layer.addSublayer(itemLayer) self.layer.addSublayer(itemLayer)
}
itemLayer.isVisibleForAnimations = self.enableLooping && self.visibility
itemLayer.enableAnimation = stickerItem.enableAnimation
let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
itemLayer.isVisibleForAnimations = isVisibleForAnimations
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
itemLayer.reloadAnimation()
}
} }
itemLayer.frame = itemFrame itemLayer.frame = itemFrame
@ -535,7 +574,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
let _ = apply() let _ = apply()
if let arguments = self.arguments { if let arguments = self.arguments {
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor) self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
} }
return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated, numberOfLines: layout.numberOfLines) return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated, numberOfLines: layout.numberOfLines)
@ -550,7 +589,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
let _ = apply() let _ = apply()
if let arguments = self.arguments { if let arguments = self.arguments {
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor) self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
} }
return layout return layout

View File

@ -5221,11 +5221,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let peerId = chatLocationPeerId let peerId = chatLocationPeerId
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId { if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
peerView.set(context.account.viewTracker.peerView(peerId)) peerView.set(context.account.viewTracker.peerView(peerId))
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil) var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
var hasScheduledMessages: Signal<Bool, NoError> = .single(false) var hasScheduledMessages: Signal<Bool, NoError> = .single(false)
if peerId.namespace == Namespaces.Peer.CloudChannel { if peerId.namespace == Namespaces.Peer.CloudChannel {
let recentOnlineSignal: Signal<Int32?, NoError> = peerView.get() let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView.get()
|> map { view -> Bool? in |> map { view -> Bool? in
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
if case .broadcast = peer.info { if case .broadcast = peer.info {
@ -5240,17 +5240,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
|> distinctUntilChanged |> distinctUntilChanged
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in |> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
if let isLarge = isLarge { if let isLarge = isLarge {
if isLarge { if isLarge {
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init) |> map { value -> (total: Int32?, recent: Int32?) in
return (nil, value)
}
} else { } else {
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init) |> map { value -> (total: Int32?, recent: Int32?) in
return (value.total, value.recent)
}
} }
} else { } else {
return .single(nil) return .single((nil, nil))
} }
} }
onlineMemberCount = recentOnlineSignal onlineMemberCount = recentOnlineSignal
@ -6192,9 +6196,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil) var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
if peerId.namespace == Namespaces.Peer.CloudChannel { if peerId.namespace == Namespaces.Peer.CloudChannel {
let recentOnlineSignal: Signal<Int32?, NoError> = peerView let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView
|> map { view -> Bool? in |> map { view -> Bool? in
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
if case .broadcast = peer.info { if case .broadcast = peer.info {
@ -6209,17 +6213,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
|> distinctUntilChanged |> distinctUntilChanged
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in |> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
if let isLarge = isLarge { if let isLarge = isLarge {
if isLarge { if isLarge {
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init) |> map { value -> (total: Int32?, recent: Int32?) in
return (nil, value)
}
} else { } else {
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|> map(Optional.init) |> map { value -> (total: Int32?, recent: Int32?) in
return (value.total, value.recent)
}
} }
} else { } else {
return .single(nil) return .single((nil, nil))
} }
} }
onlineMemberCount = recentOnlineSignal onlineMemberCount = recentOnlineSignal
@ -6321,7 +6329,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
peerPresences: [:], peerPresences: [:],
cachedData: nil cachedData: nil
) )
strongSelf.chatTitleView?.titleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: nil, isScheduledMessages: false, isMuted: false, customMessageCount: savedMessagesPeer?.messageCount ?? 0, isEnabled: true) strongSelf.chatTitleView?.titleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: savedMessagesPeer?.messageCount ?? 0, isEnabled: true)
strongSelf.peerView = peerView strongSelf.peerView = peerView
@ -8551,7 +8559,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
statusText = self.presentationData.strings.Undo_ChatCleared statusText = self.presentationData.strings.Undo_ChatCleared
} }
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, action: { [weak self] value in self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, action: { [weak self] value in
guard let strongSelf = self else { guard let strongSelf = self else {
return false return false
} }

View File

@ -94,7 +94,7 @@ extension ChatControllerImpl {
self.present( self.present(
UndoOverlayController( UndoOverlayController(
presentationData: self.presentationData, presentationData: self.presentationData,
content: undoRights.isEmpty ? .actionSucceeded(title: title, text: text, cancel: nil, destructive: false) : .removedChat(title: title ?? text, text: title == nil ? nil : text), content: undoRights.isEmpty ? .actionSucceeded(title: title, text: text, cancel: nil, destructive: false) : .removedChat(context: self.context, title: NSAttributedString(string: title ?? text), text: title == nil ? nil : text),
elevatedLayout: false, elevatedLayout: false,
action: { [weak self] action in action: { [weak self] action in
guard let self else { guard let self else {
@ -357,7 +357,7 @@ extension ChatControllerImpl {
self.chatDisplayNode.historyNode.ignoreMessageIds = Set(messageIds) self.chatDisplayNode.historyNode.ignoreMessageIds = Set(messageIds)
let undoTitle = self.presentationData.strings.Chat_MessagesDeletedToast_Text(Int32(messageIds.count)) let undoTitle = self.presentationData.strings.Chat_MessagesDeletedToast_Text(Int32(messageIds.count))
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: undoTitle, text: nil), elevatedLayout: false, position: .top, action: { [weak self] value in self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: undoTitle), text: nil), elevatedLayout: false, position: .top, action: { [weak self] value in
guard let self else { guard let self else {
return false return false
} }

View File

@ -153,7 +153,7 @@ extension ChatControllerImpl {
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = range strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = range
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, action: { value in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, action: { value in
guard let strongSelf = self else { guard let strongSelf = self else {
return false return false
} }

View File

@ -35,6 +35,7 @@ import ChatMessageTransitionNode
import ChatControllerInteraction import ChatControllerInteraction
import DustEffect import DustEffect
import UrlHandling import UrlHandling
import TextFormat
struct ChatTopVisibleMessageRange: Equatable { struct ChatTopVisibleMessageRange: Equatable {
var lowerBound: MessageIndex var lowerBound: MessageIndex
@ -2313,41 +2314,57 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.currentOverscrollExpandProgress = expandProgress self.currentOverscrollExpandProgress = expandProgress
if let nextChannelToRead = self.nextChannelToRead { if let nextChannelToRead = self.nextChannelToRead {
let swipeText: (String, [(Int, NSRange)]) let swipeText: NSAttributedString
let releaseText: (String, [(Int, NSRange)]) let releaseText: NSAttributedString
switch nextChannelToRead.location { switch nextChannelToRead.location {
case .same: case .same:
if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl, chatController.customChatNavigationStack != nil { if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl, chatController.customChatNavigationStack != nil {
swipeText = (self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress, []) swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress)
releaseText = (self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction, []) releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction)
} else if nextChannelToRead.threadData != nil { } else if nextChannelToRead.threadData != nil {
swipeText = (self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress, []) swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress)
releaseText = (self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction, []) releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction)
} else { } else {
swipeText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress, []) swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress)
releaseText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction, []) releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction)
} }
case .archived: case .archived:
swipeText = (self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress, []) swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress)
releaseText = (self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction, []) releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction)
case .unarchived: case .unarchived:
swipeText = (self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress, []) swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress)
releaseText = (self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction, []) releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction)
case let .folder(_, title): case let .folder(_, title):
//TODO:release let swipeTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgressV2)
swipeText = self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgress(title.text)._tuple let swipeFolderRange = (swipeTextValue.string as NSString).range(of: "{folder}")
releaseText = self.currentPresentationData.strings.Chat_NextChannelFolderSwipeAction(title.text)._tuple if swipeFolderRange.location != NSNotFound {
swipeTextValue.replaceCharacters(in: swipeFolderRange, with: "")
swipeTextValue.insert(title.attributedString(attributes: [
ChatTextInputAttributes.bold: true
]), at: swipeFolderRange.location)
}
swipeText = swipeTextValue
let releaseTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeActionV2)
let releaseTextFolderRange = (releaseTextValue.string as NSString).range(of: "{folder}")
if releaseTextFolderRange.location != NSNotFound {
releaseTextValue.replaceCharacters(in: releaseTextFolderRange, with: "")
releaseTextValue.insert(title.attributedString(attributes: [
ChatTextInputAttributes.bold: true
]), at: releaseTextFolderRange.location)
}
releaseText = releaseTextValue
} }
if expandProgress < 0.1 { if expandProgress < 0.1 {
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil) chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil)
} else if expandProgress >= 1.0 { } else if expandProgress >= 1.0 {
if chatControllerNode.inputPanelOverscrollNode?.text.0 != releaseText.0 { if chatControllerNode.inputPanelOverscrollNode?.text.string != releaseText.string {
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1)) chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1))
} }
} else { } else {
if chatControllerNode.inputPanelOverscrollNode?.text.0 != swipeText.0 { if chatControllerNode.inputPanelOverscrollNode?.text.string != swipeText.string {
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2)) chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2))
} }
} }
} else { } else {

View File

@ -550,7 +550,7 @@ public final class PeerChannelMemberCategoriesContextsManager {
|> runOn(Queue.mainQueue()) |> runOn(Queue.mainQueue())
} }
public func recentOnlineSmall(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal<Int32, NoError> { public func recentOnlineSmall(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal<(total: Int32, recent: Int32), NoError> {
return Signal { [weak self] subscriber in return Signal { [weak self] subscriber in
var previousIds: Set<PeerId>? var previousIds: Set<PeerId>?
let statusesDisposable = MetaDisposable() let statusesDisposable = MetaDisposable()
@ -587,7 +587,7 @@ public final class PeerChannelMemberCategoriesContextsManager {
} }
|> distinctUntilChanged |> distinctUntilChanged
|> deliverOnMainQueue).start(next: { count in |> deliverOnMainQueue).start(next: { count in
subscriber.putNext(count) subscriber.putNext((Int32(updatedIds.count), count))
})) }))
} }
}) })

View File

@ -402,6 +402,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
case file case file
case topicId case topicId
case topicInfo case topicInfo
case enableAnimation
} }
public enum Custom: Codable { public enum Custom: Codable {
@ -417,12 +418,14 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
public let fileId: Int64 public let fileId: Int64
public let file: TelegramMediaFile? public let file: TelegramMediaFile?
public let custom: Custom? public let custom: Custom?
public let enableAnimation: Bool
public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, custom: Custom? = nil) { public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, custom: Custom? = nil, enableAnimation: Bool = true) {
self.interactivelySelectedFromPackId = interactivelySelectedFromPackId self.interactivelySelectedFromPackId = interactivelySelectedFromPackId
self.fileId = fileId self.fileId = fileId
self.file = file self.file = file
self.custom = custom self.custom = custom
self.enableAnimation = enableAnimation
super.init() super.init()
} }
@ -433,6 +436,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
self.fileId = try container.decode(Int64.self, forKey: .fileId) self.fileId = try container.decode(Int64.self, forKey: .fileId)
self.file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) self.file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file)
self.custom = nil self.custom = nil
self.enableAnimation = try container.decodeIfPresent(Bool.self, forKey: .enableAnimation) ?? true
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -440,6 +444,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
try container.encodeIfPresent(self.interactivelySelectedFromPackId, forKey: .interactivelySelectedFromPackId) try container.encodeIfPresent(self.interactivelySelectedFromPackId, forKey: .interactivelySelectedFromPackId)
try container.encode(self.fileId, forKey: .fileId) try container.encode(self.fileId, forKey: .fileId)
try container.encodeIfPresent(self.file, forKey: .file) try container.encodeIfPresent(self.file, forKey: .file)
try container.encode(self.enableAnimation, forKey: .enableAnimation)
} }
override public func isEqual(_ object: Any?) -> Bool { override public func isEqual(_ object: Any?) -> Bool {

View File

@ -8,7 +8,7 @@ import ComponentFlow
import AnimatedTextComponent import AnimatedTextComponent
public enum UndoOverlayContent { public enum UndoOverlayContent {
case removedChat(title: String, text: String?) case removedChat(context: AccountContext, title: NSAttributedString, text: String?)
case archivedChat(peerId: Int64, title: String, text: String, undo: Bool) case archivedChat(peerId: Int64, title: String, text: String, undo: Bool)
case hidArchive(title: String, text: String, undo: Bool) case hidArchive(title: String, text: String, undo: Bool)
case revealedArchive(title: String, text: String, undo: Bool) case revealedArchive(title: String, text: String, undo: Bool)
@ -19,8 +19,8 @@ public enum UndoOverlayContent {
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 stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext)
case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?) case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?)
case chatAddedToFolder(chatTitle: String, folderTitle: String) case chatAddedToFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString)
case chatRemovedFromFolder(chatTitle: String, folderTitle: String) case chatRemovedFromFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString)
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool) case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
case setProximityAlert(title: String, text: String, cancelled: Bool) case setProximityAlert(title: String, text: String, cancelled: Bool)
case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, title: String?, text: String, action: String?, duration: Double) case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, title: String?, text: String, action: String?, duration: Double)
@ -45,6 +45,7 @@ public enum UndoOverlayContent {
case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?) case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?)
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?) case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)
case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?, timeout: Double?) case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?, timeout: Double?)
case universalWithEntities(context: AccountContext, animation: String, scale: CGFloat, colors: [String: UIColor], title: NSAttributedString?, text: NSAttributedString, animateEntities: Bool, customUndoText: String?, timeout: Double?)
case universalImage(image: UIImage, size: CGSize?, title: String?, text: String, customUndoText: String?, timeout: Double?) case universalImage(image: UIImage, size: CGSize?, title: String?, text: String, customUndoText: String?, timeout: Double?)
case premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?) case premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?)
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?) case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)

View File

@ -46,7 +46,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private var stickerSourceSize: CGSize? private var stickerSourceSize: CGSize?
private var stickerOffset: CGPoint? private var stickerOffset: CGPoint?
private var emojiStatus: ComponentView<Empty>? private var emojiStatus: ComponentView<Empty>?
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNodeWithEntities
private let textNode: ImmediateTextNodeWithEntities private let textNode: ImmediateTextNodeWithEntities
private var textComponent: ComponentView<Empty>? private var textComponent: ComponentView<Empty>?
private var animatedTextItems: [AnimatedTextComponent.Item]? private var animatedTextItems: [AnimatedTextComponent.Item]?
@ -92,7 +92,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.timerTextNode = ImmediateTextNode() self.timerTextNode = ImmediateTextNode()
self.timerTextNode.displaysAsynchronously = false self.timerTextNode.displaysAsynchronously = false
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNodeWithEntities()
self.titleNode.layer.anchorPoint = CGPoint() self.titleNode.layer.anchorPoint = CGPoint()
self.titleNode.displaysAsynchronously = false self.titleNode.displaysAsynchronously = false
self.titleNode.maximumNumberOfLines = 0 self.titleNode.maximumNumberOfLines = 0
@ -115,22 +115,51 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
var isUserInteractionEnabled = false var isUserInteractionEnabled = false
switch content { switch content {
case let .removedChat(title, text): case let .removedChat(context, title, text):
self.avatarNode = nil self.avatarNode = nil
self.iconNode = nil self.iconNode = nil
self.iconCheckNode = nil self.iconCheckNode = nil
self.animationNode = nil self.animationNode = nil
self.animatedStickerNode = nil self.animatedStickerNode = nil
let attributedTitle = NSMutableAttributedString(string: title.string)
attributedTitle.addAttribute(.font, value: Font.semibold(14.0), range: NSRange(location: 0, length: title.length))
attributedTitle.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(location: 0, length: title.length))
title.enumerateAttributes(in: NSRange(location: 0, length: title.length), using: { attributes, range, _ in
for (key, value) in attributes {
attributedTitle.addAttribute(key, value: value, range: range)
}
})
if let text { if let text {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) self.titleNode.attributedText = attributedTitle
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural) let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText self.textNode.attributedText = attributedText
} else { } else {
self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) self.textNode.attributedText = attributedTitle
} }
self.titleNode.visibility = true
self.titleNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
attemptSynchronous: false
)
self.textNode.visibility = true
self.textNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
attemptSynchronous: false
)
displayUndo = true displayUndo = true
self.originalRemainingSeconds = 5 self.originalRemainingSeconds = 5
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear) self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
@ -343,38 +372,67 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 5 self.originalRemainingSeconds = 5
case let .chatAddedToFolder(chatTitle, folderTitle): case let .chatAddedToFolder(context, chatTitle, folderTitle), let .chatRemovedFromFolder(context, chatTitle, folderTitle):
self.avatarNode = nil self.avatarNode = nil
self.iconNode = nil self.iconNode = nil
self.iconCheckNode = nil self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
let baseString: String
if case .chatAddedToFolder = content {
baseString = presentationData.strings.ChatList_AddedToFolderTooltipV2
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
} else {
baseString = presentationData.strings.ChatList_RemovedFromFolderTooltipV2
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
}
self.animatedStickerNode = nil self.animatedStickerNode = nil
let formattedString = presentationData.strings.ChatList_AddedToFolderTooltip(chatTitle, folderTitle) let string = NSMutableAttributedString(string: baseString)
string.addAttributes([
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white)) .font: Font.regular(14.0),
for range in formattedString.ranges { .foregroundColor: UIColor.white
string.addAttribute(.font, value: Font.regular(14.0), range: range.range) ], range: NSRange(location: 0, length: string.length))
}
let folderRange = (string.string as NSString).range(of: "{folder}")
self.textNode.attributedText = string if folderRange.location != NSNotFound {
displayUndo = false string.replaceCharacters(in: folderRange, with: "")
self.originalRemainingSeconds = 5 let processedFolderTitle = NSMutableAttributedString(string: folderTitle.string)
case let .chatRemovedFromFolder(chatTitle, folderTitle): processedFolderTitle.addAttributes([
self.avatarNode = nil .font: Font.semibold(14.0),
self.iconNode = nil .foregroundColor: UIColor.white
self.iconCheckNode = nil ], range: NSRange(location: 0, length: processedFolderTitle.length))
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0) folderTitle.enumerateAttributes(in: NSRange(location: 0, length: folderTitle.length), using: { attributes, range, _ in
self.animatedStickerNode = nil for (key, value) in attributes {
if key == ChatTextInputAttributes.bold {
let formattedString = presentationData.strings.ChatList_RemovedFromFolderTooltip(chatTitle, folderTitle) processedFolderTitle.addAttribute(.font, value: Font.semibold(14.0), range: range)
} else if key == ChatTextInputAttributes.italic {
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white)) processedFolderTitle.addAttribute(.font, value: Font.italic(14.0), range: range)
for range in formattedString.ranges { } else if key == ChatTextInputAttributes.monospace {
string.addAttribute(.font, value: Font.regular(14.0), range: range.range) processedFolderTitle.addAttribute(.font, value: Font.monospace(14.0), range: range)
} else {
processedFolderTitle.addAttribute(key, value: value, range: range)
}
}
})
string.insert(processedFolderTitle, at: folderRange.location)
}
let chatRange = (string.string as NSString).range(of: "{chat}")
if chatRange.location != NSNotFound {
string.replaceCharacters(in: chatRange, with: "")
string.insert(NSAttributedString(string: chatTitle, font: Font.semibold(14.0), textColor: .white), at: chatRange.location)
} }
self.textNode.attributedText = string self.textNode.attributedText = string
self.textNode.visibility = true
self.textNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
attemptSynchronous: false
)
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 5 self.originalRemainingSeconds = 5
case let .paymentSent(currencyValue, itemTitle): case let .paymentSent(currencyValue, itemTitle):
@ -1072,6 +1130,86 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.maximumNumberOfLines = 5 self.textNode.maximumNumberOfLines = 5
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
case let .universalWithEntities(context, animation, scale, colors, title, text, animateEntities, customUndoText, timeout):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: animation, colors: colors, scale: scale)
self.animatedStickerNode = nil
var attributedTitle: NSAttributedString?
if let title {
let attributedTitleValue = NSMutableAttributedString(string: title.string)
attributedTitleValue.addAttribute(.font, value: Font.semibold(14.0), range: NSRange(location: 0, length: title.length))
attributedTitleValue.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(location: 0, length: title.length))
title.enumerateAttributes(in: NSRange(location: 0, length: title.length), using: { attributes, range, _ in
for (key, value) in attributes {
attributedTitleValue.addAttribute(key, value: value, range: range)
}
})
attributedTitle = attributedTitleValue
}
if let attributedTitle, text.length == 0 {
self.titleNode.attributedText = nil
self.textNode.attributedText = attributedTitle
} else {
if let attributedTitle {
self.titleNode.attributedText = attributedTitle
} else {
self.titleNode.attributedText = nil
}
let attributedText = NSMutableAttributedString(string: text.string)
attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: text.length))
attributedText.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(location: 0, length: text.length))
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
for (key, value) in attributes {
if key == ChatTextInputAttributes.bold {
attributedText.addAttribute(.font, value: Font.semibold(14.0), range: range)
} else if key == ChatTextInputAttributes.italic {
attributedText.addAttribute(.font, value: Font.italic(14.0), range: range)
} else if key == ChatTextInputAttributes.monospace {
attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range)
} else {
attributedText.addAttribute(key, value: value, range: range)
}
}
})
self.textNode.attributedText = attributedText
}
if text.string.contains("](") {
isUserInteractionEnabled = true
}
self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3)
self.titleNode.visibility = animateEntities
self.titleNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
attemptSynchronous: false
)
self.textNode.maximumNumberOfLines = 5
self.textNode.visibility = animateEntities
self.textNode.arguments = TextNodeWithEntities.Arguments(
context: context,
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
attemptSynchronous: false
)
if let customUndoText = customUndoText { if let customUndoText = customUndoText {
undoText = customUndoText undoText = customUndoText
displayUndo = true displayUndo = true
@ -1431,7 +1569,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else { } else {
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
} }
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal,. universalImage, .premiumPaywall, .peers, .messageTagged: case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .universalWithEntities, .universalImage, .premiumPaywall, .peers, .messageTagged:
if self.textNode.tapAttributeAction != nil || displayUndo { if self.textNode.tapAttributeAction != nil || displayUndo {
self.isUserInteractionEnabled = true self.isUserInteractionEnabled = true
} else { } else {