mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Emoji in chat folders
This commit is contained in:
parent
1d28e11879
commit
4bed1703a2
@ -13521,3 +13521,17 @@ Sorry for the inconvenience.";
|
||||
"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.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";
|
||||
|
@ -413,7 +413,7 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
|
||||
case monospace
|
||||
case textMention(EnginePeer.Id)
|
||||
case textUrl(String)
|
||||
case customEmoji(stickerPack: StickerPackReference?, fileId: Int64)
|
||||
case customEmoji(stickerPack: StickerPackReference?, fileId: Int64, enableAnimation: Bool)
|
||||
case strikethrough
|
||||
case underline
|
||||
case spoiler
|
||||
@ -440,7 +440,8 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
|
||||
case 5:
|
||||
let stickerPack = try container.decodeIfPresent(StickerPackReference.self, forKey: "s")
|
||||
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:
|
||||
self = .strikethrough
|
||||
case 7:
|
||||
@ -474,10 +475,11 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
|
||||
case let .textUrl(url):
|
||||
try container.encode(4 as Int32, forKey: "t")
|
||||
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.encodeIfPresent(stickerPack, forKey: "s")
|
||||
try container.encode(fileId, forKey: "f")
|
||||
try container.encode(enableAnimation, forKey: "ea")
|
||||
case .strikethrough:
|
||||
try container.encode(6 as Int32, forKey: "t")
|
||||
case .underline:
|
||||
@ -560,7 +562,7 @@ public struct ChatTextInputStateText: Codable, Equatable {
|
||||
} else if key == ChatTextInputAttributes.textUrl, let value = value as? ChatTextInputTextUrlAttribute {
|
||||
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .textUrl(value.url), range: range.location ..< (range.location + range.length)))
|
||||
} 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 {
|
||||
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .strikethrough, range: range.location ..< (range.location + range.length)))
|
||||
} 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))
|
||||
case let .textUrl(url):
|
||||
result.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
case let .customEmoji(_, fileId):
|
||||
result.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
case let .customEmoji(_, fileId, enableAnimation):
|
||||
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:
|
||||
result.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
case .underline:
|
||||
@ -1204,3 +1206,40 @@ public protocol ChatHistoryListNode: ListView {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
||||
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||
"//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen",
|
||||
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
|
||||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||
|
@ -223,8 +223,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
}
|
||||
|> deliverOnMainQueue).startStandalone(completed: {
|
||||
c?.dismiss(completion: {
|
||||
//TODO:release
|
||||
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
|
||||
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
|
||||
return false
|
||||
}), in: .current)
|
||||
})
|
||||
@ -274,8 +273,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
}
|
||||
|
||||
let filterType = chatListFilterType(data)
|
||||
//TODO:release
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: title.text, icon: { theme in
|
||||
updatedItems.append(.action(ContextMenuActionItem(text: title.text, entities: title.entities, enableEntityAnimations: title.enableAnimations, icon: { theme in
|
||||
let imageName: String
|
||||
switch filterType {
|
||||
case .generic:
|
||||
@ -339,8 +337,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
}
|
||||
return filters
|
||||
}).startStandalone()
|
||||
//TODO:release
|
||||
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
|
||||
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
|
||||
return false
|
||||
}), 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)
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ import PeerInfoStoryGridScreen
|
||||
import ArchiveInfoScreen
|
||||
import BirthdayPickerScreen
|
||||
import OldChannelsController
|
||||
import TextFormat
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
@ -1453,7 +1454,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
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)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -1811,28 +1812,42 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:release
|
||||
let iconColor: UIColor = .white
|
||||
let overlayController: UndoOverlayController
|
||||
if !filterPeersAreMuted.areMuted {
|
||||
let text = strongSelf.presentationData.strings.ChatList_ToastFolderMuted(title.text).string
|
||||
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
|
||||
let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderMutedV2)
|
||||
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,
|
||||
"Top.Group 1.Fill 1": iconColor,
|
||||
"Bottom.Group 1.Fill 1": iconColor,
|
||||
"EXAMPLE.Group 1.Fill 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 {
|
||||
//TODO:release
|
||||
let text = strongSelf.presentationData.strings.ChatList_ToastFolderUnmuted(title.text).string
|
||||
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
|
||||
let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderUnmutedV2)
|
||||
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_profileunmute", scale: 0.075, colors: [
|
||||
"Middle.Group 1.Fill 1": iconColor,
|
||||
"Top.Group 1.Fill 1": iconColor,
|
||||
"Bottom.Group 1.Fill 1": iconColor,
|
||||
"EXAMPLE.Group 1.Fill 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)
|
||||
})
|
||||
@ -4736,7 +4751,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -4842,7 +4857,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -4914,7 +4929,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -5274,7 +5289,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -5496,7 +5511,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -5793,7 +5808,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -5933,7 +5948,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
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
|
||||
let imageName: String
|
||||
if isDisabled {
|
||||
@ -6347,9 +6361,9 @@ private final class ChatListLocationContext {
|
||||
let peerView = Promise<PeerView>()
|
||||
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
|
||||
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
||||
if case .broadcast = peer.info {
|
||||
@ -6364,17 +6378,21 @@ private final class ChatListLocationContext {
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
|
||||
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
|
||||
if let isLarge = isLarge {
|
||||
if isLarge {
|
||||
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 {
|
||||
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 {
|
||||
return .single(nil)
|
||||
return .single((nil, nil))
|
||||
}
|
||||
}
|
||||
onlineMemberCount = recentOnlineSignal
|
||||
@ -6783,7 +6801,7 @@ private final class ChatListLocationContext {
|
||||
private func updateForum(
|
||||
peerId: EnginePeer.Id,
|
||||
peerView: PeerView,
|
||||
onlineMemberCount: Int32?,
|
||||
onlineMemberCount: (total: Int32?, recent: Int32?),
|
||||
stateAndFilterId: (state: ChatListNodeState, filterId: Int32?),
|
||||
presentationData: PresentationData
|
||||
) {
|
||||
|
@ -35,6 +35,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
let context: AccountContext
|
||||
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void
|
||||
let updateName: (ChatFolderTitle) -> Void
|
||||
let toggleNameInputMode: () -> Void
|
||||
let toggleNameAnimations: () -> Void
|
||||
let openAddIncludePeer: () -> Void
|
||||
let openAddExcludePeer: () -> Void
|
||||
@ -58,6 +59,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
context: AccountContext,
|
||||
updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void,
|
||||
updateName: @escaping (ChatFolderTitle) -> Void,
|
||||
toggleNameInputMode: @escaping () -> Void,
|
||||
toggleNameAnimations: @escaping () -> Void,
|
||||
openAddIncludePeer: @escaping () -> Void,
|
||||
openAddExcludePeer: @escaping () -> Void,
|
||||
@ -80,6 +82,7 @@ private final class ChatListFilterPresetControllerArguments {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
self.updateName = updateName
|
||||
self.toggleNameInputMode = toggleNameInputMode
|
||||
self.toggleNameAnimations = toggleNameAnimations
|
||||
self.openAddIncludePeer = openAddIncludePeer
|
||||
self.openAddExcludePeer = openAddExcludePeer
|
||||
@ -228,7 +231,7 @@ private enum ChatListFilterRevealedItemId: Equatable {
|
||||
|
||||
private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
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 includePeersHeader(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)
|
||||
case let .nameHeader(title, enableAnimations):
|
||||
//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()
|
||||
}, sectionId: self.section)
|
||||
case let .name(placeholder, value, inputMode, enableAnimations):
|
||||
@ -393,15 +400,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||
arguments.updateName(ChatFolderTitle(attributedString: value, enableAnimations: true))
|
||||
},
|
||||
toggleInputMode: {
|
||||
arguments.updateState { current in
|
||||
var state = current
|
||||
if state.nameInputMode == .emoji {
|
||||
state.nameInputMode = .keyboard
|
||||
} else {
|
||||
state.nameInputMode = .emoji
|
||||
}
|
||||
return state
|
||||
}
|
||||
arguments.toggleNameInputMode()
|
||||
}
|
||||
)
|
||||
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 {
|
||||
var name: ChatFolderTitle
|
||||
var changedName: Bool
|
||||
@ -624,7 +592,7 @@ private func chatListFilterPresetControllerEntries(context: AccountContext, pres
|
||||
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(.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: {
|
||||
updateState { current in
|
||||
var name = current.name
|
||||
@ -2145,7 +2125,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
|
||||
return
|
||||
}
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListSingleLineInputItemNode {
|
||||
if let itemNode = itemNode as? ItemListFilterTitleInputItemNode {
|
||||
itemNode.focus()
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import ChatFolderLinkPreviewScreen
|
||||
private final class ChatListFilterPresetListControllerArguments {
|
||||
let context: AccountContext
|
||||
|
||||
let addSuggestedPressed: (String, ChatListFilterData) -> Void
|
||||
let addSuggestedPressed: (ChatFolderTitle, ChatListFilterData) -> Void
|
||||
let openPreset: (ChatListFilter) -> Void
|
||||
let addNew: () -> Void
|
||||
let setItemWithRevealedOptions: (Int32?, Int32?) -> Void
|
||||
@ -24,7 +24,7 @@ private final class ChatListFilterPresetListControllerArguments {
|
||||
let updateDisplayTags: (Bool) -> 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.addSuggestedPressed = addSuggestedPressed
|
||||
self.openPreset = openPreset
|
||||
@ -92,10 +92,10 @@ private struct PresetIndex: Equatable {
|
||||
private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
case screenHeader(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 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 listFooter(String)
|
||||
case displayTags(Bool?)
|
||||
@ -176,7 +176,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||
case let .suggestedListHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
|
||||
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)
|
||||
}, tag: nil)
|
||||
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 {
|
||||
arguments.addNew()
|
||||
} else {
|
||||
@ -285,12 +285,11 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
|
||||
var folderCount = 0
|
||||
for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) {
|
||||
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 {
|
||||
folderCount += 1
|
||||
//TODO:release
|
||||
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))
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,8 +299,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
|
||||
if !filteredSuggestedFilters.isEmpty && actualFilters.count < limits.maxFoldersCount {
|
||||
entries.append(.suggestedListHeader(presentationData.strings.ChatListFolderSettings_RecommendedFoldersSection))
|
||||
for filter in filteredSuggestedFilters {
|
||||
//TODO:release
|
||||
entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title.text, label: filter.description, preset: filter.data))
|
||||
entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title, label: filter.description, preset: filter.data))
|
||||
}
|
||||
if filters.isEmpty {
|
||||
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
|
||||
var filters = filters
|
||||
let id = context.engine.peers.generateNewChatListFilterId(filters: filters)
|
||||
//TODO:release
|
||||
filters.append(.filter(id: id, title: ChatFolderTitle(text: title, entities: [], enableAnimations: true), emoticon: nil, data: data))
|
||||
filters.append(.filter(id: id, title: title, emoticon: nil, data: data))
|
||||
return filters
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
|
@ -7,6 +7,8 @@ import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import TextNodeWithEntities
|
||||
|
||||
struct ChatListFilterPresetListItemEditing: Equatable {
|
||||
let editable: Bool
|
||||
@ -15,9 +17,10 @@ struct ChatListFilterPresetListItemEditing: Equatable {
|
||||
}
|
||||
|
||||
final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let presentationData: ItemListPresentationData
|
||||
let preset: ChatListFilter
|
||||
let title: String
|
||||
let title: ChatFolderTitle
|
||||
let label: String
|
||||
let tagColor: UIColor?
|
||||
let editing: ChatListFilterPresetListItemEditing
|
||||
@ -31,9 +34,10 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
||||
let remove: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
presentationData: ItemListPresentationData,
|
||||
preset: ChatListFilter,
|
||||
title: String,
|
||||
title: ChatFolderTitle,
|
||||
label: String,
|
||||
tagColor: UIColor?,
|
||||
editing: ChatListFilterPresetListItemEditing,
|
||||
@ -46,6 +50,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
||||
setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void,
|
||||
remove: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.preset = preset
|
||||
self.title = title
|
||||
@ -124,7 +129,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
||||
return self.containerNode
|
||||
}
|
||||
|
||||
private let titleNode: TextNode
|
||||
private let titleNode: TextNodeWithEntities
|
||||
private let labelNode: TextNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let sharedIconNode: ASImageNode
|
||||
@ -145,6 +150,15 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
||||
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() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
@ -160,10 +174,11 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
||||
self.maskNode = ASImageNode()
|
||||
self.maskNode.isUserInteractionEnabled = false
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
self.titleNode = TextNodeWithEntities()
|
||||
self.titleNode.textNode.isUserInteractionEnabled = false
|
||||
self.titleNode.textNode.contentMode = .left
|
||||
self.titleNode.textNode.contentsScale = UIScreen.main.scale
|
||||
self.titleNode.resetEmojiToFirstFrameAutomatically = true
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
@ -186,7 +201,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.titleNode)
|
||||
self.containerNode.addSubnode(self.titleNode.textNode)
|
||||
self.containerNode.addSubnode(self.labelNode)
|
||||
self.containerNode.addSubnode(self.arrowNode)
|
||||
self.containerNode.addSubnode(self.sharedIconNode)
|
||||
@ -199,7 +214,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
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 editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||
@ -230,7 +245,11 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
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 reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||
@ -337,9 +356,18 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
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 enableAnimations = item.title.enableAnimations
|
||||
strongSelf.titleNode.visibilityRect = (strongSelf.visibility == ListViewItemNodeVisibility.none || !enableAnimations) ? CGRect.zero : CGRect.infinite
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
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.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)
|
||||
strongSelf.labelNode.frame = labelFrame
|
||||
@ -542,7 +570,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
||||
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
|
||||
labelFrame.origin.x = params.width - rightArrowInset - labelFrame.width + revealOffset
|
||||
|
@ -109,11 +109,13 @@ private final class ItemNode: ASDisplayNode {
|
||||
self.titleNode = ImmediateTextNodeWithEntities()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||
self.titleNode.resetEmojiToFirstFrameAutomatically = true
|
||||
|
||||
self.titleActiveNode = ImmediateTextNodeWithEntities()
|
||||
self.titleActiveNode.displaysAsynchronously = false
|
||||
self.titleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||
self.titleActiveNode.alpha = 0.0
|
||||
self.titleActiveNode.resetEmojiToFirstFrameAutomatically = true
|
||||
|
||||
self.shortTitleContainer = ASDisplayNode()
|
||||
|
||||
@ -121,12 +123,14 @@ private final class ItemNode: ASDisplayNode {
|
||||
self.shortTitleNode.displaysAsynchronously = false
|
||||
self.shortTitleNode.alpha = 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.displaysAsynchronously = false
|
||||
self.shortTitleActiveNode.alpha = 0.0
|
||||
self.shortTitleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||
self.shortTitleActiveNode.alpha = 0.0
|
||||
self.shortTitleActiveNode.resetEmojiToFirstFrameAutomatically = true
|
||||
|
||||
self.badgeContainerNode = ASDisplayNode()
|
||||
|
||||
|
@ -239,7 +239,8 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
|
||||
cache: item.context.animationCache,
|
||||
renderer: item.context.animationRenderer,
|
||||
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor,
|
||||
attemptSynchronous: true
|
||||
attemptSynchronous: true,
|
||||
emojiOffset: CGPoint(x: 0.0, y: -1.0)
|
||||
))
|
||||
let badgeSideInset: CGFloat = 4.0
|
||||
let badgeBackgroundSize: CGSize
|
||||
@ -248,7 +249,7 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
|
||||
} else {
|
||||
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
|
||||
if let current = strongSelf.badgeBackgroundLayer {
|
||||
@ -262,6 +263,7 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
|
||||
if strongSelf.badgeTextNode !== badgeTextNode {
|
||||
strongSelf.badgeTextNode?.textNode.removeFromSupernode()
|
||||
strongSelf.badgeTextNode = badgeTextNode
|
||||
badgeTextNode.resetEmojiToFirstFrameAutomatically = true
|
||||
strongSelf.addSubnode(badgeTextNode.textNode)
|
||||
}
|
||||
|
||||
|
@ -197,12 +197,12 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
} else if case let .user(user) = primaryPeer {
|
||||
let servicePeer = isServicePeer(primaryPeer._asPeer())
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
let presence = peer.presence ?? TelegramUserPresence(status: .none, lastActivity: 0)
|
||||
@ -211,19 +211,19 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
status = .none
|
||||
}
|
||||
} 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 {
|
||||
if case .group = channel.info {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
@ -870,9 +870,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
var status: ContactsPeerItemStatus = .none
|
||||
if case let .user(user) = primaryPeer, let _ = user.botInfo, !primaryPeer.id.isVerificationCodes {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,6 +238,7 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
|
||||
backspaceKeyAction: nil,
|
||||
selection: nil,
|
||||
inputMode: item.inputMode,
|
||||
alwaysDisplayInputModeSelector: true,
|
||||
toggleInputMode: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
@ -268,6 +269,9 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
|
||||
}
|
||||
|
||||
public func focus() {
|
||||
if let textFieldView = self.textField.view as? ListComposePollOptionComponent.View {
|
||||
textFieldView.activateInput()
|
||||
}
|
||||
}
|
||||
|
||||
public func selectAll() {
|
||||
|
@ -28,6 +28,7 @@ import EmojiStatusComponent
|
||||
import AvatarVideoNode
|
||||
import AppBundle
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import ShimmerEffect
|
||||
|
||||
public enum ChatListItemContent {
|
||||
@ -82,10 +83,10 @@ public enum ChatListItemContent {
|
||||
|
||||
public struct Tag: Equatable {
|
||||
public var id: Int32
|
||||
public var title: String
|
||||
public var title: ChatFolderTitle
|
||||
public var colorId: Int32
|
||||
|
||||
public init(id: Int32, title: String, colorId: Int32) {
|
||||
public init(id: Int32, title: ChatFolderTitle, colorId: Int32) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.colorId = colorId
|
||||
@ -269,6 +270,8 @@ private final class ChatListItemTagListComponent: Component {
|
||||
let backgroundView: UIImageView
|
||||
let title = ComponentView<Empty>()
|
||||
|
||||
private var currentTitle: ChatFolderTitle?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.backgroundView = UIImageView(image: tagBackgroundImage)
|
||||
|
||||
@ -281,11 +284,20 @@ private final class ChatListItemTagListComponent: Component {
|
||||
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(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: title.isEmpty ? " " : title, font: Font.semibold(floor(11.0 * sizeFactor)), textColor: foregroundColor))
|
||||
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||
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: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
@ -309,11 +321,30 @@ private final class ChatListItemTagListComponent: Component {
|
||||
|
||||
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 {
|
||||
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) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
@ -332,13 +363,13 @@ private final class ChatListItemTagListComponent: Component {
|
||||
}
|
||||
|
||||
let itemId: Int32
|
||||
let itemTitle: String
|
||||
let itemTitle: ChatFolderTitle
|
||||
let itemBackgroundColor: UIColor
|
||||
let itemForegroundColor: UIColor
|
||||
|
||||
if validIds.count >= 3 {
|
||||
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
|
||||
itemBackgroundColor = itemForegroundColor.withMultipliedAlpha(0.1)
|
||||
} else {
|
||||
@ -347,7 +378,7 @@ private final class ChatListItemTagListComponent: Component {
|
||||
let tagColor = PeerNameColor(rawValue: tag.colorId)
|
||||
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)
|
||||
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 itemFrame = CGRect(origin: CGPoint(x: nextX, y: 0.0), size: itemSize)
|
||||
itemView.frame = itemFrame
|
||||
itemView.updateVisibility(self.isVisible)
|
||||
|
||||
validIds.append(itemId)
|
||||
nextX += itemSize.width
|
||||
@ -1451,6 +1483,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
)
|
||||
}
|
||||
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: {},
|
||||
containerSize: itemTagListFrame.size
|
||||
)
|
||||
if let itemTagListView = itemTagList.view {
|
||||
if let itemTagListView = itemTagList.view as? ChatListItemTagListComponent.View {
|
||||
if itemTagListView.superview == nil {
|
||||
itemTagListView.isUserInteractionEnabled = false
|
||||
strongSelf.mainContentContainerNode.view.addSubview(itemTagListView)
|
||||
}
|
||||
|
||||
itemTagListTransition.updateFrame(view: itemTagListView, frame: itemTagListFrame)
|
||||
itemTagListView.isVisible = strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji
|
||||
}
|
||||
} else {
|
||||
if let itemTagList = strongSelf.itemTagList {
|
||||
|
@ -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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if displayAutoremoveTimeout {
|
||||
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 {
|
||||
return (strings.ChatList_LabelAutodeleteDisabled, false, false, .autoremove)
|
||||
return (NSAttributedString(string: strings.ChatList_LabelAutodeleteDisabled), false, false, .autoremove)
|
||||
}
|
||||
}
|
||||
|
||||
if let chatListFilters = chatListFilters {
|
||||
var result = ""
|
||||
let result = NSMutableAttributedString(string: "")
|
||||
for case let .filter(_, title, _, data) in chatListFilters {
|
||||
let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
|
||||
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
|
||||
if !result.isEmpty {
|
||||
result.append(", ")
|
||||
if result.length != 0 {
|
||||
result.append(NSAttributedString(string: ", "))
|
||||
}
|
||||
//TODO:release
|
||||
result.append(title.text)
|
||||
result.append(title.rawAttributedString)
|
||||
}
|
||||
}
|
||||
|
||||
if result.isEmpty {
|
||||
if result.length == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return (result, true, false, nil)
|
||||
@ -4313,30 +4312,30 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres
|
||||
return nil
|
||||
} else if case let .user(user) = peer {
|
||||
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 {
|
||||
if isContact {
|
||||
return (strings.ChatList_PeerTypeContact, false, false, nil)
|
||||
return (NSAttributedString(string: strings.ChatList_PeerTypeContact), false, false, nil)
|
||||
} else {
|
||||
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil)
|
||||
return (NSAttributedString(string: strings.ChatList_PeerTypeNonContactUser), false, false, nil)
|
||||
}
|
||||
}
|
||||
} else if case .secretChat = peer {
|
||||
if isContact {
|
||||
return (strings.ChatList_PeerTypeContact, false, false, nil)
|
||||
return (NSAttributedString(string: strings.ChatList_PeerTypeContact), false, false, nil)
|
||||
} else {
|
||||
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil)
|
||||
return (NSAttributedString(string: strings.ChatList_PeerTypeNonContactUser), false, false, nil)
|
||||
}
|
||||
} 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 {
|
||||
if case .group = channel.info {
|
||||
return (strings.ChatList_PeerTypeGroup, false, false, nil)
|
||||
return (NSAttributedString(string: strings.ChatList_PeerTypeGroup), false, false, nil)
|
||||
} 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 {
|
||||
@ -4425,10 +4424,9 @@ func chatListItemTags(location: ChatListControllerLocation, accountPeerId: Engin
|
||||
if data.color != nil {
|
||||
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) {
|
||||
//TODO:release
|
||||
result.append(ChatListItemContent.Tag(
|
||||
id: id,
|
||||
title: title.text,
|
||||
title: title,
|
||||
colorId: data.color?.rawValue ?? PeerNameColor.blue.rawValue
|
||||
))
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
||||
public let textStroke: (UIColor, CGFloat)?
|
||||
public let highlightColor: UIColor?
|
||||
public let handleSpoilers: Bool
|
||||
public let manualVisibilityControl: Bool
|
||||
public let resetAnimationsOnVisibilityChange: Bool
|
||||
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
|
||||
public let tapAction: (([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,
|
||||
highlightColor: UIColor? = nil,
|
||||
handleSpoilers: Bool = false,
|
||||
manualVisibilityControl: Bool = false,
|
||||
resetAnimationsOnVisibilityChange: Bool = false,
|
||||
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
|
||||
tapAction: (([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.highlightAction = highlightAction
|
||||
self.handleSpoilers = handleSpoilers
|
||||
self.manualVisibilityControl = manualVisibilityControl
|
||||
self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange
|
||||
self.tapAction = tapAction
|
||||
self.longTapAction = longTapAction
|
||||
}
|
||||
@ -105,6 +111,12 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
||||
if lhs.handleSpoilers != rhs.handleSpoilers {
|
||||
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 !lhsTextShadowColor.isEqual(rhsTextShadowColor) {
|
||||
return false
|
||||
@ -151,6 +163,10 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
||||
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 {
|
||||
let attributedString: NSAttributedString
|
||||
switch component.text {
|
||||
@ -176,6 +192,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
||||
self.textNode.highlightAttributeAction = component.highlightAction
|
||||
self.textNode.tapAttributeAction = component.tapAction
|
||||
self.textNode.longTapAttributeAction = component.longTapAction
|
||||
|
||||
self.textNode.resetEmojiToFirstFrameAutomatically = component.resetAnimationsOnVisibilityChange
|
||||
|
||||
if case let .curve(duration, _) = transition.animation, let previousText = previousText, previousText != attributedString.string {
|
||||
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 {
|
||||
self.textNode.arguments = TextNodeWithEntities.Arguments(
|
||||
context: context,
|
||||
|
@ -82,6 +82,7 @@ public final class ListComposePollOptionComponent: Component {
|
||||
public let backspaceKeyAction: (() -> Void)?
|
||||
public let selection: Selection?
|
||||
public let inputMode: InputMode?
|
||||
public let alwaysDisplayInputModeSelector: Bool
|
||||
public let toggleInputMode: (() -> Void)?
|
||||
public let tag: AnyObject?
|
||||
|
||||
@ -100,6 +101,7 @@ public final class ListComposePollOptionComponent: Component {
|
||||
backspaceKeyAction: (() -> Void)?,
|
||||
selection: Selection?,
|
||||
inputMode: InputMode?,
|
||||
alwaysDisplayInputModeSelector: Bool = false,
|
||||
toggleInputMode: (() -> Void)?,
|
||||
tag: AnyObject? = nil
|
||||
) {
|
||||
@ -117,6 +119,7 @@ public final class ListComposePollOptionComponent: Component {
|
||||
self.backspaceKeyAction = backspaceKeyAction
|
||||
self.selection = selection
|
||||
self.inputMode = inputMode
|
||||
self.alwaysDisplayInputModeSelector = alwaysDisplayInputModeSelector
|
||||
self.toggleInputMode = toggleInputMode
|
||||
self.tag = tag
|
||||
}
|
||||
@ -158,6 +161,9 @@ public final class ListComposePollOptionComponent: Component {
|
||||
if lhs.inputMode != rhs.inputMode {
|
||||
return false
|
||||
}
|
||||
if lhs.alwaysDisplayInputModeSelector != rhs.alwaysDisplayInputModeSelector {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -490,15 +496,17 @@ public final class ListComposePollOptionComponent: Component {
|
||||
ComponentTransition.immediate.setScale(view: modeSelectorView, scale: 0.001)
|
||||
}
|
||||
|
||||
if playAnimation, let animationView = modeSelectorView.contentView as? LottieComponent.View {
|
||||
animationView.playOnce()
|
||||
if let animationView = modeSelectorView.contentView as? LottieComponent.View {
|
||||
if playAnimation {
|
||||
animationView.playOnce()
|
||||
}
|
||||
}
|
||||
|
||||
modeSelectorTransition.setPosition(view: modeSelectorView, position: modeSelectorFrame.center)
|
||||
modeSelectorTransition.setBounds(view: modeSelectorView, bounds: CGRect(origin: CGPoint(), size: modeSelectorFrame.size))
|
||||
|
||||
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.setScale(view: modeSelectorView, scale: displaySelector ? 1.0 : 0.001)
|
||||
|
@ -158,19 +158,19 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
if let _ = peer as? TelegramUser {
|
||||
status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat)
|
||||
} 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 {
|
||||
if case .group = channel.info {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
@ -220,7 +220,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
|
||||
let text: String
|
||||
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
|
||||
|
@ -674,7 +674,7 @@ public class ContactsController: ViewController {
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ private enum InviteContactsEntry: Comparable, Identifiable {
|
||||
case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder):
|
||||
let status: ContactsPeerItemStatus
|
||||
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 {
|
||||
status = .none
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||
"//submodules/MoreButtonNode",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -21,6 +21,8 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EmojiStatusComponent
|
||||
import MoreButtonNode
|
||||
import TextFormat
|
||||
import TextNodeWithEntities
|
||||
|
||||
public final class ContactItemHighlighting {
|
||||
public var chatLocation: ChatLocation?
|
||||
@ -39,7 +41,7 @@ public enum ContactsPeerItemStatus {
|
||||
case none
|
||||
case presence(EnginePeer.Presence, PresentationDateTimeFormat)
|
||||
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 {
|
||||
@ -437,7 +439,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
private var credibilityIconComponent: EmojiStatusComponent?
|
||||
private var verifiedIconView: ComponentHostView<Empty>?
|
||||
private var verifiedIconComponent: EmojiStatusComponent?
|
||||
public let statusNode: TextNode
|
||||
public let statusNode: TextNodeWithEntities
|
||||
private var statusIconNode: ASImageNode?
|
||||
private var badgeBackgroundNode: ASImageNode?
|
||||
private var badgeTextNode: TextNode?
|
||||
@ -519,6 +521,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
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.titleNode = TextNode()
|
||||
self.statusNode = TextNode()
|
||||
self.statusNode = TextNodeWithEntities()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||
|
||||
@ -575,7 +578,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
self.avatarNodeContainer.addSubnode(self.avatarNode)
|
||||
self.offsetContainerNode.addSubnode(self.avatarNodeContainer)
|
||||
self.offsetContainerNode.addSubnode(self.titleNode)
|
||||
self.offsetContainerNode.addSubnode(self.statusNode)
|
||||
self.offsetContainerNode.addSubnode(self.statusNode.textNode)
|
||||
|
||||
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)) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||
let makeStatusLayout = TextNodeWithEntities.asyncLayout(self.statusNode)
|
||||
let currentSelectionNode = self.selectionNode
|
||||
|
||||
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)
|
||||
}
|
||||
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
|
||||
statusIsActive = isActive
|
||||
multilineStatus = multiline
|
||||
@ -964,7 +984,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
switch item.status {
|
||||
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
|
||||
statusIsActive = isActive
|
||||
statusIcon = icon
|
||||
@ -1395,17 +1431,24 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
|
||||
|
||||
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)
|
||||
if let statusIconImage {
|
||||
statusFrame.origin.x += statusIconImage.size.width + 1.0
|
||||
}
|
||||
let previousStatusFrame = strongSelf.statusNode.frame
|
||||
let previousStatusFrame = strongSelf.statusNode.textNode.frame
|
||||
|
||||
strongSelf.statusNode.frame = statusFrame
|
||||
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
|
||||
strongSelf.statusNode.textNode.frame = statusFrame
|
||||
transition.animatePositionAdditive(node: strongSelf.statusNode.textNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
|
||||
|
||||
if let statusIconImage {
|
||||
let statusIconNode: ASImageNode
|
||||
@ -1413,7 +1456,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
statusIconNode = current
|
||||
} else {
|
||||
statusIconNode = ASImageNode()
|
||||
strongSelf.statusNode.addSubnode(statusIconNode)
|
||||
strongSelf.statusNode.textNode.addSubnode(statusIconNode)
|
||||
}
|
||||
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)
|
||||
|
@ -317,7 +317,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
||||
if !self.item.entities.isEmpty {
|
||||
let inputStateText = ChatTextInputStateText(text: self.item.text, attributes: self.item.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
|
||||
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
|
||||
})
|
||||
|
@ -62,7 +62,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
case peer(EnginePeer.Id)
|
||||
}
|
||||
|
||||
case header(String)
|
||||
case header(NSAttributedString)
|
||||
|
||||
case mainLinkHeader(String)
|
||||
case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool)
|
||||
@ -225,13 +225,13 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
private func folderInviteLinkListControllerEntries(
|
||||
presentationData: PresentationData,
|
||||
state: FolderInviteLinkListControllerState,
|
||||
title: String,
|
||||
title: ChatFolderTitle,
|
||||
allPeers: [EnginePeer]
|
||||
) -> [InviteLinksListEntry] {
|
||||
var entries: [InviteLinksListEntry] = []
|
||||
|
||||
var infoString: String?
|
||||
let chatCountString: String
|
||||
let chatCountString: NSAttributedString
|
||||
let peersHeaderString: String
|
||||
|
||||
let canShareChats = !allPeers.allSatisfy({ !canShareLinkToPeer(peer: $0) })
|
||||
@ -241,16 +241,36 @@ private func folderInviteLinkListControllerEntries(
|
||||
|
||||
if !canShareChats {
|
||||
infoString = presentationData.strings.FolderLinkScreen_TitleDescriptionUnavailable
|
||||
chatCountString = presentationData.strings.FolderLinkScreen_ChatCountHeaderUnavailable
|
||||
chatCountString = NSAttributedString(string: presentationData.strings.FolderLinkScreen_ChatCountHeaderUnavailable)
|
||||
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderUnavailable
|
||||
} 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
|
||||
if allPeers.count > 1 {
|
||||
selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
|
||||
}
|
||||
} 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))
|
||||
if allPeers.count > 1 {
|
||||
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)
|
||||
//TODO:release
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: folderInviteLinkListControllerEntries(
|
||||
presentationData: presentationData,
|
||||
state: state,
|
||||
title: filterTitle.text,
|
||||
title: filterTitle,
|
||||
allPeers: allPeers
|
||||
), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||
|
||||
|
@ -11,18 +11,19 @@ import TelegramAnimatedStickerNode
|
||||
import AccountContext
|
||||
import Markdown
|
||||
import TextFormat
|
||||
import TextNodeWithEntities
|
||||
|
||||
public class InviteLinkHeaderItem: ListViewItem, ItemListItem {
|
||||
public let context: AccountContext
|
||||
public let theme: PresentationTheme
|
||||
public let title: String?
|
||||
public let text: String
|
||||
public let text: NSAttributedString
|
||||
public let animationName: String
|
||||
public let hideOnSmallScreens: Bool
|
||||
public let sectionId: ItemListSectionId
|
||||
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.theme = theme
|
||||
self.title = title
|
||||
@ -75,7 +76,7 @@ private let textFont = Font.regular(14.0)
|
||||
|
||||
class InviteLinkHeaderItemNode: ListViewItemNode {
|
||||
private let titleNode: TextNode
|
||||
private let textNode: TextNode
|
||||
private let textNode: TextNodeWithEntities
|
||||
private var animationNode: AnimatedStickerNode
|
||||
|
||||
private var item: InviteLinkHeaderItem?
|
||||
@ -86,17 +87,17 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.contentMode = .left
|
||||
self.textNode.contentsScale = UIScreen.main.scale
|
||||
self.textNode = TextNodeWithEntities()
|
||||
self.textNode.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.textNode.contentMode = .left
|
||||
self.textNode.textNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.textNode.textNode)
|
||||
self.addSubnode(self.animationNode)
|
||||
}
|
||||
|
||||
@ -112,7 +113,7 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
||||
|
||||
func asyncLayout() -> (_ item: InviteLinkHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||
|
||||
return { item, params, neighbors in
|
||||
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 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
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
}))
|
||||
let attributedText = NSMutableAttributedString(string: item.text.string)
|
||||
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()))
|
||||
|
||||
@ -168,8 +182,15 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
||||
origin += titleLayout.size.height + spacing
|
||||
}
|
||||
|
||||
let _ = textApply()
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: origin), size: textLayout.size)
|
||||
let _ = textApply(TextNodeWithEntities.Arguments(
|
||||
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 {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
let textFrame = self.textNode.frame
|
||||
let textFrame = self.textNode.textNode.frame
|
||||
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 {
|
||||
item.linkAction?(.tap(url))
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ private enum InviteLinksListSection: Int32 {
|
||||
}
|
||||
|
||||
private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
case header(PresentationTheme, String)
|
||||
case header(PresentationTheme, NSAttributedString)
|
||||
|
||||
case mainLinkHeader(PresentationTheme, String)
|
||||
case mainLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool)
|
||||
@ -278,7 +278,7 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
||||
} else {
|
||||
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
|
||||
}
|
||||
entries.append(.header(presentationData.theme, helpText))
|
||||
entries.append(.header(presentationData.theme, NSAttributedString(string: helpText)))
|
||||
}
|
||||
|
||||
let mainInvite: ExportedInvitation?
|
||||
|
@ -100,7 +100,7 @@ private enum InviteRequestsEntry: ItemListNodeEntry {
|
||||
let arguments = arguments as! InviteRequestsControllerArguments
|
||||
switch self {
|
||||
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()
|
||||
})
|
||||
case let .requestsHeader(_, text):
|
||||
|
@ -112,7 +112,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
let arguments = arguments as! PaymentMethodListScreenArguments
|
||||
switch self {
|
||||
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):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .addMethod(text):
|
||||
|
@ -158,7 +158,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable {
|
||||
case let .participant(participant, label, revealActions, revealed, enabled):
|
||||
let status: ContactsPeerItemStatus
|
||||
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 {
|
||||
status = .presence(EnginePeer.Presence(presence), dateTimeFormat)
|
||||
} else {
|
||||
|
@ -129,7 +129,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
||||
case let .peer(_, participant, editing, label, enabled, isChannel, isContact):
|
||||
let status: ContactsPeerItemStatus
|
||||
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 {
|
||||
let presence = participant.presences[participant.peer.id] ?? TelegramUserPresence(status: .none, lastActivity: 0)
|
||||
status = .presence(EnginePeer.Presence(presence), presentationData.dateTimeFormat)
|
||||
|
@ -117,7 +117,7 @@ private enum DeleteAccountDataEntry: ItemListNodeEntry, Equatable {
|
||||
let arguments = arguments as! DeleteAccountDataArguments
|
||||
switch self {
|
||||
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):
|
||||
return DeleteAccountPeersItem(context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, peers: peers, sectionId: self.section)
|
||||
case let .info(_, text):
|
||||
|
@ -234,8 +234,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) }
|
||||
dict[-712374074] = { return Api.Dialog.parse_dialog($0) }
|
||||
dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) }
|
||||
dict[1605718587] = { return Api.DialogFilter.parse_dialogFilter($0) }
|
||||
dict[-1612542300] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) }
|
||||
dict[-1438177711] = { return Api.DialogFilter.parse_dialogFilter($0) }
|
||||
dict[-1772913705] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) }
|
||||
dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) }
|
||||
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($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[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($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[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($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[-2073059774] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultChooseOption($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[-1816295539] = { return Api.chatlists.ChatlistUpdates.parse_chatlistUpdates($0) }
|
||||
dict[283567014] = { return Api.chatlists.ExportedChatlistInvite.parse_exportedChatlistInvite($0) }
|
||||
|
@ -373,7 +373,7 @@ public extension Api {
|
||||
case messageActionSetChatTheme(emoticon: String)
|
||||
case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper)
|
||||
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 messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
|
||||
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)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)}
|
||||
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 {
|
||||
buffer.appendInt32(139818551)
|
||||
buffer.appendInt32(-1253342558)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
gift.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 << 7) != 0 {serializeInt32(canExportAt!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .messageActionSuggestProfilePhoto(let photo):
|
||||
if boxed {
|
||||
@ -853,8 +854,8 @@ public extension Api {
|
||||
return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)])
|
||||
case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom):
|
||||
return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)])
|
||||
case .messageActionStarGift(let flags, let gift, let message, let convertStars):
|
||||
return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any)])
|
||||
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), ("canExportAt", canExportAt as Any)])
|
||||
case .messageActionSuggestProfilePhoto(let photo):
|
||||
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
|
||||
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
|
||||
@ -1515,12 +1516,15 @@ public extension Api {
|
||||
} }
|
||||
var _4: Int64?
|
||||
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 _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4)
|
||||
let _c5 = (Int(_1!) & Int(1 << 7) == 0) || _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, canExportAt: _5)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -476,17 +476,17 @@ public extension Api.channels {
|
||||
}
|
||||
public extension Api.chatlists {
|
||||
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])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(500007837)
|
||||
buffer.appendInt32(-250687953)
|
||||
}
|
||||
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)}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(peers.count))
|
||||
@ -545,8 +545,10 @@ public extension Api.chatlists {
|
||||
public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _2: Api.TextWithEntities?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||
}
|
||||
var _3: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
|
||||
var _4: [Api.Peer]?
|
||||
|
@ -1166,19 +1166,19 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
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 dialogFilterChatlist(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [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: Api.TextWithEntities, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer])
|
||||
case dialogFilterDefault
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers):
|
||||
if boxed {
|
||||
buffer.appendInt32(1605718587)
|
||||
buffer.appendInt32(-1438177711)
|
||||
}
|
||||
serializeInt32(flags, 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 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
|
||||
buffer.appendInt32(481674261)
|
||||
@ -1199,11 +1199,11 @@ public extension Api {
|
||||
break
|
||||
case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1612542300)
|
||||
buffer.appendInt32(-1772913705)
|
||||
}
|
||||
serializeInt32(flags, 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 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
|
||||
buffer.appendInt32(481674261)
|
||||
@ -1242,8 +1242,10 @@ public extension Api {
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _3: Api.TextWithEntities?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||
}
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
|
||||
var _5: Int32?
|
||||
@ -1280,8 +1282,10 @@ public extension Api {
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _3: Api.TextWithEntities?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||
}
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
|
||||
var _5: Int32?
|
||||
|
@ -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))
|
||||
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)))
|
||||
case let .messageActionStarGift(flags, apiGift, message, convertStars):
|
||||
case let .messageActionStarGift(flags, apiGift, message, convertStars, _):
|
||||
let text: String?
|
||||
let entities: [MessageTextEntity]?
|
||||
switch message {
|
||||
|
@ -342,9 +342,17 @@ extension ChatListFilter {
|
||||
case .dialogFilterDefault:
|
||||
self = .allChats
|
||||
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(
|
||||
id: id,
|
||||
title: ChatFolderTitle(text: title, entities: [], enableAnimations: true),
|
||||
title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimations),
|
||||
emoticon: emoticon,
|
||||
data: ChatListFilterData(
|
||||
isShared: false,
|
||||
@ -392,9 +400,18 @@ extension ChatListFilter {
|
||||
)
|
||||
)
|
||||
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(
|
||||
id: id,
|
||||
title: ChatFolderTitle(text: title, entities: [], enableAnimations: true),
|
||||
title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimations),
|
||||
emoticon: emoticon,
|
||||
data: ChatListFilterData(
|
||||
isShared: true,
|
||||
@ -446,7 +463,10 @@ extension ChatListFilter {
|
||||
if data.color != nil {
|
||||
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)
|
||||
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
||||
if data.includePeers.pinnedPeers.contains(peerId) {
|
||||
@ -472,7 +492,10 @@ extension ChatListFilter {
|
||||
if data.color != nil {
|
||||
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)
|
||||
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
||||
if data.includePeers.pinnedPeers.contains(peerId) {
|
||||
|
@ -273,9 +273,11 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
||||
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
|
||||
return account.postbox.transaction { transaction -> ChatFolderLinkContents in
|
||||
switch result {
|
||||
case let .chatlistInvite(_, title, emoticon, peers, chats, users):
|
||||
case let .chatlistInvite(flags, title, emoticon, peers, chats, users):
|
||||
let _ = emoticon
|
||||
|
||||
let disableTitleAnimation = (flags & (1 << 1)) != 0
|
||||
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||
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):
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||
var memberCounts: [PeerId: Int] = [:]
|
||||
|
@ -22,6 +22,7 @@ swift_library(
|
||||
"//submodules/WallpaperBackgroundNode",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -11,6 +11,7 @@ import Markdown
|
||||
import WallpaperBackgroundNode
|
||||
import EmojiStatusComponent
|
||||
import TelegramPresentationData
|
||||
import TextNodeWithEntities
|
||||
|
||||
final class BlurredRoundedRectangle: Component {
|
||||
let color: UIColor
|
||||
@ -1210,21 +1211,42 @@ public final class ChatOverscrollControl: CombinedComponent {
|
||||
}
|
||||
|
||||
public final class ChatInputPanelOverscrollNode: ASDisplayNode {
|
||||
public let text: (String, [(Int, NSRange)])
|
||||
public let text: NSAttributedString
|
||||
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.priority = priority
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode = ImmediateTextNodeWithEntities()
|
||||
|
||||
super.init()
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: color)
|
||||
let bold = MarkdownAttributeSet(font: Font.bold(14.0), textColor: color)
|
||||
|
||||
self.titleNode.attributedText = addAttributesToStringWithRanges(text, body: body, argumentAttributes: [0: bold])
|
||||
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: color, 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.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)
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ swift_library(
|
||||
"//submodules/PremiumUI",
|
||||
"//submodules/QrCodeUI",
|
||||
"//submodules/InviteLinksUI",
|
||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -7,6 +7,7 @@ import AccountContext
|
||||
import MultilineTextComponent
|
||||
import TelegramPresentationData
|
||||
import TelegramCore
|
||||
import MultilineTextWithEntitiesComponent
|
||||
|
||||
final class BadgeComponent: Component {
|
||||
let fillColor: UIColor
|
||||
@ -84,17 +85,20 @@ final class BadgeComponent: Component {
|
||||
}
|
||||
|
||||
final class ChatFolderLinkHeaderComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let title: ChatFolderTitle
|
||||
let badge: String?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
title: ChatFolderTitle,
|
||||
badge: String?
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.title = title
|
||||
@ -213,10 +217,16 @@ final class ChatFolderLinkHeaderComponent: Component {
|
||||
}
|
||||
contentWidth += spacing
|
||||
|
||||
//TODO:release
|
||||
let titleSize = self.title.update(
|
||||
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: {},
|
||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||
)
|
||||
|
@ -10,6 +10,7 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import SolidRoundedButtonComponent
|
||||
import PresentationDataUtils
|
||||
import Markdown
|
||||
@ -437,6 +438,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
let topIconSize = self.topIcon.update(
|
||||
transition: contentTransition,
|
||||
component: AnyComponent(ChatFolderLinkHeaderComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
title: component.linkContents?.title ?? ChatFolderTitle(text: "Folder", entities: [], enableAnimations: true),
|
||||
@ -457,37 +459,53 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
contentHeight += topIconSize.height
|
||||
contentHeight += 20.0
|
||||
|
||||
let text: String
|
||||
let text: NSAttributedString
|
||||
if case .linkList = component.subject {
|
||||
text = environment.strings.FolderLinkPreview_TextLinkList
|
||||
text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextLinkList)
|
||||
} else if let linkContents = component.linkContents {
|
||||
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 {
|
||||
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 {
|
||||
text = environment.strings.FolderLinkPreview_TextAddFolder
|
||||
} else {
|
||||
text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextAddFolder, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
|
||||
} else if let title = linkContents.title {
|
||||
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 {
|
||||
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(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(text: text, attributes: MarkdownAttributes(
|
||||
body: body,
|
||||
bold: bold,
|
||||
link: body,
|
||||
linkAttribute: { _ in nil }
|
||||
)),
|
||||
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: environment.theme.list.freeTextColor.withMultipliedAlpha(0.1),
|
||||
text: .plain(text),
|
||||
horizontalAlignment: .center,
|
||||
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.viewTracker.addHiddenChatListFilterIds([folderId]))
|
||||
|
||||
//TODO:release
|
||||
let folderTitle = linkContents.title?.text ?? ""
|
||||
let folderTitle: ChatFolderTitle
|
||||
if let title = linkContents.title {
|
||||
folderTitle = title
|
||||
} else {
|
||||
folderTitle = ChatFolderTitle(text: "", entities: [], enableAnimations: true)
|
||||
}
|
||||
|
||||
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 selectedItems = self.selectedItems
|
||||
let undoOverlayController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .removedChat(title: presentationData.strings.FolderLinkPreview_ToastLeftTitle(folderTitle).string, text: additionalText),
|
||||
content: .removedChat(context: component.context, title: undoText, text: additionalText),
|
||||
elevatedLayout: false,
|
||||
action: { value in
|
||||
if case .commit = value {
|
||||
@ -1112,8 +1141,14 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
}
|
||||
|
||||
if isUpdates {
|
||||
//TODO:release
|
||||
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 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_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 {
|
||||
let animationBackgroundColor: UIColor
|
||||
if presentationData.theme.overallDarkAppearance {
|
||||
@ -1121,8 +1156,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
} else {
|
||||
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 {
|
||||
let animationBackgroundColor: UIColor
|
||||
if presentationData.theme.overallDarkAppearance {
|
||||
@ -1130,8 +1172,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||
} else {
|
||||
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
|
||||
//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
|
||||
(navigationController?.topViewController as? ViewController)?.present(c, in: .window(.root))
|
||||
}))
|
||||
|
@ -90,7 +90,7 @@ public enum ChatTitleContent: Equatable {
|
||||
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 custom(String, String?, Bool)
|
||||
|
||||
@ -104,7 +104,7 @@ public enum ChatTitleContent: Equatable {
|
||||
if customTitle != rhsCustomTitle {
|
||||
return false
|
||||
}
|
||||
if onlineMemberCount != rhsOnlineMemberCount {
|
||||
if onlineMemberCount.0 != rhsOnlineMemberCount.0 || onlineMemberCount.1 != rhsOnlineMemberCount.1 {
|
||||
return false
|
||||
}
|
||||
if isScheduledMessages != rhsIsScheduledMessages {
|
||||
@ -616,7 +616,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
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)
|
||||
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 {
|
||||
let string: NSAttributedString
|
||||
if case .group = channel.info {
|
||||
@ -626,7 +626,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
}
|
||||
state = .info(string, .generic)
|
||||
} 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()
|
||||
|
||||
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
|
||||
|
@ -382,6 +382,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||
}
|
||||
}
|
||||
|
||||
public var enableAnimation: Bool = true
|
||||
|
||||
public weak var mirrorLayer: CALayer? {
|
||||
didSet {
|
||||
if let mirrorLayer = self.mirrorLayer {
|
||||
|
@ -330,7 +330,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS
|
||||
}
|
||||
|
||||
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 {
|
||||
let _ = context.engine.messages.clearHistoryInteractively(peerId: context.account.peerId, threadId: peer.id.toInt64(), type: .forLocalPeer).startStandalone(completed: {
|
||||
guard let self else {
|
||||
|
@ -1659,7 +1659,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
)
|
||||
}
|
||||
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 {
|
||||
onlineMemberCount = context.account.viewTracker.peerView(groupId, updateData: false)
|
||||
|> map { view -> Bool? in
|
||||
@ -1676,17 +1676,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
|
||||
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
|
||||
if let isLarge = isLarge {
|
||||
if isLarge {
|
||||
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 {
|
||||
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 {
|
||||
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),
|
||||
onlineMemberCount
|
||||
)
|
||||
|> map { peerView, onlineMemberCount -> PeerInfoStatusData? in
|
||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
||||
if let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
|
||||
|> map { peerView, memberCountData -> PeerInfoStatusData? in
|
||||
let (preciseTotalMemberCount, onlineMemberCount) = memberCountData
|
||||
|
||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = preciseTotalMemberCount ?? cachedChannelData.participantsSummary.memberCount {
|
||||
if let onlineMemberCount, onlineMemberCount > 1 {
|
||||
var string = ""
|
||||
|
||||
string.append("\(strings.Conversation_StatusMembers(Int32(memberCount))), ")
|
||||
|
@ -172,7 +172,7 @@ private enum OldChannelsEntry: ItemListNodeEntry {
|
||||
case let .peersHeader(title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
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)
|
||||
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ private enum OldChannelsSearchEntry: Comparable, Identifiable {
|
||||
func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
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)
|
||||
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ final class PeerSelectionLoadingView: UIView {
|
||||
context: context,
|
||||
peerMode: .peer,
|
||||
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,
|
||||
requiresPremiumForMessaging: false,
|
||||
enabled: true,
|
||||
@ -242,7 +242,7 @@ final class PeerSelectionLoadingView: UIView {
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
|
@ -119,7 +119,7 @@ final class PeerSelectionScreenComponent: Component {
|
||||
context: listNode.context,
|
||||
peerMode: .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,
|
||||
requiresPremiumForMessaging: false,
|
||||
enabled: true,
|
||||
|
@ -20,11 +20,13 @@ private final class InlineStickerItem: Hashable {
|
||||
let emoji: ChatTextInputTextCustomEmojiAttribute
|
||||
let file: TelegramMediaFile?
|
||||
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.file = file
|
||||
self.fontSize = fontSize
|
||||
self.enableAnimation = enableAnimation
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
@ -42,6 +44,9 @@ private final class InlineStickerItem: Hashable {
|
||||
if lhs.fontSize != rhs.fontSize {
|
||||
return false
|
||||
}
|
||||
if lhs.enableAnimation != rhs.enableAnimation {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -65,19 +70,25 @@ public final class TextNodeWithEntities {
|
||||
public let renderer: MultiAnimationRenderer
|
||||
public let placeholderColor: UIColor
|
||||
public let attemptSynchronous: Bool
|
||||
public let emojiOffset: CGPoint
|
||||
public let fontSizeNorm: CGFloat
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
cache: AnimationCache,
|
||||
renderer: MultiAnimationRenderer,
|
||||
placeholderColor: UIColor,
|
||||
attemptSynchronous: Bool
|
||||
attemptSynchronous: Bool,
|
||||
emojiOffset: CGPoint = CGPoint(),
|
||||
fontSizeNorm: CGFloat = 17.0
|
||||
) {
|
||||
self.context = context
|
||||
self.cache = cache
|
||||
self.renderer = renderer
|
||||
self.placeholderColor = placeholderColor
|
||||
self.attemptSynchronous = attemptSynchronous
|
||||
self.emojiOffset = emojiOffset
|
||||
self.fontSizeNorm = fontSizeNorm
|
||||
}
|
||||
|
||||
public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments {
|
||||
@ -86,7 +97,8 @@ public final class TextNodeWithEntities {
|
||||
cache: self.cache,
|
||||
renderer: self.renderer,
|
||||
placeholderColor: color,
|
||||
attemptSynchronous: self.attemptSynchronous
|
||||
attemptSynchronous: self.attemptSynchronous,
|
||||
emojiOffset: self.emojiOffset
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -96,6 +108,8 @@ public final class TextNodeWithEntities {
|
||||
|
||||
private var enableLooping: Bool = true
|
||||
|
||||
public var resetEmojiToFirstFrameAutomatically: Bool = false
|
||||
|
||||
public var visibilityRect: CGRect? {
|
||||
didSet {
|
||||
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibilityRect {
|
||||
@ -110,7 +124,13 @@ public final class TextNodeWithEntities {
|
||||
} else {
|
||||
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)
|
||||
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)
|
||||
originalTextId += 1
|
||||
|
||||
@ -197,7 +217,7 @@ public final class TextNodeWithEntities {
|
||||
|
||||
if let maybeNode = maybeNode {
|
||||
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
|
||||
@ -205,7 +225,7 @@ public final class TextNodeWithEntities {
|
||||
let resultNode = TextNodeWithEntities(textNode: result)
|
||||
|
||||
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
|
||||
@ -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
|
||||
|
||||
var nextIndexById: [Int64: Int] = [:]
|
||||
@ -241,9 +261,9 @@ public final class TextNodeWithEntities {
|
||||
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
|
||||
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.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)
|
||||
self.inlineStickerItemLayers[id] = 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
|
||||
@ -301,12 +326,19 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
||||
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:]
|
||||
public private(set) var dustNode: InvisibleInkDustNode?
|
||||
|
||||
public var resetEmojiToFirstFrameAutomatically: Bool = false
|
||||
|
||||
public var visibility: Bool = false {
|
||||
didSet {
|
||||
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility {
|
||||
for (_, itemLayer) in self.inlineStickerItemLayers {
|
||||
let isItemVisible: Bool = self.visibility
|
||||
itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible
|
||||
let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
|
||||
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)
|
||||
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)
|
||||
originalTextId += 1
|
||||
|
||||
@ -437,7 +469,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
||||
|
||||
var enableAnimations = true
|
||||
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
|
||||
}
|
||||
self.updateSpoilers(enableAnimations: enableAnimations, textLayout: layout)
|
||||
@ -450,7 +482,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
||||
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
|
||||
|
||||
var nextIndexById: [Int64: Int] = [:]
|
||||
@ -469,7 +501,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
||||
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
|
||||
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)
|
||||
if let file = stickerItem.file, let customItemLayout = self.customItemLayout {
|
||||
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)
|
||||
self.inlineStickerItemLayers[id] = 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
|
||||
@ -535,7 +574,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
||||
let _ = apply()
|
||||
|
||||
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)
|
||||
@ -550,7 +589,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
||||
let _ = apply()
|
||||
|
||||
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
|
||||
|
@ -5221,11 +5221,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let peerId = chatLocationPeerId
|
||||
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = 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)
|
||||
|
||||
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
|
||||
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
||||
if case .broadcast = peer.info {
|
||||
@ -5240,17 +5240,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
|
||||
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
|
||||
if let isLarge = isLarge {
|
||||
if isLarge {
|
||||
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 {
|
||||
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 {
|
||||
return .single(nil)
|
||||
return .single((nil, nil))
|
||||
}
|
||||
}
|
||||
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 {
|
||||
let recentOnlineSignal: Signal<Int32?, NoError> = peerView
|
||||
let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView
|
||||
|> map { view -> Bool? in
|
||||
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
||||
if case .broadcast = peer.info {
|
||||
@ -6209,17 +6213,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
|
||||
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
|
||||
if let isLarge = isLarge {
|
||||
if isLarge {
|
||||
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 {
|
||||
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 {
|
||||
return .single(nil)
|
||||
return .single((nil, nil))
|
||||
}
|
||||
}
|
||||
onlineMemberCount = recentOnlineSignal
|
||||
@ -6321,7 +6329,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
peerPresences: [:],
|
||||
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
|
||||
|
||||
@ -8551,7 +8559,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ extension ChatControllerImpl {
|
||||
self.present(
|
||||
UndoOverlayController(
|
||||
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,
|
||||
action: { [weak self] action in
|
||||
guard let self else {
|
||||
@ -357,7 +357,7 @@ extension ChatControllerImpl {
|
||||
self.chatDisplayNode.historyNode.ignoreMessageIds = Set(messageIds)
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ extension ChatControllerImpl {
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import ChatMessageTransitionNode
|
||||
import ChatControllerInteraction
|
||||
import DustEffect
|
||||
import UrlHandling
|
||||
import TextFormat
|
||||
|
||||
struct ChatTopVisibleMessageRange: Equatable {
|
||||
var lowerBound: MessageIndex
|
||||
@ -2313,41 +2314,57 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
self.currentOverscrollExpandProgress = expandProgress
|
||||
|
||||
if let nextChannelToRead = self.nextChannelToRead {
|
||||
let swipeText: (String, [(Int, NSRange)])
|
||||
let releaseText: (String, [(Int, NSRange)])
|
||||
let swipeText: NSAttributedString
|
||||
let releaseText: NSAttributedString
|
||||
switch nextChannelToRead.location {
|
||||
case .same:
|
||||
if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl, chatController.customChatNavigationStack != nil {
|
||||
swipeText = (self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress, [])
|
||||
releaseText = (self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction, [])
|
||||
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress)
|
||||
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction)
|
||||
} else if nextChannelToRead.threadData != nil {
|
||||
swipeText = (self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress, [])
|
||||
releaseText = (self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction, [])
|
||||
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress)
|
||||
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction)
|
||||
} else {
|
||||
swipeText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress, [])
|
||||
releaseText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction, [])
|
||||
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress)
|
||||
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction)
|
||||
}
|
||||
case .archived:
|
||||
swipeText = (self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress, [])
|
||||
releaseText = (self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction, [])
|
||||
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress)
|
||||
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction)
|
||||
case .unarchived:
|
||||
swipeText = (self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress, [])
|
||||
releaseText = (self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction, [])
|
||||
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress)
|
||||
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction)
|
||||
case let .folder(_, title):
|
||||
//TODO:release
|
||||
swipeText = self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgress(title.text)._tuple
|
||||
releaseText = self.currentPresentationData.strings.Chat_NextChannelFolderSwipeAction(title.text)._tuple
|
||||
let swipeTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgressV2)
|
||||
let swipeFolderRange = (swipeTextValue.string as NSString).range(of: "{folder}")
|
||||
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 {
|
||||
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil)
|
||||
} else if expandProgress >= 1.0 {
|
||||
if chatControllerNode.inputPanelOverscrollNode?.text.0 != releaseText.0 {
|
||||
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1))
|
||||
if chatControllerNode.inputPanelOverscrollNode?.text.string != releaseText.string {
|
||||
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1))
|
||||
}
|
||||
} else {
|
||||
if chatControllerNode.inputPanelOverscrollNode?.text.0 != swipeText.0 {
|
||||
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2))
|
||||
if chatControllerNode.inputPanelOverscrollNode?.text.string != swipeText.string {
|
||||
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -550,7 +550,7 @@ public final class PeerChannelMemberCategoriesContextsManager {
|
||||
|> 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
|
||||
var previousIds: Set<PeerId>?
|
||||
let statusesDisposable = MetaDisposable()
|
||||
@ -587,7 +587,7 @@ public final class PeerChannelMemberCategoriesContextsManager {
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> deliverOnMainQueue).start(next: { count in
|
||||
subscriber.putNext(count)
|
||||
subscriber.putNext((Int32(updatedIds.count), count))
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
@ -402,6 +402,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
|
||||
case file
|
||||
case topicId
|
||||
case topicInfo
|
||||
case enableAnimation
|
||||
}
|
||||
|
||||
public enum Custom: Codable {
|
||||
@ -417,12 +418,14 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
|
||||
public let fileId: Int64
|
||||
public let file: TelegramMediaFile?
|
||||
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.fileId = fileId
|
||||
self.file = file
|
||||
self.custom = custom
|
||||
self.enableAnimation = enableAnimation
|
||||
|
||||
super.init()
|
||||
}
|
||||
@ -433,6 +436,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
|
||||
self.fileId = try container.decode(Int64.self, forKey: .fileId)
|
||||
self.file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file)
|
||||
self.custom = nil
|
||||
self.enableAnimation = try container.decodeIfPresent(Bool.self, forKey: .enableAnimation) ?? true
|
||||
}
|
||||
|
||||
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.encode(self.fileId, forKey: .fileId)
|
||||
try container.encodeIfPresent(self.file, forKey: .file)
|
||||
try container.encode(self.enableAnimation, forKey: .enableAnimation)
|
||||
}
|
||||
|
||||
override public func isEqual(_ object: Any?) -> Bool {
|
||||
|
@ -8,7 +8,7 @@ import ComponentFlow
|
||||
import AnimatedTextComponent
|
||||
|
||||
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 hidArchive(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 stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext)
|
||||
case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?)
|
||||
case chatAddedToFolder(chatTitle: String, folderTitle: String)
|
||||
case chatRemovedFromFolder(chatTitle: String, folderTitle: String)
|
||||
case chatAddedToFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString)
|
||||
case chatRemovedFromFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString)
|
||||
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
|
||||
case setProximityAlert(title: String, text: String, cancelled: Bool)
|
||||
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 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 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 premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?)
|
||||
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)
|
||||
|
@ -46,7 +46,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private var stickerSourceSize: CGSize?
|
||||
private var stickerOffset: CGPoint?
|
||||
private var emojiStatus: ComponentView<Empty>?
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let titleNode: ImmediateTextNodeWithEntities
|
||||
private let textNode: ImmediateTextNodeWithEntities
|
||||
private var textComponent: ComponentView<Empty>?
|
||||
private var animatedTextItems: [AnimatedTextComponent.Item]?
|
||||
@ -92,7 +92,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.timerTextNode = ImmediateTextNode()
|
||||
self.timerTextNode.displaysAsynchronously = false
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode = ImmediateTextNodeWithEntities()
|
||||
self.titleNode.layer.anchorPoint = CGPoint()
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
@ -115,22 +115,51 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
var isUserInteractionEnabled = false
|
||||
switch content {
|
||||
case let .removedChat(title, text):
|
||||
case let .removedChat(context, title, text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = 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 {
|
||||
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 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)
|
||||
self.textNode.attributedText = attributedText
|
||||
} 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
|
||||
self.originalRemainingSeconds = 5
|
||||
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)
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .chatAddedToFolder(chatTitle, folderTitle):
|
||||
case let .chatAddedToFolder(context, chatTitle, folderTitle), let .chatRemovedFromFolder(context, chatTitle, folderTitle):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = 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
|
||||
|
||||
let formattedString = presentationData.strings.ChatList_AddedToFolderTooltip(chatTitle, folderTitle)
|
||||
|
||||
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
|
||||
for range in formattedString.ranges {
|
||||
string.addAttribute(.font, value: Font.regular(14.0), range: range.range)
|
||||
}
|
||||
|
||||
self.textNode.attributedText = string
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .chatRemovedFromFolder(chatTitle, folderTitle):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||
self.animatedStickerNode = nil
|
||||
|
||||
let formattedString = presentationData.strings.ChatList_RemovedFromFolderTooltip(chatTitle, folderTitle)
|
||||
|
||||
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
|
||||
for range in formattedString.ranges {
|
||||
string.addAttribute(.font, value: Font.regular(14.0), range: range.range)
|
||||
let string = NSMutableAttributedString(string: baseString)
|
||||
string.addAttributes([
|
||||
.font: Font.regular(14.0),
|
||||
.foregroundColor: UIColor.white
|
||||
], range: NSRange(location: 0, length: string.length))
|
||||
|
||||
let folderRange = (string.string as NSString).range(of: "{folder}")
|
||||
if folderRange.location != NSNotFound {
|
||||
string.replaceCharacters(in: folderRange, with: "")
|
||||
let processedFolderTitle = NSMutableAttributedString(string: folderTitle.string)
|
||||
processedFolderTitle.addAttributes([
|
||||
.font: Font.semibold(14.0),
|
||||
.foregroundColor: UIColor.white
|
||||
], range: NSRange(location: 0, length: processedFolderTitle.length))
|
||||
folderTitle.enumerateAttributes(in: NSRange(location: 0, length: folderTitle.length), using: { attributes, range, _ in
|
||||
for (key, value) in attributes {
|
||||
if key == ChatTextInputAttributes.bold {
|
||||
processedFolderTitle.addAttribute(.font, value: Font.semibold(14.0), range: range)
|
||||
} else if key == ChatTextInputAttributes.italic {
|
||||
processedFolderTitle.addAttribute(.font, value: Font.italic(14.0), range: range)
|
||||
} else if key == ChatTextInputAttributes.monospace {
|
||||
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.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
|
||||
self.originalRemainingSeconds = 5
|
||||
case let .paymentSent(currencyValue, itemTitle):
|
||||
@ -1072,6 +1130,86 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
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 {
|
||||
undoText = customUndoText
|
||||
displayUndo = true
|
||||
@ -1431,7 +1569,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
} else {
|
||||
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 {
|
||||
self.isUserInteractionEnabled = true
|
||||
} else {
|
||||
|
Loading…
x
Reference in New Issue
Block a user