mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45: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.Channel" = "This channel is verified as official by the representatives of Telegram.";
|
||||||
"PeerInfo.VerificationInfo.Group" = "This group is verified as official by the representatives of Telegram.";
|
"PeerInfo.VerificationInfo.Group" = "This group is verified as official by the representatives of Telegram.";
|
||||||
"PeerInfo.VerificationInfo.URL" = "https://telegram.org/verify";
|
"PeerInfo.VerificationInfo.URL" = "https://telegram.org/verify";
|
||||||
|
|
||||||
|
"ChatList.ToastFolderMutedV2" = "All chats in {folder} are now muted";
|
||||||
|
"ChatList.ToastFolderUnmutedV2" = "All chats in {folder} are now unmuted";
|
||||||
|
"ChatList.AddedToFolderTooltipV2" = "{chat} has been added to folder {folder}";
|
||||||
|
"ChatList.RemovedFromFolderTooltipV2" = "{chat} has been removed from folder {folder}";
|
||||||
|
|
||||||
|
"FolderLinkScreen.TitleDescriptionDeselectedV2" = "Anyone with this link can add {folder} folder and the chats selected below.";
|
||||||
|
"FolderLinkScreen.TitleDescriptionSelectedV2" = "Anyone with this link can add {folder} folder and {chats} selected below.";
|
||||||
|
"Chat.NextChannelFolderSwipeProgressV2" = "Swipe up to go to the {folder} folder";
|
||||||
|
"Chat.NextChannelFolderSwipeActionV2" = "Release to go to the {folder} folder";
|
||||||
|
"FolderLinkPreview.TextAddChatsV2" = "Do you want to add {chats} to the\nfolder {folder}?";
|
||||||
|
"FolderLinkPreview.ToastLeftTitleV2" = "Folder {folder} deleted";
|
||||||
|
"FolderLinkPreview.ToastChatsAddedTitleV2" = "Folder {folder} Updated";
|
||||||
|
"FolderLinkPreview.ToastFolderAddedTitleV2" = "Folder {folder} Added";
|
||||||
|
@ -413,7 +413,7 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
|
|||||||
case monospace
|
case monospace
|
||||||
case textMention(EnginePeer.Id)
|
case textMention(EnginePeer.Id)
|
||||||
case textUrl(String)
|
case textUrl(String)
|
||||||
case customEmoji(stickerPack: StickerPackReference?, fileId: Int64)
|
case customEmoji(stickerPack: StickerPackReference?, fileId: Int64, enableAnimation: Bool)
|
||||||
case strikethrough
|
case strikethrough
|
||||||
case underline
|
case underline
|
||||||
case spoiler
|
case spoiler
|
||||||
@ -440,7 +440,8 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
|
|||||||
case 5:
|
case 5:
|
||||||
let stickerPack = try container.decodeIfPresent(StickerPackReference.self, forKey: "s")
|
let stickerPack = try container.decodeIfPresent(StickerPackReference.self, forKey: "s")
|
||||||
let fileId = try container.decode(Int64.self, forKey: "f")
|
let fileId = try container.decode(Int64.self, forKey: "f")
|
||||||
self = .customEmoji(stickerPack: stickerPack, fileId: fileId)
|
let enableAnimation = try container.decodeIfPresent(Bool.self, forKey: "ea") ?? true
|
||||||
|
self = .customEmoji(stickerPack: stickerPack, fileId: fileId, enableAnimation: enableAnimation)
|
||||||
case 6:
|
case 6:
|
||||||
self = .strikethrough
|
self = .strikethrough
|
||||||
case 7:
|
case 7:
|
||||||
@ -474,10 +475,11 @@ public enum ChatTextInputStateTextAttributeType: Codable, Equatable {
|
|||||||
case let .textUrl(url):
|
case let .textUrl(url):
|
||||||
try container.encode(4 as Int32, forKey: "t")
|
try container.encode(4 as Int32, forKey: "t")
|
||||||
try container.encode(url, forKey: "url")
|
try container.encode(url, forKey: "url")
|
||||||
case let .customEmoji(stickerPack, fileId):
|
case let .customEmoji(stickerPack, fileId, enableAnimation):
|
||||||
try container.encode(5 as Int32, forKey: "t")
|
try container.encode(5 as Int32, forKey: "t")
|
||||||
try container.encodeIfPresent(stickerPack, forKey: "s")
|
try container.encodeIfPresent(stickerPack, forKey: "s")
|
||||||
try container.encode(fileId, forKey: "f")
|
try container.encode(fileId, forKey: "f")
|
||||||
|
try container.encode(enableAnimation, forKey: "ea")
|
||||||
case .strikethrough:
|
case .strikethrough:
|
||||||
try container.encode(6 as Int32, forKey: "t")
|
try container.encode(6 as Int32, forKey: "t")
|
||||||
case .underline:
|
case .underline:
|
||||||
@ -560,7 +562,7 @@ public struct ChatTextInputStateText: Codable, Equatable {
|
|||||||
} else if key == ChatTextInputAttributes.textUrl, let value = value as? ChatTextInputTextUrlAttribute {
|
} else if key == ChatTextInputAttributes.textUrl, let value = value as? ChatTextInputTextUrlAttribute {
|
||||||
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .textUrl(value.url), range: range.location ..< (range.location + range.length)))
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .textUrl(value.url), range: range.location ..< (range.location + range.length)))
|
||||||
} else if key == ChatTextInputAttributes.customEmoji, let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
} else if key == ChatTextInputAttributes.customEmoji, let value = value as? ChatTextInputTextCustomEmojiAttribute {
|
||||||
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: value.fileId), range: range.location ..< (range.location + range.length)))
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: value.fileId, enableAnimation: value.enableAnimation), range: range.location ..< (range.location + range.length)))
|
||||||
} else if key == ChatTextInputAttributes.strikethrough {
|
} else if key == ChatTextInputAttributes.strikethrough {
|
||||||
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .strikethrough, range: range.location ..< (range.location + range.length)))
|
parsedAttributes.append(ChatTextInputStateTextAttribute(type: .strikethrough, range: range.location ..< (range.location + range.length)))
|
||||||
} else if key == ChatTextInputAttributes.underline {
|
} else if key == ChatTextInputAttributes.underline {
|
||||||
@ -618,8 +620,8 @@ public struct ChatTextInputStateText: Codable, Equatable {
|
|||||||
result.addAttribute(ChatTextInputAttributes.textMention, value: ChatTextInputTextMentionAttribute(peerId: id), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
result.addAttribute(ChatTextInputAttributes.textMention, value: ChatTextInputTextMentionAttribute(peerId: id), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||||
case let .textUrl(url):
|
case let .textUrl(url):
|
||||||
result.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
result.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||||
case let .customEmoji(_, fileId):
|
case let .customEmoji(_, fileId, enableAnimation):
|
||||||
result.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
result.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil, enableAnimation: enableAnimation), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||||
case .strikethrough:
|
case .strikethrough:
|
||||||
result.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
result.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||||
case .underline:
|
case .underline:
|
||||||
@ -1204,3 +1206,40 @@ public protocol ChatHistoryListNode: ListView {
|
|||||||
|
|
||||||
var contentPositionChanged: (ListViewVisibleContentOffset) -> Void { get set }
|
var contentPositionChanged: (ListViewVisibleContentOffset) -> Void { get set }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension ChatFolderTitle {
|
||||||
|
init(attributedString: NSAttributedString, enableAnimations: Bool) {
|
||||||
|
let inputStateText = ChatTextInputStateText(attributedText: attributedString)
|
||||||
|
self.init(text: inputStateText.text, entities: inputStateText.attributes.compactMap { attribute -> MessageTextEntity? in
|
||||||
|
if case let .customEmoji(_, fileId, _) = attribute.type {
|
||||||
|
return MessageTextEntity(range: attribute.range, type: .CustomEmoji(stickerPack: nil, fileId: fileId))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}, enableAnimations: enableAnimations)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawAttributedString: NSAttributedString {
|
||||||
|
let inputStateText = ChatTextInputStateText(text: self.text, attributes: self.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
|
||||||
|
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||||
|
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: self.enableAnimations), range: entity.range)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return inputStateText.attributedText()
|
||||||
|
}
|
||||||
|
|
||||||
|
func attributedString(attributes: [NSAttributedString.Key: Any]) -> NSAttributedString {
|
||||||
|
let result = NSMutableAttributedString(attributedString: self.rawAttributedString)
|
||||||
|
result.addAttributes(attributes, range: NSRange(location: 0, length: result.length))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func attributedString(font: UIFont, textColor: UIColor) -> NSAttributedString {
|
||||||
|
let result = NSMutableAttributedString(attributedString: self.rawAttributedString)
|
||||||
|
result.addAttributes([
|
||||||
|
.font: font,
|
||||||
|
.foregroundColor: textColor
|
||||||
|
], range: NSRange(location: 0, length: result.length))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -103,6 +103,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
"//submodules/TelegramUI/Components/Settings/PeerNameColorItem",
|
||||||
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
|
"//submodules/TelegramUI/Components/Settings/BirthdayPickerScreen",
|
||||||
"//submodules/Components/MultilineTextComponent",
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||||
"//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen",
|
"//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen",
|
||||||
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
|
"//submodules/TelegramUI/Components/PeerManagement/OldChannelsController",
|
||||||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||||
|
@ -223,8 +223,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
}
|
}
|
||||||
|> deliverOnMainQueue).startStandalone(completed: {
|
|> deliverOnMainQueue).startStandalone(completed: {
|
||||||
c?.dismiss(completion: {
|
c?.dismiss(completion: {
|
||||||
//TODO:release
|
chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatRemovedFromFolder(context: context, chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.rawAttributedString), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||||
chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatRemovedFromFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
|
||||||
return false
|
return false
|
||||||
}), in: .current)
|
}), in: .current)
|
||||||
})
|
})
|
||||||
@ -274,8 +273,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
let filterType = chatListFilterType(data)
|
let filterType = chatListFilterType(data)
|
||||||
//TODO:release
|
updatedItems.append(.action(ContextMenuActionItem(text: title.text, entities: title.entities, enableEntityAnimations: title.enableAnimations, icon: { theme in
|
||||||
updatedItems.append(.action(ContextMenuActionItem(text: title.text, icon: { theme in
|
|
||||||
let imageName: String
|
let imageName: String
|
||||||
switch filterType {
|
switch filterType {
|
||||||
case .generic:
|
case .generic:
|
||||||
@ -339,8 +337,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
}
|
}
|
||||||
return filters
|
return filters
|
||||||
}).startStandalone()
|
}).startStandalone()
|
||||||
//TODO:release
|
chatListController?.present(UndoOverlayController( presentationData: presentationData, content: .chatAddedToFolder(context: context, chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.rawAttributedString), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||||
chatListController?.present(UndoOverlayController(presentationData: presentationData, content: .chatAddedToFolder(chatTitle: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), folderTitle: title.text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
|
||||||
return false
|
return false
|
||||||
}), in: .current)
|
}), in: .current)
|
||||||
})
|
})
|
||||||
@ -348,7 +345,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c?.setItems(.single(ContextController.Items(content: .list(updatedItems))), minHeight: nil, animated: true)
|
c?.setItems(.single(ContextController.Items(content: .list(updatedItems), context: context)), minHeight: nil, animated: true)
|
||||||
})))
|
})))
|
||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ import PeerInfoStoryGridScreen
|
|||||||
import ArchiveInfoScreen
|
import ArchiveInfoScreen
|
||||||
import BirthdayPickerScreen
|
import BirthdayPickerScreen
|
||||||
import OldChannelsController
|
import OldChannelsController
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||||
let controller: ViewController
|
let controller: ViewController
|
||||||
@ -1453,7 +1454,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
|
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
let contextController = ContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: source, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.peerId, promoInfo: promoInfo, source: .chatList(filter: strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter), chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||||
strongSelf.presentInGlobalOverlay(contextController)
|
strongSelf.presentInGlobalOverlay(contextController)
|
||||||
|
|
||||||
dismissPreviewingImpl = { [weak contextController] in
|
dismissPreviewingImpl = { [weak contextController] in
|
||||||
@ -1526,7 +1527,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
|
contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextController = ContextController(presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
let contextController = ContextController(context: strongSelf.context, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||||
strongSelf.presentInGlobalOverlay(contextController)
|
strongSelf.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1811,28 +1812,42 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:release
|
|
||||||
let iconColor: UIColor = .white
|
let iconColor: UIColor = .white
|
||||||
let overlayController: UndoOverlayController
|
let overlayController: UndoOverlayController
|
||||||
if !filterPeersAreMuted.areMuted {
|
if !filterPeersAreMuted.areMuted {
|
||||||
let text = strongSelf.presentationData.strings.ChatList_ToastFolderMuted(title.text).string
|
let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderMutedV2)
|
||||||
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
|
let folderNameRange = (text.string as NSString).range(of: "{folder}")
|
||||||
|
if folderNameRange.location != NSNotFound {
|
||||||
|
text.replaceCharacters(in: folderNameRange, with: "")
|
||||||
|
text.insert(title.attributedString(attributes: [
|
||||||
|
ChatTextInputAttributes.bold: true
|
||||||
|
]), at: folderNameRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universalWithEntities(context: strongSelf.context, animation: "anim_profilemute", scale: 0.075, colors: [
|
||||||
"Middle.Group 1.Fill 1": iconColor,
|
"Middle.Group 1.Fill 1": iconColor,
|
||||||
"Top.Group 1.Fill 1": iconColor,
|
"Top.Group 1.Fill 1": iconColor,
|
||||||
"Bottom.Group 1.Fill 1": iconColor,
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
"EXAMPLE.Group 1.Fill 1": iconColor,
|
"EXAMPLE.Group 1.Fill 1": iconColor,
|
||||||
"Line.Group 1.Stroke 1": iconColor
|
"Line.Group 1.Stroke 1": iconColor
|
||||||
], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
|
], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
|
||||||
} else {
|
} else {
|
||||||
//TODO:release
|
let text = NSMutableAttributedString(string: strongSelf.presentationData.strings.ChatList_ToastFolderUnmutedV2)
|
||||||
let text = strongSelf.presentationData.strings.ChatList_ToastFolderUnmuted(title.text).string
|
let folderNameRange = (text.string as NSString).range(of: "{folder}")
|
||||||
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
|
if folderNameRange.location != NSNotFound {
|
||||||
|
text.replaceCharacters(in: folderNameRange, with: "")
|
||||||
|
text.insert(title.attributedString(attributes: [
|
||||||
|
ChatTextInputAttributes.bold: true
|
||||||
|
]), at: folderNameRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universalWithEntities(context: strongSelf.context, animation: "anim_profileunmute", scale: 0.075, colors: [
|
||||||
"Middle.Group 1.Fill 1": iconColor,
|
"Middle.Group 1.Fill 1": iconColor,
|
||||||
"Top.Group 1.Fill 1": iconColor,
|
"Top.Group 1.Fill 1": iconColor,
|
||||||
"Bottom.Group 1.Fill 1": iconColor,
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
"EXAMPLE.Group 1.Fill 1": iconColor,
|
"EXAMPLE.Group 1.Fill 1": iconColor,
|
||||||
"Line.Group 1.Stroke 1": iconColor
|
"Line.Group 1.Stroke 1": iconColor
|
||||||
], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
|
], title: nil, text: text, animateEntities: title.enableAnimations, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
|
||||||
}
|
}
|
||||||
strongSelf.present(overlayController, in: .current)
|
strongSelf.present(overlayController, in: .current)
|
||||||
})
|
})
|
||||||
@ -4736,7 +4751,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
let text = strongSelf.presentationData.strings.ChatList_DeletedThreads(Int32(threadIds.count))
|
let text = strongSelf.presentationData.strings.ChatList_DeletedThreads(Int32(threadIds.count))
|
||||||
|
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: text), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -4842,7 +4857,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count))
|
let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count))
|
||||||
|
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: text), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -4914,7 +4929,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count))
|
let text = strongSelf.presentationData.strings.ChatList_DeletedChats(Int32(peerIds.count))
|
||||||
|
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: text), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -5274,7 +5289,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: strongSelf.presentationData.strings.Undo_ChatCleared, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: strongSelf.presentationData.strings.Undo_ChatCleared), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -5496,7 +5511,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
let statusText = self.presentationData.strings.Undo_DeletedTopic
|
let statusText = self.presentationData.strings.Undo_DeletedTopic
|
||||||
|
|
||||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -5793,7 +5808,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -5933,7 +5948,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
badge = ContextMenuActionBadge(value: "\(item.1)", color: item.2 ? .accent : .inactive)
|
badge = ContextMenuActionBadge(value: "\(item.1)", color: item.2 ? .accent : .inactive)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO:release
|
|
||||||
items.append(.action(ContextMenuActionItem(text: title.text, entities: title.entities, enableEntityAnimations: title.enableAnimations, badge: badge, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: title.text, entities: title.entities, enableEntityAnimations: title.enableAnimations, badge: badge, icon: { theme in
|
||||||
let imageName: String
|
let imageName: String
|
||||||
if isDisabled {
|
if isDisabled {
|
||||||
@ -6347,9 +6361,9 @@ private final class ChatListLocationContext {
|
|||||||
let peerView = Promise<PeerView>()
|
let peerView = Promise<PeerView>()
|
||||||
peerView.set(context.account.viewTracker.peerView(peerId))
|
peerView.set(context.account.viewTracker.peerView(peerId))
|
||||||
|
|
||||||
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
|
var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
|
||||||
|
|
||||||
let recentOnlineSignal: Signal<Int32?, NoError> = peerView.get()
|
let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView.get()
|
||||||
|> map { view -> Bool? in
|
|> map { view -> Bool? in
|
||||||
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
||||||
if case .broadcast = peer.info {
|
if case .broadcast = peer.info {
|
||||||
@ -6364,17 +6378,21 @@ private final class ChatListLocationContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
|
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
|
||||||
if let isLarge = isLarge {
|
if let isLarge = isLarge {
|
||||||
if isLarge {
|
if isLarge {
|
||||||
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|
||||||
|> map(Optional.init)
|
|> map { value -> (total: Int32?, recent: Int32?) in
|
||||||
|
return (nil, value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|
||||||
|> map(Optional.init)
|
|> map { value -> (total: Int32?, recent: Int32?) in
|
||||||
|
return (value.total, value.recent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single((nil, nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onlineMemberCount = recentOnlineSignal
|
onlineMemberCount = recentOnlineSignal
|
||||||
@ -6783,7 +6801,7 @@ private final class ChatListLocationContext {
|
|||||||
private func updateForum(
|
private func updateForum(
|
||||||
peerId: EnginePeer.Id,
|
peerId: EnginePeer.Id,
|
||||||
peerView: PeerView,
|
peerView: PeerView,
|
||||||
onlineMemberCount: Int32?,
|
onlineMemberCount: (total: Int32?, recent: Int32?),
|
||||||
stateAndFilterId: (state: ChatListNodeState, filterId: Int32?),
|
stateAndFilterId: (state: ChatListNodeState, filterId: Int32?),
|
||||||
presentationData: PresentationData
|
presentationData: PresentationData
|
||||||
) {
|
) {
|
||||||
|
@ -35,6 +35,7 @@ private final class ChatListFilterPresetControllerArguments {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void
|
let updateState: ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void
|
||||||
let updateName: (ChatFolderTitle) -> Void
|
let updateName: (ChatFolderTitle) -> Void
|
||||||
|
let toggleNameInputMode: () -> Void
|
||||||
let toggleNameAnimations: () -> Void
|
let toggleNameAnimations: () -> Void
|
||||||
let openAddIncludePeer: () -> Void
|
let openAddIncludePeer: () -> Void
|
||||||
let openAddExcludePeer: () -> Void
|
let openAddExcludePeer: () -> Void
|
||||||
@ -58,6 +59,7 @@ private final class ChatListFilterPresetControllerArguments {
|
|||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void,
|
updateState: @escaping ((ChatListFilterPresetControllerState) -> ChatListFilterPresetControllerState) -> Void,
|
||||||
updateName: @escaping (ChatFolderTitle) -> Void,
|
updateName: @escaping (ChatFolderTitle) -> Void,
|
||||||
|
toggleNameInputMode: @escaping () -> Void,
|
||||||
toggleNameAnimations: @escaping () -> Void,
|
toggleNameAnimations: @escaping () -> Void,
|
||||||
openAddIncludePeer: @escaping () -> Void,
|
openAddIncludePeer: @escaping () -> Void,
|
||||||
openAddExcludePeer: @escaping () -> Void,
|
openAddExcludePeer: @escaping () -> Void,
|
||||||
@ -80,6 +82,7 @@ private final class ChatListFilterPresetControllerArguments {
|
|||||||
self.context = context
|
self.context = context
|
||||||
self.updateState = updateState
|
self.updateState = updateState
|
||||||
self.updateName = updateName
|
self.updateName = updateName
|
||||||
|
self.toggleNameInputMode = toggleNameInputMode
|
||||||
self.toggleNameAnimations = toggleNameAnimations
|
self.toggleNameAnimations = toggleNameAnimations
|
||||||
self.openAddIncludePeer = openAddIncludePeer
|
self.openAddIncludePeer = openAddIncludePeer
|
||||||
self.openAddExcludePeer = openAddExcludePeer
|
self.openAddExcludePeer = openAddExcludePeer
|
||||||
@ -228,7 +231,7 @@ private enum ChatListFilterRevealedItemId: Equatable {
|
|||||||
|
|
||||||
private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
||||||
case screenHeader
|
case screenHeader
|
||||||
case nameHeader(title: String, enableAnimations: Bool)
|
case nameHeader(title: String, enableAnimations: Bool?)
|
||||||
case name(placeholder: String, value: NSAttributedString, inputMode: ListComposePollOptionComponent.InputMode?, enableAnimations: Bool)
|
case name(placeholder: String, value: NSAttributedString, inputMode: ListComposePollOptionComponent.InputMode?, enableAnimations: Bool)
|
||||||
case includePeersHeader(String)
|
case includePeersHeader(String)
|
||||||
case addIncludePeer(title: String)
|
case addIncludePeer(title: String)
|
||||||
@ -376,7 +379,11 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: "", animation: .newFolder, sectionId: self.section)
|
return ChatListFilterSettingsHeaderItem(context: arguments.context, theme: presentationData.theme, text: "", animation: .newFolder, sectionId: self.section)
|
||||||
case let .nameHeader(title, enableAnimations):
|
case let .nameHeader(title, enableAnimations):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, actionText: enableAnimations ? "Disable Animations" : "Enable Animations", action: {
|
var actionText: String?
|
||||||
|
if let enableAnimations {
|
||||||
|
actionText = enableAnimations ? "Disable Animations" : "Enable Animations"
|
||||||
|
}
|
||||||
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, actionText: actionText, action: {
|
||||||
arguments.toggleNameAnimations()
|
arguments.toggleNameAnimations()
|
||||||
}, sectionId: self.section)
|
}, sectionId: self.section)
|
||||||
case let .name(placeholder, value, inputMode, enableAnimations):
|
case let .name(placeholder, value, inputMode, enableAnimations):
|
||||||
@ -393,15 +400,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
arguments.updateName(ChatFolderTitle(attributedString: value, enableAnimations: true))
|
arguments.updateName(ChatFolderTitle(attributedString: value, enableAnimations: true))
|
||||||
},
|
},
|
||||||
toggleInputMode: {
|
toggleInputMode: {
|
||||||
arguments.updateState { current in
|
arguments.toggleNameInputMode()
|
||||||
var state = current
|
|
||||||
if state.nameInputMode == .emoji {
|
|
||||||
state.nameInputMode = .keyboard
|
|
||||||
} else {
|
|
||||||
state.nameInputMode = .emoji
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
case .includePeersHeader(let text), .excludePeersHeader(let text):
|
case .includePeersHeader(let text), .excludePeersHeader(let text):
|
||||||
@ -545,37 +544,6 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ChatFolderTitle {
|
|
||||||
init(attributedString: NSAttributedString, enableAnimations: Bool) {
|
|
||||||
let inputStateText = ChatTextInputStateText(attributedText: attributedString)
|
|
||||||
self.init(text: inputStateText.text, entities: inputStateText.attributes.compactMap { attribute -> MessageTextEntity? in
|
|
||||||
if case let .customEmoji(_, fileId) = attribute.type {
|
|
||||||
return MessageTextEntity(range: attribute.range, type: .CustomEmoji(stickerPack: nil, fileId: fileId))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}, enableAnimations: enableAnimations)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawAttributedString: NSAttributedString {
|
|
||||||
let inputStateText = ChatTextInputStateText(text: self.text, attributes: self.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
|
|
||||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
|
||||||
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId), range: entity.range)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return inputStateText.attributedText()
|
|
||||||
}
|
|
||||||
|
|
||||||
func attributedString(font: UIFont, textColor: UIColor) -> NSAttributedString {
|
|
||||||
let result = NSMutableAttributedString(attributedString: self.rawAttributedString)
|
|
||||||
result.addAttributes([
|
|
||||||
.font: font,
|
|
||||||
.foregroundColor: textColor
|
|
||||||
], range: NSRange(location: 0, length: result.length))
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct ChatListFilterPresetControllerState: Equatable {
|
private struct ChatListFilterPresetControllerState: Equatable {
|
||||||
var name: ChatFolderTitle
|
var name: ChatFolderTitle
|
||||||
var changedName: Bool
|
var changedName: Bool
|
||||||
@ -624,7 +592,7 @@ private func chatListFilterPresetControllerEntries(context: AccountContext, pres
|
|||||||
entries.append(.screenHeader)
|
entries.append(.screenHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.nameHeader(title: presentationData.strings.ChatListFolder_NameSectionHeader, enableAnimations: state.name.enableAnimations))
|
entries.append(.nameHeader(title: presentationData.strings.ChatListFolder_NameSectionHeader, enableAnimations: state.name.entities.isEmpty ? nil : state.name.enableAnimations))
|
||||||
entries.append(.name(placeholder: presentationData.strings.ChatListFolder_NamePlaceholder, value: state.name.rawAttributedString, inputMode: state.nameInputMode, enableAnimations: state.name.enableAnimations))
|
entries.append(.name(placeholder: presentationData.strings.ChatListFolder_NamePlaceholder, value: state.name.rawAttributedString, inputMode: state.nameInputMode, enableAnimations: state.name.enableAnimations))
|
||||||
|
|
||||||
entries.append(.includePeersHeader(presentationData.strings.ChatListFolder_IncludedSectionHeader))
|
entries.append(.includePeersHeader(presentationData.strings.ChatListFolder_IncludedSectionHeader))
|
||||||
@ -1584,6 +1552,18 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
toggleNameInputMode: {
|
||||||
|
updateState { current in
|
||||||
|
var state = current
|
||||||
|
if state.nameInputMode == .emoji {
|
||||||
|
state.nameInputMode = .keyboard
|
||||||
|
} else {
|
||||||
|
state.nameInputMode = .emoji
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
focusOnNameImpl?()
|
||||||
|
},
|
||||||
toggleNameAnimations: {
|
toggleNameAnimations: {
|
||||||
updateState { current in
|
updateState { current in
|
||||||
var name = current.name
|
var name = current.name
|
||||||
@ -2145,7 +2125,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.forEachItemNode { itemNode in
|
controller.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? ItemListSingleLineInputItemNode {
|
if let itemNode = itemNode as? ItemListFilterTitleInputItemNode {
|
||||||
itemNode.focus()
|
itemNode.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import ChatFolderLinkPreviewScreen
|
|||||||
private final class ChatListFilterPresetListControllerArguments {
|
private final class ChatListFilterPresetListControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
|
|
||||||
let addSuggestedPressed: (String, ChatListFilterData) -> Void
|
let addSuggestedPressed: (ChatFolderTitle, ChatListFilterData) -> Void
|
||||||
let openPreset: (ChatListFilter) -> Void
|
let openPreset: (ChatListFilter) -> Void
|
||||||
let addNew: () -> Void
|
let addNew: () -> Void
|
||||||
let setItemWithRevealedOptions: (Int32?, Int32?) -> Void
|
let setItemWithRevealedOptions: (Int32?, Int32?) -> Void
|
||||||
@ -24,7 +24,7 @@ private final class ChatListFilterPresetListControllerArguments {
|
|||||||
let updateDisplayTags: (Bool) -> Void
|
let updateDisplayTags: (Bool) -> Void
|
||||||
let updateDisplayTagsLocked: () -> Void
|
let updateDisplayTagsLocked: () -> Void
|
||||||
|
|
||||||
init(context: AccountContext, addSuggestedPressed: @escaping (String, ChatListFilterData) -> Void, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void, updateDisplayTags: @escaping (Bool) -> Void, updateDisplayTagsLocked: @escaping () -> Void) {
|
init(context: AccountContext, addSuggestedPressed: @escaping (ChatFolderTitle, ChatListFilterData) -> Void, openPreset: @escaping (ChatListFilter) -> Void, addNew: @escaping () -> Void, setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void, removePreset: @escaping (Int32) -> Void, updateDisplayTags: @escaping (Bool) -> Void, updateDisplayTagsLocked: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.addSuggestedPressed = addSuggestedPressed
|
self.addSuggestedPressed = addSuggestedPressed
|
||||||
self.openPreset = openPreset
|
self.openPreset = openPreset
|
||||||
@ -92,10 +92,10 @@ private struct PresetIndex: Equatable {
|
|||||||
private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
||||||
case screenHeader(String)
|
case screenHeader(String)
|
||||||
case suggestedListHeader(String)
|
case suggestedListHeader(String)
|
||||||
case suggestedPreset(index: PresetIndex, title: String, label: String, preset: ChatListFilterData)
|
case suggestedPreset(index: PresetIndex, title: ChatFolderTitle, label: String, preset: ChatListFilterData)
|
||||||
case suggestedAddCustom(String)
|
case suggestedAddCustom(String)
|
||||||
case listHeader(String)
|
case listHeader(String)
|
||||||
case preset(index: PresetIndex, title: String, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool, isAllChats: Bool, isDisabled: Bool, displayTags: Bool)
|
case preset(index: PresetIndex, title: ChatFolderTitle, label: String, preset: ChatListFilter, canBeReordered: Bool, canBeDeleted: Bool, isEditing: Bool, isAllChats: Bool, isDisabled: Bool, displayTags: Bool)
|
||||||
case addItem(text: String, isEditing: Bool)
|
case addItem(text: String, isEditing: Bool)
|
||||||
case listFooter(String)
|
case listFooter(String)
|
||||||
case displayTags(Bool?)
|
case displayTags(Bool?)
|
||||||
@ -176,7 +176,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
|||||||
case let .suggestedListHeader(text):
|
case let .suggestedListHeader(text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
|
||||||
case let .suggestedPreset(_, title, label, preset):
|
case let .suggestedPreset(_, title, label, preset):
|
||||||
return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, title: title, label: label, sectionId: self.section, style: .blocks, installAction: {
|
return ChatListFilterPresetListSuggestedItem(presentationData: presentationData, title: title.text, label: label, sectionId: self.section, style: .blocks, installAction: {
|
||||||
arguments.addSuggestedPressed(title, preset)
|
arguments.addSuggestedPressed(title, preset)
|
||||||
}, tag: nil)
|
}, tag: nil)
|
||||||
case let .suggestedAddCustom(text):
|
case let .suggestedAddCustom(text):
|
||||||
@ -194,7 +194,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChatListFilterPresetListItem(presentationData: presentationData, preset: preset, title: title, label: label, tagColor: resolvedColor, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: {
|
return ChatListFilterPresetListItem(context: arguments.context, presentationData: presentationData, preset: preset, title: title, label: label, tagColor: resolvedColor, editing: ChatListFilterPresetListItemEditing(editable: true, editing: isEditing, revealed: false), canBeReordered: canBeReordered, canBeDeleted: canBeDeleted, isAllChats: isAllChats, isDisabled: isDisabled, sectionId: self.section, action: {
|
||||||
if isDisabled {
|
if isDisabled {
|
||||||
arguments.addNew()
|
arguments.addNew()
|
||||||
} else {
|
} else {
|
||||||
@ -285,12 +285,11 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
|
|||||||
var folderCount = 0
|
var folderCount = 0
|
||||||
for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) {
|
for (filter, chatCount) in filtersWithAppliedOrder(filters: filters, order: updatedFilterOrder) {
|
||||||
if case .allChats = filter {
|
if case .allChats = filter {
|
||||||
entries.append(.preset(index: PresetIndex(value: entries.count), title: "", label: "", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: false, isEditing: state.isEditing, isAllChats: true, isDisabled: false, displayTags: effectiveDisplayTags == true))
|
entries.append(.preset(index: PresetIndex(value: entries.count), title: ChatFolderTitle(text: "", entities: [], enableAnimations: true), label: "", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: false, isEditing: state.isEditing, isAllChats: true, isDisabled: false, displayTags: effectiveDisplayTags == true))
|
||||||
}
|
}
|
||||||
if case let .filter(_, title, _, _) = filter {
|
if case let .filter(_, title, _, _) = filter {
|
||||||
folderCount += 1
|
folderCount += 1
|
||||||
//TODO:release
|
entries.append(.preset(index: PresetIndex(value: entries.count), title: title, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing, isAllChats: false, isDisabled: !isPremium && folderCount > limits.maxFoldersCount, displayTags: effectiveDisplayTags == true))
|
||||||
entries.append(.preset(index: PresetIndex(value: entries.count), title: title.text, label: chatCount == 0 ? "" : "\(chatCount)", preset: filter, canBeReordered: filters.count > 1, canBeDeleted: true, isEditing: state.isEditing, isAllChats: false, isDisabled: !isPremium && folderCount > limits.maxFoldersCount, displayTags: effectiveDisplayTags == true))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,8 +299,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
|
|||||||
if !filteredSuggestedFilters.isEmpty && actualFilters.count < limits.maxFoldersCount {
|
if !filteredSuggestedFilters.isEmpty && actualFilters.count < limits.maxFoldersCount {
|
||||||
entries.append(.suggestedListHeader(presentationData.strings.ChatListFolderSettings_RecommendedFoldersSection))
|
entries.append(.suggestedListHeader(presentationData.strings.ChatListFolderSettings_RecommendedFoldersSection))
|
||||||
for filter in filteredSuggestedFilters {
|
for filter in filteredSuggestedFilters {
|
||||||
//TODO:release
|
entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title, label: filter.description, preset: filter.data))
|
||||||
entries.append(.suggestedPreset(index: PresetIndex(value: entries.count), title: filter.title.text, label: filter.description, preset: filter.data))
|
|
||||||
}
|
}
|
||||||
if filters.isEmpty {
|
if filters.isEmpty {
|
||||||
entries.append(.suggestedAddCustom(presentationData.strings.ChatListFolderSettings_RecommendedNewFolder))
|
entries.append(.suggestedAddCustom(presentationData.strings.ChatListFolderSettings_RecommendedNewFolder))
|
||||||
@ -389,8 +387,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||||||
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
||||||
var filters = filters
|
var filters = filters
|
||||||
let id = context.engine.peers.generateNewChatListFilterId(filters: filters)
|
let id = context.engine.peers.generateNewChatListFilterId(filters: filters)
|
||||||
//TODO:release
|
filters.append(.filter(id: id, title: title, emoticon: nil, data: data))
|
||||||
filters.append(.filter(id: id, title: ChatFolderTitle(text: title, entities: [], enableAnimations: true), emoticon: nil, data: data))
|
|
||||||
return filters
|
return filters
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { _ in
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
|
@ -7,6 +7,8 @@ import TelegramCore
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ItemListUI
|
import ItemListUI
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
|
import AccountContext
|
||||||
|
import TextNodeWithEntities
|
||||||
|
|
||||||
struct ChatListFilterPresetListItemEditing: Equatable {
|
struct ChatListFilterPresetListItemEditing: Equatable {
|
||||||
let editable: Bool
|
let editable: Bool
|
||||||
@ -15,9 +17,10 @@ struct ChatListFilterPresetListItemEditing: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
||||||
|
let context: AccountContext
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let preset: ChatListFilter
|
let preset: ChatListFilter
|
||||||
let title: String
|
let title: ChatFolderTitle
|
||||||
let label: String
|
let label: String
|
||||||
let tagColor: UIColor?
|
let tagColor: UIColor?
|
||||||
let editing: ChatListFilterPresetListItemEditing
|
let editing: ChatListFilterPresetListItemEditing
|
||||||
@ -31,9 +34,10 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
|||||||
let remove: () -> Void
|
let remove: () -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
|
context: AccountContext,
|
||||||
presentationData: ItemListPresentationData,
|
presentationData: ItemListPresentationData,
|
||||||
preset: ChatListFilter,
|
preset: ChatListFilter,
|
||||||
title: String,
|
title: ChatFolderTitle,
|
||||||
label: String,
|
label: String,
|
||||||
tagColor: UIColor?,
|
tagColor: UIColor?,
|
||||||
editing: ChatListFilterPresetListItemEditing,
|
editing: ChatListFilterPresetListItemEditing,
|
||||||
@ -46,6 +50,7 @@ final class ChatListFilterPresetListItem: ListViewItem, ItemListItem {
|
|||||||
setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void,
|
setItemWithRevealedOptions: @escaping (Int32?, Int32?) -> Void,
|
||||||
remove: @escaping () -> Void
|
remove: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.preset = preset
|
self.preset = preset
|
||||||
self.title = title
|
self.title = title
|
||||||
@ -124,7 +129,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
return self.containerNode
|
return self.containerNode
|
||||||
}
|
}
|
||||||
|
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNodeWithEntities
|
||||||
private let labelNode: TextNode
|
private let labelNode: TextNode
|
||||||
private let arrowNode: ASImageNode
|
private let arrowNode: ASImageNode
|
||||||
private let sharedIconNode: ASImageNode
|
private let sharedIconNode: ASImageNode
|
||||||
@ -145,6 +150,15 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var visibility: ListViewItemNodeVisibility {
|
||||||
|
didSet {
|
||||||
|
if self.visibility != oldValue {
|
||||||
|
let enableAnimations = self.item?.title.enableAnimations ?? true
|
||||||
|
self.titleNode.visibilityRect = (self.visibility == ListViewItemNodeVisibility.none || !enableAnimations) ? CGRect.zero : CGRect.infinite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
@ -160,10 +174,11 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.maskNode = ASImageNode()
|
self.maskNode = ASImageNode()
|
||||||
self.maskNode.isUserInteractionEnabled = false
|
self.maskNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNodeWithEntities()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.textNode.isUserInteractionEnabled = false
|
||||||
self.titleNode.contentMode = .left
|
self.titleNode.textNode.contentMode = .left
|
||||||
self.titleNode.contentsScale = UIScreen.main.scale
|
self.titleNode.textNode.contentsScale = UIScreen.main.scale
|
||||||
|
self.titleNode.resetEmojiToFirstFrameAutomatically = true
|
||||||
|
|
||||||
self.labelNode = TextNode()
|
self.labelNode = TextNode()
|
||||||
self.labelNode.isUserInteractionEnabled = false
|
self.labelNode.isUserInteractionEnabled = false
|
||||||
@ -186,7 +201,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||||
|
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.containerNode.addSubnode(self.titleNode)
|
self.containerNode.addSubnode(self.titleNode.textNode)
|
||||||
self.containerNode.addSubnode(self.labelNode)
|
self.containerNode.addSubnode(self.labelNode)
|
||||||
self.containerNode.addSubnode(self.arrowNode)
|
self.containerNode.addSubnode(self.arrowNode)
|
||||||
self.containerNode.addSubnode(self.sharedIconNode)
|
self.containerNode.addSubnode(self.sharedIconNode)
|
||||||
@ -199,7 +214,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: ChatListFilterPresetListItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
func asyncLayout() -> (_ item: ChatListFilterPresetListItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNodeWithEntities.asyncLayout(self.titleNode)
|
||||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||||
@ -230,7 +245,11 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let titleAttributedString = NSMutableAttributedString()
|
let titleAttributedString = NSMutableAttributedString()
|
||||||
titleAttributedString.append(NSAttributedString(string: item.isAllChats ? item.presentationData.strings.ChatList_FolderAllChats : item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor))
|
if item.isAllChats {
|
||||||
|
titleAttributedString.append(NSAttributedString(string: item.presentationData.strings.ChatList_FolderAllChats, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor))
|
||||||
|
} else {
|
||||||
|
titleAttributedString.append(item.title.attributedString(font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor))
|
||||||
|
}
|
||||||
|
|
||||||
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
|
var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)?
|
||||||
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
var reorderControlSizeAndApply: (CGFloat, (CGFloat, Bool, ContainedViewLayoutTransition) -> ItemListEditableReorderControlNode)?
|
||||||
@ -337,9 +356,18 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
strongSelf.editableControlNode?.isHidden = !item.canBeDeleted
|
strongSelf.editableControlNode?.isHidden = !item.canBeDeleted
|
||||||
|
|
||||||
let _ = titleApply()
|
let _ = titleApply(TextNodeWithEntities.Arguments(
|
||||||
|
context: item.context,
|
||||||
|
cache: item.context.animationCache,
|
||||||
|
renderer: item.context.animationRenderer,
|
||||||
|
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor,
|
||||||
|
attemptSynchronous: true
|
||||||
|
))
|
||||||
let _ = labelApply()
|
let _ = labelApply()
|
||||||
|
|
||||||
|
let enableAnimations = item.title.enableAnimations
|
||||||
|
strongSelf.titleNode.visibilityRect = (strongSelf.visibility == ListViewItemNodeVisibility.none || !enableAnimations) ? CGRect.zero : CGRect.infinite
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||||
}
|
}
|
||||||
@ -385,7 +413,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
||||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size))
|
transition.updateFrame(node: strongSelf.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 11.0), size: titleLayout.size))
|
||||||
|
|
||||||
let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: 11.0), size: labelLayout.size)
|
let labelFrame = CGRect(origin: CGPoint(x: params.width - rightArrowInset - labelLayout.size.width + revealOffset, y: 11.0), size: labelLayout.size)
|
||||||
strongSelf.labelNode.frame = labelFrame
|
strongSelf.labelNode.frame = labelFrame
|
||||||
@ -542,7 +570,7 @@ final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
editingOffset = 0.0
|
editingOffset = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + offset + editingOffset, y: self.titleNode.frame.minY), size: self.titleNode.bounds.size))
|
transition.updateFrame(node: self.titleNode.textNode, frame: CGRect(origin: CGPoint(x: leftInset + offset + editingOffset, y: self.titleNode.textNode.frame.minY), size: self.titleNode.textNode.bounds.size))
|
||||||
|
|
||||||
var labelFrame = self.labelNode.frame
|
var labelFrame = self.labelNode.frame
|
||||||
labelFrame.origin.x = params.width - rightArrowInset - labelFrame.width + revealOffset
|
labelFrame.origin.x = params.width - rightArrowInset - labelFrame.width + revealOffset
|
||||||
|
@ -109,11 +109,13 @@ private final class ItemNode: ASDisplayNode {
|
|||||||
self.titleNode = ImmediateTextNodeWithEntities()
|
self.titleNode = ImmediateTextNodeWithEntities()
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||||
|
self.titleNode.resetEmojiToFirstFrameAutomatically = true
|
||||||
|
|
||||||
self.titleActiveNode = ImmediateTextNodeWithEntities()
|
self.titleActiveNode = ImmediateTextNodeWithEntities()
|
||||||
self.titleActiveNode.displaysAsynchronously = false
|
self.titleActiveNode.displaysAsynchronously = false
|
||||||
self.titleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
self.titleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||||
self.titleActiveNode.alpha = 0.0
|
self.titleActiveNode.alpha = 0.0
|
||||||
|
self.titleActiveNode.resetEmojiToFirstFrameAutomatically = true
|
||||||
|
|
||||||
self.shortTitleContainer = ASDisplayNode()
|
self.shortTitleContainer = ASDisplayNode()
|
||||||
|
|
||||||
@ -121,12 +123,14 @@ private final class ItemNode: ASDisplayNode {
|
|||||||
self.shortTitleNode.displaysAsynchronously = false
|
self.shortTitleNode.displaysAsynchronously = false
|
||||||
self.shortTitleNode.alpha = 0.0
|
self.shortTitleNode.alpha = 0.0
|
||||||
self.shortTitleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
self.shortTitleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||||
|
self.shortTitleNode.resetEmojiToFirstFrameAutomatically = true
|
||||||
|
|
||||||
self.shortTitleActiveNode = ImmediateTextNodeWithEntities()
|
self.shortTitleActiveNode = ImmediateTextNodeWithEntities()
|
||||||
self.shortTitleActiveNode.displaysAsynchronously = false
|
self.shortTitleActiveNode.displaysAsynchronously = false
|
||||||
self.shortTitleActiveNode.alpha = 0.0
|
self.shortTitleActiveNode.alpha = 0.0
|
||||||
self.shortTitleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
self.shortTitleActiveNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
|
||||||
self.shortTitleActiveNode.alpha = 0.0
|
self.shortTitleActiveNode.alpha = 0.0
|
||||||
|
self.shortTitleActiveNode.resetEmojiToFirstFrameAutomatically = true
|
||||||
|
|
||||||
self.badgeContainerNode = ASDisplayNode()
|
self.badgeContainerNode = ASDisplayNode()
|
||||||
|
|
||||||
|
@ -239,7 +239,8 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
|
|||||||
cache: item.context.animationCache,
|
cache: item.context.animationCache,
|
||||||
renderer: item.context.animationRenderer,
|
renderer: item.context.animationRenderer,
|
||||||
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor,
|
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor,
|
||||||
attemptSynchronous: true
|
attemptSynchronous: true,
|
||||||
|
emojiOffset: CGPoint(x: 0.0, y: -1.0)
|
||||||
))
|
))
|
||||||
let badgeSideInset: CGFloat = 4.0
|
let badgeSideInset: CGFloat = 4.0
|
||||||
let badgeBackgroundSize: CGSize
|
let badgeBackgroundSize: CGSize
|
||||||
@ -248,7 +249,7 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
|
|||||||
} else {
|
} else {
|
||||||
badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + badgeLayoutAndApply.0.size.width, height: badgeLayoutAndApply.0.size.height + 3.0)
|
badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + badgeLayoutAndApply.0.size.width, height: badgeLayoutAndApply.0.size.height + 3.0)
|
||||||
}
|
}
|
||||||
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + badgeSpacing, y: strongSelf.titleNode.frame.minY - UIScreenPixel + floorToScreenPixels((strongSelf.titleNode.bounds.height - badgeBackgroundSize.height) * 0.5)), size: badgeBackgroundSize)
|
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + badgeSpacing, y: strongSelf.titleNode.frame.minY + floorToScreenPixels((strongSelf.titleNode.bounds.height - badgeBackgroundSize.height) * 0.5)), size: badgeBackgroundSize)
|
||||||
|
|
||||||
let badgeBackgroundLayer: SimpleLayer
|
let badgeBackgroundLayer: SimpleLayer
|
||||||
if let current = strongSelf.badgeBackgroundLayer {
|
if let current = strongSelf.badgeBackgroundLayer {
|
||||||
@ -262,6 +263,7 @@ public class ChatListFilterTagSectionHeaderItemNode: ListViewItemNode {
|
|||||||
if strongSelf.badgeTextNode !== badgeTextNode {
|
if strongSelf.badgeTextNode !== badgeTextNode {
|
||||||
strongSelf.badgeTextNode?.textNode.removeFromSupernode()
|
strongSelf.badgeTextNode?.textNode.removeFromSupernode()
|
||||||
strongSelf.badgeTextNode = badgeTextNode
|
strongSelf.badgeTextNode = badgeTextNode
|
||||||
|
badgeTextNode.resetEmojiToFirstFrameAutomatically = true
|
||||||
strongSelf.addSubnode(badgeTextNode.textNode)
|
strongSelf.addSubnode(badgeTextNode.textNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,12 +197,12 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
} else if case let .user(user) = primaryPeer {
|
} else if case let .user(user) = primaryPeer {
|
||||||
let servicePeer = isServicePeer(primaryPeer._asPeer())
|
let servicePeer = isServicePeer(primaryPeer._asPeer())
|
||||||
if user.flags.contains(.isSupport) && !servicePeer {
|
if user.flags.contains(.isSupport) && !servicePeer {
|
||||||
status = .custom(string: strings.Bot_GenericSupportStatus, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Bot_GenericSupportStatus), multiline: false, isActive: false, icon: nil)
|
||||||
} else if let _ = user.botInfo {
|
} else if let _ = user.botInfo {
|
||||||
if let subscriberCount = user.subscriberCount {
|
if let subscriberCount = user.subscriberCount {
|
||||||
status = .custom(string: strings.Conversation_StatusBotSubscribers(subscriberCount), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Conversation_StatusBotSubscribers(subscriberCount)), multiline: false, isActive: false, icon: nil)
|
||||||
} else {
|
} else {
|
||||||
status = .custom(string: strings.Bot_GenericBotStatus, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Bot_GenericBotStatus), multiline: false, isActive: false, icon: nil)
|
||||||
}
|
}
|
||||||
} else if user.id != context.account.peerId && !servicePeer {
|
} else if user.id != context.account.peerId && !servicePeer {
|
||||||
let presence = peer.presence ?? TelegramUserPresence(status: .none, lastActivity: 0)
|
let presence = peer.presence ?? TelegramUserPresence(status: .none, lastActivity: 0)
|
||||||
@ -211,19 +211,19 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
status = .none
|
status = .none
|
||||||
}
|
}
|
||||||
} else if case let .legacyGroup(group) = primaryPeer {
|
} else if case let .legacyGroup(group) = primaryPeer {
|
||||||
status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(group.participantCount)), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.GroupInfo_ParticipantCount(Int32(group.participantCount))), multiline: false, isActive: false, icon: nil)
|
||||||
} else if case let .channel(channel) = primaryPeer {
|
} else if case let .channel(channel) = primaryPeer {
|
||||||
if case .group = channel.info {
|
if case .group = channel.info {
|
||||||
if let count = peer.subpeerSummary?.count, count > 0 {
|
if let count = peer.subpeerSummary?.count, count > 0 {
|
||||||
status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(count)), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.GroupInfo_ParticipantCount(Int32(count))), multiline: false, isActive: false, icon: nil)
|
||||||
} else {
|
} else {
|
||||||
status = .custom(string: strings.Group_Status, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Group_Status), multiline: false, isActive: false, icon: nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let count = peer.subpeerSummary?.count, count > 0 {
|
if let count = peer.subpeerSummary?.count, count > 0 {
|
||||||
status = .custom(string: strings.Conversation_StatusSubscribers(Int32(count)), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Conversation_StatusSubscribers(Int32(count))), multiline: false, isActive: false, icon: nil)
|
||||||
} else {
|
} else {
|
||||||
status = .custom(string: strings.Channel_Status, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Channel_Status), multiline: false, isActive: false, icon: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -870,9 +870,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
var status: ContactsPeerItemStatus = .none
|
var status: ContactsPeerItemStatus = .none
|
||||||
if case let .user(user) = primaryPeer, let _ = user.botInfo, !primaryPeer.id.isVerificationCodes {
|
if case let .user(user) = primaryPeer, let _ = user.botInfo, !primaryPeer.id.isVerificationCodes {
|
||||||
if let subscriberCount = user.subscriberCount {
|
if let subscriberCount = user.subscriberCount {
|
||||||
status = .custom(string: presentationData.strings.Conversation_StatusBotSubscribers(subscriberCount), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: presentationData.strings.Conversation_StatusBotSubscribers(subscriberCount)), multiline: false, isActive: false, icon: nil)
|
||||||
} else {
|
} else {
|
||||||
status = .custom(string: presentationData.strings.Bot_GenericBotStatus, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: presentationData.strings.Bot_GenericBotStatus), multiline: false, isActive: false, icon: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +238,7 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
|
|||||||
backspaceKeyAction: nil,
|
backspaceKeyAction: nil,
|
||||||
selection: nil,
|
selection: nil,
|
||||||
inputMode: item.inputMode,
|
inputMode: item.inputMode,
|
||||||
|
alwaysDisplayInputModeSelector: true,
|
||||||
toggleInputMode: { [weak self] in
|
toggleInputMode: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
@ -268,6 +269,9 @@ public class ItemListFilterTitleInputItemNode: ListViewItemNode, UITextFieldDele
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func focus() {
|
public func focus() {
|
||||||
|
if let textFieldView = self.textField.view as? ListComposePollOptionComponent.View {
|
||||||
|
textFieldView.activateInput()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func selectAll() {
|
public func selectAll() {
|
||||||
|
@ -28,6 +28,7 @@ import EmojiStatusComponent
|
|||||||
import AvatarVideoNode
|
import AvatarVideoNode
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
|
import MultilineTextWithEntitiesComponent
|
||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
|
||||||
public enum ChatListItemContent {
|
public enum ChatListItemContent {
|
||||||
@ -82,10 +83,10 @@ public enum ChatListItemContent {
|
|||||||
|
|
||||||
public struct Tag: Equatable {
|
public struct Tag: Equatable {
|
||||||
public var id: Int32
|
public var id: Int32
|
||||||
public var title: String
|
public var title: ChatFolderTitle
|
||||||
public var colorId: Int32
|
public var colorId: Int32
|
||||||
|
|
||||||
public init(id: Int32, title: String, colorId: Int32) {
|
public init(id: Int32, title: ChatFolderTitle, colorId: Int32) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.title = title
|
self.title = title
|
||||||
self.colorId = colorId
|
self.colorId = colorId
|
||||||
@ -269,6 +270,8 @@ private final class ChatListItemTagListComponent: Component {
|
|||||||
let backgroundView: UIImageView
|
let backgroundView: UIImageView
|
||||||
let title = ComponentView<Empty>()
|
let title = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var currentTitle: ChatFolderTitle?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.backgroundView = UIImageView(image: tagBackgroundImage)
|
self.backgroundView = UIImageView(image: tagBackgroundImage)
|
||||||
|
|
||||||
@ -281,11 +284,20 @@ private final class ChatListItemTagListComponent: Component {
|
|||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(context: AccountContext, title: String, backgroundColor: UIColor, foregroundColor: UIColor, sizeFactor: CGFloat) -> CGSize {
|
func update(context: AccountContext, title: ChatFolderTitle, backgroundColor: UIColor, foregroundColor: UIColor, sizeFactor: CGFloat) -> CGSize {
|
||||||
|
self.currentTitle = title
|
||||||
|
|
||||||
|
let titleValue = ChatFolderTitle(text: title.text.isEmpty ? " " : title.text, entities: title.entities, enableAnimations: title.enableAnimations)
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||||
text: .plain(NSAttributedString(string: title.isEmpty ? " " : title, font: Font.semibold(floor(11.0 * sizeFactor)), textColor: foregroundColor))
|
context: context,
|
||||||
|
animationCache: context.animationCache,
|
||||||
|
animationRenderer: context.animationRenderer,
|
||||||
|
placeholderColor: foregroundColor.withMultipliedAlpha(0.1),
|
||||||
|
text: .plain(titleValue.attributedString(font: Font.semibold(floor(11.0 * sizeFactor)), textColor: foregroundColor)),
|
||||||
|
manualVisibilityControl: true,
|
||||||
|
resetAnimationsOnVisibilityChange: true
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
@ -309,11 +321,30 @@ private final class ChatListItemTagListComponent: Component {
|
|||||||
|
|
||||||
return backgroundSize
|
return backgroundSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateVisibility(_ isVisible: Bool) {
|
||||||
|
guard let currentTitle = self.currentTitle else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let titleView = self.title.view as? MultilineTextWithEntitiesComponent.View {
|
||||||
|
titleView.updateVisibility(isVisible && currentTitle.enableAnimations)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView {
|
final class View: UIView {
|
||||||
private var itemViews: [Int32: ItemView] = [:]
|
private var itemViews: [Int32: ItemView] = [:]
|
||||||
|
|
||||||
|
var isVisible: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.isVisible != oldValue {
|
||||||
|
for (_, itemView) in self.itemViews {
|
||||||
|
itemView.updateVisibility(self.isVisible)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
}
|
}
|
||||||
@ -332,13 +363,13 @@ private final class ChatListItemTagListComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let itemId: Int32
|
let itemId: Int32
|
||||||
let itemTitle: String
|
let itemTitle: ChatFolderTitle
|
||||||
let itemBackgroundColor: UIColor
|
let itemBackgroundColor: UIColor
|
||||||
let itemForegroundColor: UIColor
|
let itemForegroundColor: UIColor
|
||||||
|
|
||||||
if validIds.count >= 3 {
|
if validIds.count >= 3 {
|
||||||
itemId = Int32.max
|
itemId = Int32.max
|
||||||
itemTitle = "+\(component.tags.count - validIds.count)"
|
itemTitle = ChatFolderTitle(text: "+\(component.tags.count - validIds.count)", entities: [], enableAnimations: true)
|
||||||
itemForegroundColor = component.theme.chatList.dateTextColor
|
itemForegroundColor = component.theme.chatList.dateTextColor
|
||||||
itemBackgroundColor = itemForegroundColor.withMultipliedAlpha(0.1)
|
itemBackgroundColor = itemForegroundColor.withMultipliedAlpha(0.1)
|
||||||
} else {
|
} else {
|
||||||
@ -347,7 +378,7 @@ private final class ChatListItemTagListComponent: Component {
|
|||||||
let tagColor = PeerNameColor(rawValue: tag.colorId)
|
let tagColor = PeerNameColor(rawValue: tag.colorId)
|
||||||
let resolvedColor = component.context.peerNameColors.getChatFolderTag(tagColor, dark: component.theme.overallDarkAppearance)
|
let resolvedColor = component.context.peerNameColors.getChatFolderTag(tagColor, dark: component.theme.overallDarkAppearance)
|
||||||
|
|
||||||
itemTitle = tag.title.uppercased()
|
itemTitle = ChatFolderTitle(text: tag.title.text.uppercased(), entities: tag.title.entities, enableAnimations: tag.title.enableAnimations)
|
||||||
itemBackgroundColor = resolvedColor.main.withMultipliedAlpha(0.1)
|
itemBackgroundColor = resolvedColor.main.withMultipliedAlpha(0.1)
|
||||||
itemForegroundColor = resolvedColor.main
|
itemForegroundColor = resolvedColor.main
|
||||||
}
|
}
|
||||||
@ -364,6 +395,7 @@ private final class ChatListItemTagListComponent: Component {
|
|||||||
let itemSize = itemView.update(context: component.context, title: itemTitle, backgroundColor: itemBackgroundColor, foregroundColor: itemForegroundColor, sizeFactor: component.sizeFactor)
|
let itemSize = itemView.update(context: component.context, title: itemTitle, backgroundColor: itemBackgroundColor, foregroundColor: itemForegroundColor, sizeFactor: component.sizeFactor)
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: nextX, y: 0.0), size: itemSize)
|
let itemFrame = CGRect(origin: CGPoint(x: nextX, y: 0.0), size: itemSize)
|
||||||
itemView.frame = itemFrame
|
itemView.frame = itemFrame
|
||||||
|
itemView.updateVisibility(self.isVisible)
|
||||||
|
|
||||||
validIds.append(itemId)
|
validIds.append(itemId)
|
||||||
nextX += itemSize.width
|
nextX += itemSize.width
|
||||||
@ -1451,6 +1483,10 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
self.authorNode.visibilityStatus = self.visibilityStatus
|
self.authorNode.visibilityStatus = self.visibilityStatus
|
||||||
|
|
||||||
|
if let itemTagListView = self.itemTagList?.view as? ChatListItemTagListComponent.View {
|
||||||
|
itemTagListView.isVisible = self.visibilityStatus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4243,13 +4279,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: itemTagListFrame.size
|
containerSize: itemTagListFrame.size
|
||||||
)
|
)
|
||||||
if let itemTagListView = itemTagList.view {
|
if let itemTagListView = itemTagList.view as? ChatListItemTagListComponent.View {
|
||||||
if itemTagListView.superview == nil {
|
if itemTagListView.superview == nil {
|
||||||
itemTagListView.isUserInteractionEnabled = false
|
itemTagListView.isUserInteractionEnabled = false
|
||||||
strongSelf.mainContentContainerNode.view.addSubview(itemTagListView)
|
strongSelf.mainContentContainerNode.view.addSubview(itemTagListView)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemTagListTransition.updateFrame(view: itemTagListView, frame: itemTagListFrame)
|
itemTagListTransition.updateFrame(view: itemTagListView, frame: itemTagListFrame)
|
||||||
|
itemTagListView.isVisible = strongSelf.visibilityStatus && item.context.sharedContext.energyUsageSettings.loopEmoji
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let itemTagList = strongSelf.itemTagList {
|
if let itemTagList = strongSelf.itemTagList {
|
||||||
|
@ -4276,33 +4276,32 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: PresentationStrings, peer: EnginePeer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, autoremoveTimeout: Int32?) -> (String, Bool, Bool, ContactsPeerItemStatus.Icon?)? {
|
private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: PresentationStrings, peer: EnginePeer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, autoremoveTimeout: Int32?) -> (NSAttributedString, Bool, Bool, ContactsPeerItemStatus.Icon?)? {
|
||||||
if accountPeerId == peer.id {
|
if accountPeerId == peer.id {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if displayAutoremoveTimeout {
|
if displayAutoremoveTimeout {
|
||||||
if let autoremoveTimeout = autoremoveTimeout {
|
if let autoremoveTimeout = autoremoveTimeout {
|
||||||
return (strings.ChatList_LabelAutodeleteAfter(timeIntervalString(strings: strings, value: autoremoveTimeout, usage: .afterTime)).string, false, true, .autoremove)
|
return (NSAttributedString(string: strings.ChatList_LabelAutodeleteAfter(timeIntervalString(strings: strings, value: autoremoveTimeout, usage: .afterTime)).string), false, true, .autoremove)
|
||||||
} else {
|
} else {
|
||||||
return (strings.ChatList_LabelAutodeleteDisabled, false, false, .autoremove)
|
return (NSAttributedString(string: strings.ChatList_LabelAutodeleteDisabled), false, false, .autoremove)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let chatListFilters = chatListFilters {
|
if let chatListFilters = chatListFilters {
|
||||||
var result = ""
|
let result = NSMutableAttributedString(string: "")
|
||||||
for case let .filter(_, title, _, data) in chatListFilters {
|
for case let .filter(_, title, _, data) in chatListFilters {
|
||||||
let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
|
let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
|
||||||
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
|
if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
|
||||||
if !result.isEmpty {
|
if result.length != 0 {
|
||||||
result.append(", ")
|
result.append(NSAttributedString(string: ", "))
|
||||||
}
|
}
|
||||||
//TODO:release
|
result.append(title.rawAttributedString)
|
||||||
result.append(title.text)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.isEmpty {
|
if result.length == 0 {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return (result, true, false, nil)
|
return (result, true, false, nil)
|
||||||
@ -4313,30 +4312,30 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres
|
|||||||
return nil
|
return nil
|
||||||
} else if case let .user(user) = peer {
|
} else if case let .user(user) = peer {
|
||||||
if user.botInfo != nil || user.flags.contains(.isSupport) {
|
if user.botInfo != nil || user.flags.contains(.isSupport) {
|
||||||
return (strings.ChatList_PeerTypeBot, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeBot), false, false, nil)
|
||||||
} else {
|
} else {
|
||||||
if isContact {
|
if isContact {
|
||||||
return (strings.ChatList_PeerTypeContact, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeContact), false, false, nil)
|
||||||
} else {
|
} else {
|
||||||
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeNonContactUser), false, false, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if case .secretChat = peer {
|
} else if case .secretChat = peer {
|
||||||
if isContact {
|
if isContact {
|
||||||
return (strings.ChatList_PeerTypeContact, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeContact), false, false, nil)
|
||||||
} else {
|
} else {
|
||||||
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeNonContactUser), false, false, nil)
|
||||||
}
|
}
|
||||||
} else if case .legacyGroup = peer {
|
} else if case .legacyGroup = peer {
|
||||||
return (strings.ChatList_PeerTypeGroup, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeGroup), false, false, nil)
|
||||||
} else if case let .channel(channel) = peer {
|
} else if case let .channel(channel) = peer {
|
||||||
if case .group = channel.info {
|
if case .group = channel.info {
|
||||||
return (strings.ChatList_PeerTypeGroup, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeGroup), false, false, nil)
|
||||||
} else {
|
} else {
|
||||||
return (strings.ChatList_PeerTypeChannel, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeChannel), false, false, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (strings.ChatList_PeerTypeNonContactUser, false, false, nil)
|
return (NSAttributedString(string: strings.ChatList_PeerTypeNonContactUser), false, false, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
|
public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
|
||||||
@ -4425,10 +4424,9 @@ func chatListItemTags(location: ChatListControllerLocation, accountPeerId: Engin
|
|||||||
if data.color != nil {
|
if data.color != nil {
|
||||||
let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
|
let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
|
||||||
if predicate.pinnedPeerIds.contains(peer.id) || predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
|
if predicate.pinnedPeerIds.contains(peer.id) || predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) {
|
||||||
//TODO:release
|
|
||||||
result.append(ChatListItemContent.Tag(
|
result.append(ChatListItemContent.Tag(
|
||||||
id: id,
|
id: id,
|
||||||
title: title.text,
|
title: title,
|
||||||
colorId: data.color?.rawValue ?? PeerNameColor.blue.rawValue
|
colorId: data.color?.rawValue ?? PeerNameColor.blue.rawValue
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||||||
public let textStroke: (UIColor, CGFloat)?
|
public let textStroke: (UIColor, CGFloat)?
|
||||||
public let highlightColor: UIColor?
|
public let highlightColor: UIColor?
|
||||||
public let handleSpoilers: Bool
|
public let handleSpoilers: Bool
|
||||||
|
public let manualVisibilityControl: Bool
|
||||||
|
public let resetAnimationsOnVisibilityChange: Bool
|
||||||
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
|
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
|
||||||
public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
|
public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
|
||||||
public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
|
public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
|
||||||
@ -52,6 +54,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||||||
textStroke: (UIColor, CGFloat)? = nil,
|
textStroke: (UIColor, CGFloat)? = nil,
|
||||||
highlightColor: UIColor? = nil,
|
highlightColor: UIColor? = nil,
|
||||||
handleSpoilers: Bool = false,
|
handleSpoilers: Bool = false,
|
||||||
|
manualVisibilityControl: Bool = false,
|
||||||
|
resetAnimationsOnVisibilityChange: Bool = false,
|
||||||
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
|
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
|
||||||
tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil,
|
tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil,
|
||||||
longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil
|
longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil
|
||||||
@ -73,6 +77,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||||||
self.highlightColor = highlightColor
|
self.highlightColor = highlightColor
|
||||||
self.highlightAction = highlightAction
|
self.highlightAction = highlightAction
|
||||||
self.handleSpoilers = handleSpoilers
|
self.handleSpoilers = handleSpoilers
|
||||||
|
self.manualVisibilityControl = manualVisibilityControl
|
||||||
|
self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange
|
||||||
self.tapAction = tapAction
|
self.tapAction = tapAction
|
||||||
self.longTapAction = longTapAction
|
self.longTapAction = longTapAction
|
||||||
}
|
}
|
||||||
@ -105,6 +111,12 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||||||
if lhs.handleSpoilers != rhs.handleSpoilers {
|
if lhs.handleSpoilers != rhs.handleSpoilers {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.manualVisibilityControl != rhs.manualVisibilityControl {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.resetAnimationsOnVisibilityChange != rhs.resetAnimationsOnVisibilityChange {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor {
|
if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor {
|
||||||
if !lhsTextShadowColor.isEqual(rhsTextShadowColor) {
|
if !lhsTextShadowColor.isEqual(rhsTextShadowColor) {
|
||||||
return false
|
return false
|
||||||
@ -151,6 +163,10 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func updateVisibility(_ isVisible: Bool) {
|
||||||
|
self.textNode.visibility = isVisible
|
||||||
|
}
|
||||||
|
|
||||||
public func update(component: MultilineTextWithEntitiesComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
public func update(component: MultilineTextWithEntitiesComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||||
let attributedString: NSAttributedString
|
let attributedString: NSAttributedString
|
||||||
switch component.text {
|
switch component.text {
|
||||||
@ -176,6 +192,8 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||||||
self.textNode.highlightAttributeAction = component.highlightAction
|
self.textNode.highlightAttributeAction = component.highlightAction
|
||||||
self.textNode.tapAttributeAction = component.tapAction
|
self.textNode.tapAttributeAction = component.tapAction
|
||||||
self.textNode.longTapAttributeAction = component.longTapAction
|
self.textNode.longTapAttributeAction = component.longTapAction
|
||||||
|
|
||||||
|
self.textNode.resetEmojiToFirstFrameAutomatically = component.resetAnimationsOnVisibilityChange
|
||||||
|
|
||||||
if case let .curve(duration, _) = transition.animation, let previousText = previousText, previousText != attributedString.string {
|
if case let .curve(duration, _) = transition.animation, let previousText = previousText, previousText != attributedString.string {
|
||||||
if let snapshotView = self.snapshotContentTree() {
|
if let snapshotView = self.snapshotContentTree() {
|
||||||
@ -189,7 +207,9 @@ public final class MultilineTextWithEntitiesComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.textNode.visibility = true
|
if !component.manualVisibilityControl {
|
||||||
|
self.textNode.visibility = true
|
||||||
|
}
|
||||||
if let context = component.context, let animationCache = component.animationCache, let animationRenderer = component.animationRenderer, let placeholderColor = component.placeholderColor {
|
if let context = component.context, let animationCache = component.animationCache, let animationRenderer = component.animationRenderer, let placeholderColor = component.placeholderColor {
|
||||||
self.textNode.arguments = TextNodeWithEntities.Arguments(
|
self.textNode.arguments = TextNodeWithEntities.Arguments(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -82,6 +82,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
public let backspaceKeyAction: (() -> Void)?
|
public let backspaceKeyAction: (() -> Void)?
|
||||||
public let selection: Selection?
|
public let selection: Selection?
|
||||||
public let inputMode: InputMode?
|
public let inputMode: InputMode?
|
||||||
|
public let alwaysDisplayInputModeSelector: Bool
|
||||||
public let toggleInputMode: (() -> Void)?
|
public let toggleInputMode: (() -> Void)?
|
||||||
public let tag: AnyObject?
|
public let tag: AnyObject?
|
||||||
|
|
||||||
@ -100,6 +101,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
backspaceKeyAction: (() -> Void)?,
|
backspaceKeyAction: (() -> Void)?,
|
||||||
selection: Selection?,
|
selection: Selection?,
|
||||||
inputMode: InputMode?,
|
inputMode: InputMode?,
|
||||||
|
alwaysDisplayInputModeSelector: Bool = false,
|
||||||
toggleInputMode: (() -> Void)?,
|
toggleInputMode: (() -> Void)?,
|
||||||
tag: AnyObject? = nil
|
tag: AnyObject? = nil
|
||||||
) {
|
) {
|
||||||
@ -117,6 +119,7 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
self.backspaceKeyAction = backspaceKeyAction
|
self.backspaceKeyAction = backspaceKeyAction
|
||||||
self.selection = selection
|
self.selection = selection
|
||||||
self.inputMode = inputMode
|
self.inputMode = inputMode
|
||||||
|
self.alwaysDisplayInputModeSelector = alwaysDisplayInputModeSelector
|
||||||
self.toggleInputMode = toggleInputMode
|
self.toggleInputMode = toggleInputMode
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
}
|
}
|
||||||
@ -158,6 +161,9 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
if lhs.inputMode != rhs.inputMode {
|
if lhs.inputMode != rhs.inputMode {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.alwaysDisplayInputModeSelector != rhs.alwaysDisplayInputModeSelector {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -490,15 +496,17 @@ public final class ListComposePollOptionComponent: Component {
|
|||||||
ComponentTransition.immediate.setScale(view: modeSelectorView, scale: 0.001)
|
ComponentTransition.immediate.setScale(view: modeSelectorView, scale: 0.001)
|
||||||
}
|
}
|
||||||
|
|
||||||
if playAnimation, let animationView = modeSelectorView.contentView as? LottieComponent.View {
|
if let animationView = modeSelectorView.contentView as? LottieComponent.View {
|
||||||
animationView.playOnce()
|
if playAnimation {
|
||||||
|
animationView.playOnce()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
modeSelectorTransition.setPosition(view: modeSelectorView, position: modeSelectorFrame.center)
|
modeSelectorTransition.setPosition(view: modeSelectorView, position: modeSelectorFrame.center)
|
||||||
modeSelectorTransition.setBounds(view: modeSelectorView, bounds: CGRect(origin: CGPoint(), size: modeSelectorFrame.size))
|
modeSelectorTransition.setBounds(view: modeSelectorView, bounds: CGRect(origin: CGPoint(), size: modeSelectorFrame.size))
|
||||||
|
|
||||||
if let externalState = component.externalState {
|
if let externalState = component.externalState {
|
||||||
let displaySelector = externalState.isEditing
|
let displaySelector = externalState.isEditing || component.alwaysDisplayInputModeSelector
|
||||||
|
|
||||||
alphaTransition.setAlpha(view: modeSelectorView, alpha: displaySelector ? 1.0 : 0.0)
|
alphaTransition.setAlpha(view: modeSelectorView, alpha: displaySelector ? 1.0 : 0.0)
|
||||||
alphaTransition.setScale(view: modeSelectorView, scale: displaySelector ? 1.0 : 0.001)
|
alphaTransition.setScale(view: modeSelectorView, scale: displaySelector ? 1.0 : 0.001)
|
||||||
|
@ -158,19 +158,19 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||||||
if let _ = peer as? TelegramUser {
|
if let _ = peer as? TelegramUser {
|
||||||
status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat)
|
status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat)
|
||||||
} else if let group = peer as? TelegramGroup {
|
} else if let group = peer as? TelegramGroup {
|
||||||
status = .custom(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount))), multiline: false, isActive: false, icon: nil)
|
||||||
} else if let channel = peer as? TelegramChannel {
|
} else if let channel = peer as? TelegramChannel {
|
||||||
if case .group = channel.info {
|
if case .group = channel.info {
|
||||||
if let participantCount = participantCount, participantCount != 0 {
|
if let participantCount = participantCount, participantCount != 0 {
|
||||||
status = .custom(string: strings.Conversation_StatusMembers(participantCount), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Conversation_StatusMembers(participantCount)), multiline: false, isActive: false, icon: nil)
|
||||||
} else {
|
} else {
|
||||||
status = .custom(string: strings.Group_Status, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Group_Status), multiline: false, isActive: false, icon: nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let participantCount = participantCount, participantCount != 0 {
|
if let participantCount = participantCount, participantCount != 0 {
|
||||||
status = .custom(string: strings.Conversation_StatusSubscribers(participantCount), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Conversation_StatusSubscribers(participantCount)), multiline: false, isActive: false, icon: nil)
|
||||||
} else {
|
} else {
|
||||||
status = .custom(string: strings.Channel_Status, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Channel_Status), multiline: false, isActive: false, icon: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -220,7 +220,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
|||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
text = presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyData.count))
|
text = presentationData.strings.ChatList_ArchiveStoryCount(Int32(storyData.count))
|
||||||
status = .custom(string: text, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: text), multiline: false, isActive: false, icon: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
|
||||||
|
@ -674,7 +674,7 @@ public class ContactsController: ViewController {
|
|||||||
|
|
||||||
let text = self.presentationData.strings.ContactList_DeletedContacts(Int32(peerIds.count))
|
let text = self.presentationData.strings.ContactList_DeletedContacts(Int32(peerIds.count))
|
||||||
|
|
||||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: text, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: text), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ private enum InviteContactsEntry: Comparable, Identifiable {
|
|||||||
case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder):
|
case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder):
|
||||||
let status: ContactsPeerItemStatus
|
let status: ContactsPeerItemStatus
|
||||||
if count != 0 {
|
if count != 0 {
|
||||||
status = .custom(string: strings.Contacts_ImportersCount(count), multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: strings.Contacts_ImportersCount(count)), multiline: false, isActive: false, icon: nil)
|
||||||
} else {
|
} else {
|
||||||
status = .none
|
status = .none
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,8 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||||
"//submodules/MoreButtonNode",
|
"//submodules/MoreButtonNode",
|
||||||
|
"//submodules/TextFormat",
|
||||||
|
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -21,6 +21,8 @@ import AnimationCache
|
|||||||
import MultiAnimationRenderer
|
import MultiAnimationRenderer
|
||||||
import EmojiStatusComponent
|
import EmojiStatusComponent
|
||||||
import MoreButtonNode
|
import MoreButtonNode
|
||||||
|
import TextFormat
|
||||||
|
import TextNodeWithEntities
|
||||||
|
|
||||||
public final class ContactItemHighlighting {
|
public final class ContactItemHighlighting {
|
||||||
public var chatLocation: ChatLocation?
|
public var chatLocation: ChatLocation?
|
||||||
@ -39,7 +41,7 @@ public enum ContactsPeerItemStatus {
|
|||||||
case none
|
case none
|
||||||
case presence(EnginePeer.Presence, PresentationDateTimeFormat)
|
case presence(EnginePeer.Presence, PresentationDateTimeFormat)
|
||||||
case addressName(String)
|
case addressName(String)
|
||||||
case custom(string: String, multiline: Bool, isActive: Bool, icon: Icon?)
|
case custom(string: NSAttributedString, multiline: Bool, isActive: Bool, icon: Icon?)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ContactsPeerItemSelection: Equatable {
|
public enum ContactsPeerItemSelection: Equatable {
|
||||||
@ -437,7 +439,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private var credibilityIconComponent: EmojiStatusComponent?
|
private var credibilityIconComponent: EmojiStatusComponent?
|
||||||
private var verifiedIconView: ComponentHostView<Empty>?
|
private var verifiedIconView: ComponentHostView<Empty>?
|
||||||
private var verifiedIconComponent: EmojiStatusComponent?
|
private var verifiedIconComponent: EmojiStatusComponent?
|
||||||
public let statusNode: TextNode
|
public let statusNode: TextNodeWithEntities
|
||||||
private var statusIconNode: ASImageNode?
|
private var statusIconNode: ASImageNode?
|
||||||
private var badgeBackgroundNode: ASImageNode?
|
private var badgeBackgroundNode: ASImageNode?
|
||||||
private var badgeTextNode: TextNode?
|
private var badgeTextNode: TextNode?
|
||||||
@ -519,6 +521,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
containerSize: avatarIconView.bounds.size
|
containerSize: avatarIconView.bounds.size
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
self.statusNode.visibilityRect = self.visibilityStatus == false ? CGRect.zero : CGRect.infinite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -554,7 +557,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.avatarNode.isLayerBacked = false
|
self.avatarNode.isLayerBacked = false
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.statusNode = TextNode()
|
self.statusNode = TextNodeWithEntities()
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||||
|
|
||||||
@ -575,7 +578,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
self.avatarNodeContainer.addSubnode(self.avatarNode)
|
self.avatarNodeContainer.addSubnode(self.avatarNode)
|
||||||
self.offsetContainerNode.addSubnode(self.avatarNodeContainer)
|
self.offsetContainerNode.addSubnode(self.avatarNodeContainer)
|
||||||
self.offsetContainerNode.addSubnode(self.titleNode)
|
self.offsetContainerNode.addSubnode(self.titleNode)
|
||||||
self.offsetContainerNode.addSubnode(self.statusNode)
|
self.offsetContainerNode.addSubnode(self.statusNode.textNode)
|
||||||
|
|
||||||
self.addSubnode(self.maskNode)
|
self.addSubnode(self.maskNode)
|
||||||
|
|
||||||
@ -708,7 +711,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
public func asyncLayout() -> (_ item: ContactsPeerItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (Bool, Bool) -> Void)) {
|
public func asyncLayout() -> (_ item: ContactsPeerItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> (Signal<Void, NoError>?, (Bool, Bool) -> Void)) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
let makeStatusLayout = TextNodeWithEntities.asyncLayout(self.statusNode)
|
||||||
let currentSelectionNode = self.selectionNode
|
let currentSelectionNode = self.selectionNode
|
||||||
|
|
||||||
let makeBadgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
|
let makeBadgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
|
||||||
@ -939,7 +942,24 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
statusAttributedString = NSAttributedString(string: suffix, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
statusAttributedString = NSAttributedString(string: suffix, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||||
}
|
}
|
||||||
case let .custom(text, multiline, isActive, icon):
|
case let .custom(text, multiline, isActive, icon):
|
||||||
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor)
|
let statusAttributedStringValue = NSMutableAttributedString(string: text.string)
|
||||||
|
statusAttributedStringValue.addAttribute(.font, value: statusFont, range: NSRange(location: 0, length: statusAttributedStringValue.length))
|
||||||
|
statusAttributedStringValue.addAttribute(.foregroundColor, value: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor, range: NSRange(location: 0, length: statusAttributedStringValue.length))
|
||||||
|
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
|
||||||
|
for (key, value) in attributes {
|
||||||
|
if key == ChatTextInputAttributes.bold {
|
||||||
|
statusAttributedStringValue.addAttribute(.font, value: Font.semibold(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.italic {
|
||||||
|
statusAttributedStringValue.addAttribute(.font, value: Font.italic(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.monospace {
|
||||||
|
statusAttributedStringValue.addAttribute(.font, value: Font.monospace(14.0), range: range)
|
||||||
|
} else {
|
||||||
|
statusAttributedStringValue.addAttribute(key, value: value, range: range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
statusAttributedString = statusAttributedStringValue
|
||||||
statusIcon = icon
|
statusIcon = icon
|
||||||
statusIsActive = isActive
|
statusIsActive = isActive
|
||||||
multilineStatus = multiline
|
multilineStatus = multiline
|
||||||
@ -964,7 +984,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
switch item.status {
|
switch item.status {
|
||||||
case let .custom(text, multiline, isActive, icon):
|
case let .custom(text, multiline, isActive, icon):
|
||||||
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor)
|
let statusAttributedStringValue = NSMutableAttributedString(string: "")
|
||||||
|
statusAttributedStringValue.addAttribute(.font, value: statusFont, range: NSRange(location: 0, length: text.length))
|
||||||
|
statusAttributedStringValue.addAttribute(.foregroundColor, value: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor, range: NSRange(location: 0, length: text.length))
|
||||||
|
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
|
||||||
|
for (key, value) in attributes {
|
||||||
|
if key == ChatTextInputAttributes.bold {
|
||||||
|
statusAttributedStringValue.addAttribute(.font, value: Font.semibold(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.italic {
|
||||||
|
statusAttributedStringValue.addAttribute(.font, value: Font.italic(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.monospace {
|
||||||
|
statusAttributedStringValue.addAttribute(.font, value: Font.monospace(14.0), range: range)
|
||||||
|
} else {
|
||||||
|
statusAttributedStringValue.addAttribute(key, value: value, range: range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
statusAttributedString = statusAttributedStringValue
|
||||||
multilineStatus = multiline
|
multilineStatus = multiline
|
||||||
statusIsActive = isActive
|
statusIsActive = isActive
|
||||||
statusIcon = icon
|
statusIcon = icon
|
||||||
@ -1395,17 +1431,24 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
|
transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame)
|
||||||
|
|
||||||
strongSelf.titleNode.alpha = item.enabled ? 1.0 : 0.4
|
strongSelf.titleNode.alpha = item.enabled ? 1.0 : 0.4
|
||||||
strongSelf.statusNode.alpha = item.enabled ? 1.0 : 1.0
|
strongSelf.statusNode.textNode.alpha = item.enabled ? 1.0 : 1.0
|
||||||
|
|
||||||
let _ = statusApply()
|
strongSelf.statusNode.visibilityRect = strongSelf.visibilityStatus == false ? CGRect.zero : CGRect.infinite
|
||||||
|
let _ = statusApply(TextNodeWithEntities.Arguments(
|
||||||
|
context: item.context,
|
||||||
|
cache: item.context.animationCache,
|
||||||
|
renderer: item.context.animationRenderer,
|
||||||
|
placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor,
|
||||||
|
attemptSynchronous: false
|
||||||
|
))
|
||||||
var statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY - 1.0), size: statusLayout.size)
|
var statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY - 1.0), size: statusLayout.size)
|
||||||
if let statusIconImage {
|
if let statusIconImage {
|
||||||
statusFrame.origin.x += statusIconImage.size.width + 1.0
|
statusFrame.origin.x += statusIconImage.size.width + 1.0
|
||||||
}
|
}
|
||||||
let previousStatusFrame = strongSelf.statusNode.frame
|
let previousStatusFrame = strongSelf.statusNode.textNode.frame
|
||||||
|
|
||||||
strongSelf.statusNode.frame = statusFrame
|
strongSelf.statusNode.textNode.frame = statusFrame
|
||||||
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
|
transition.animatePositionAdditive(node: strongSelf.statusNode.textNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
|
||||||
|
|
||||||
if let statusIconImage {
|
if let statusIconImage {
|
||||||
let statusIconNode: ASImageNode
|
let statusIconNode: ASImageNode
|
||||||
@ -1413,7 +1456,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
statusIconNode = current
|
statusIconNode = current
|
||||||
} else {
|
} else {
|
||||||
statusIconNode = ASImageNode()
|
statusIconNode = ASImageNode()
|
||||||
strongSelf.statusNode.addSubnode(statusIconNode)
|
strongSelf.statusNode.textNode.addSubnode(statusIconNode)
|
||||||
}
|
}
|
||||||
statusIconNode.image = statusIconImage
|
statusIconNode.image = statusIconImage
|
||||||
statusIconNode.frame = CGRect(origin: CGPoint(x: -statusIconImage.size.width - 1.0, y: floor((statusFrame.height - statusIconImage.size.height) / 2.0) + 1.0), size: statusIconImage.size)
|
statusIconNode.frame = CGRect(origin: CGPoint(x: -statusIconImage.size.width - 1.0, y: floor((statusFrame.height - statusIconImage.size.height) / 2.0) + 1.0), size: statusIconImage.size)
|
||||||
|
@ -317,7 +317,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||||||
if !self.item.entities.isEmpty {
|
if !self.item.entities.isEmpty {
|
||||||
let inputStateText = ChatTextInputStateText(text: self.item.text, attributes: self.item.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
|
let inputStateText = ChatTextInputStateText(text: self.item.text, attributes: self.item.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
|
||||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||||
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId), range: entity.range)
|
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: true), range: entity.range)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -62,7 +62,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
case peer(EnginePeer.Id)
|
case peer(EnginePeer.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
case header(String)
|
case header(NSAttributedString)
|
||||||
|
|
||||||
case mainLinkHeader(String)
|
case mainLinkHeader(String)
|
||||||
case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool)
|
case mainLink(link: ExportedChatFolderLink?, isGenerating: Bool)
|
||||||
@ -225,13 +225,13 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
private func folderInviteLinkListControllerEntries(
|
private func folderInviteLinkListControllerEntries(
|
||||||
presentationData: PresentationData,
|
presentationData: PresentationData,
|
||||||
state: FolderInviteLinkListControllerState,
|
state: FolderInviteLinkListControllerState,
|
||||||
title: String,
|
title: ChatFolderTitle,
|
||||||
allPeers: [EnginePeer]
|
allPeers: [EnginePeer]
|
||||||
) -> [InviteLinksListEntry] {
|
) -> [InviteLinksListEntry] {
|
||||||
var entries: [InviteLinksListEntry] = []
|
var entries: [InviteLinksListEntry] = []
|
||||||
|
|
||||||
var infoString: String?
|
var infoString: String?
|
||||||
let chatCountString: String
|
let chatCountString: NSAttributedString
|
||||||
let peersHeaderString: String
|
let peersHeaderString: String
|
||||||
|
|
||||||
let canShareChats = !allPeers.allSatisfy({ !canShareLinkToPeer(peer: $0) })
|
let canShareChats = !allPeers.allSatisfy({ !canShareLinkToPeer(peer: $0) })
|
||||||
@ -241,16 +241,36 @@ private func folderInviteLinkListControllerEntries(
|
|||||||
|
|
||||||
if !canShareChats {
|
if !canShareChats {
|
||||||
infoString = presentationData.strings.FolderLinkScreen_TitleDescriptionUnavailable
|
infoString = presentationData.strings.FolderLinkScreen_TitleDescriptionUnavailable
|
||||||
chatCountString = presentationData.strings.FolderLinkScreen_ChatCountHeaderUnavailable
|
chatCountString = NSAttributedString(string: presentationData.strings.FolderLinkScreen_ChatCountHeaderUnavailable)
|
||||||
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderUnavailable
|
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderUnavailable
|
||||||
} else if state.selectedPeerIds.isEmpty {
|
} else if state.selectedPeerIds.isEmpty {
|
||||||
chatCountString = presentationData.strings.FolderLinkScreen_TitleDescriptionDeselected(title).string
|
let chatCountStringValue = NSMutableAttributedString(string: presentationData.strings.FolderLinkScreen_TitleDescriptionDeselectedV2)
|
||||||
|
let folderRange = (chatCountStringValue.string as NSString).range(of: "{folder}")
|
||||||
|
if folderRange.location != NSNotFound {
|
||||||
|
chatCountStringValue.replaceCharacters(in: folderRange, with: "")
|
||||||
|
chatCountStringValue.insert(title.rawAttributedString, at: folderRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatCountString = chatCountStringValue
|
||||||
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeader
|
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeader
|
||||||
if allPeers.count > 1 {
|
if allPeers.count > 1 {
|
||||||
selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
|
selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chatCountString = presentationData.strings.FolderLinkScreen_TitleDescriptionSelected(title, presentationData.strings.FolderLinkScreen_TitleDescriptionSelectedCount(Int32(state.selectedPeerIds.count))).string
|
let chatCountStringValue = NSMutableAttributedString(string: presentationData.strings.FolderLinkScreen_TitleDescriptionSelectedV2)
|
||||||
|
let folderRange = (chatCountStringValue.string as NSString).range(of: "{folder}")
|
||||||
|
if folderRange.location != NSNotFound {
|
||||||
|
chatCountStringValue.replaceCharacters(in: folderRange, with: "")
|
||||||
|
chatCountStringValue.insert(title.rawAttributedString, at: folderRange.location)
|
||||||
|
}
|
||||||
|
let chatsRange = (chatCountStringValue.string as NSString).range(of: "{chats}")
|
||||||
|
if chatsRange.location != NSNotFound {
|
||||||
|
chatCountStringValue.replaceCharacters(in: chatsRange, with: "")
|
||||||
|
let countValue = presentationData.strings.FolderLinkScreen_TitleDescriptionSelectedCount(Int32(state.selectedPeerIds.count))
|
||||||
|
chatCountStringValue.insert(NSAttributedString(string: countValue), at: chatsRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatCountString = chatCountStringValue
|
||||||
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderSelected(Int32(state.selectedPeerIds.count))
|
peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderSelected(Int32(state.selectedPeerIds.count))
|
||||||
if allPeers.count > 1 {
|
if allPeers.count > 1 {
|
||||||
selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
|
selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
|
||||||
@ -699,11 +719,10 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: doneButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: doneButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||||
//TODO:release
|
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: folderInviteLinkListControllerEntries(
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: folderInviteLinkListControllerEntries(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
state: state,
|
state: state,
|
||||||
title: filterTitle.text,
|
title: filterTitle,
|
||||||
allPeers: allPeers
|
allPeers: allPeers
|
||||||
), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges)
|
), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||||
|
|
||||||
|
@ -11,18 +11,19 @@ import TelegramAnimatedStickerNode
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import Markdown
|
import Markdown
|
||||||
import TextFormat
|
import TextFormat
|
||||||
|
import TextNodeWithEntities
|
||||||
|
|
||||||
public class InviteLinkHeaderItem: ListViewItem, ItemListItem {
|
public class InviteLinkHeaderItem: ListViewItem, ItemListItem {
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
public let theme: PresentationTheme
|
public let theme: PresentationTheme
|
||||||
public let title: String?
|
public let title: String?
|
||||||
public let text: String
|
public let text: NSAttributedString
|
||||||
public let animationName: String
|
public let animationName: String
|
||||||
public let hideOnSmallScreens: Bool
|
public let hideOnSmallScreens: Bool
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
public let linkAction: ((ItemListTextItemLinkAction) -> Void)?
|
public let linkAction: ((ItemListTextItemLinkAction) -> Void)?
|
||||||
|
|
||||||
public init(context: AccountContext, theme: PresentationTheme, title: String? = nil, text: String, animationName: String, hideOnSmallScreens: Bool = false, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil) {
|
public init(context: AccountContext, theme: PresentationTheme, title: String? = nil, text: NSAttributedString, animationName: String, hideOnSmallScreens: Bool = false, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.title = title
|
self.title = title
|
||||||
@ -75,7 +76,7 @@ private let textFont = Font.regular(14.0)
|
|||||||
|
|
||||||
class InviteLinkHeaderItemNode: ListViewItemNode {
|
class InviteLinkHeaderItemNode: ListViewItemNode {
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let textNode: TextNode
|
private let textNode: TextNodeWithEntities
|
||||||
private var animationNode: AnimatedStickerNode
|
private var animationNode: AnimatedStickerNode
|
||||||
|
|
||||||
private var item: InviteLinkHeaderItem?
|
private var item: InviteLinkHeaderItem?
|
||||||
@ -86,17 +87,17 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
|||||||
self.titleNode.contentMode = .left
|
self.titleNode.contentMode = .left
|
||||||
self.titleNode.contentsScale = UIScreen.main.scale
|
self.titleNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNodeWithEntities()
|
||||||
self.textNode.isUserInteractionEnabled = false
|
self.textNode.textNode.isUserInteractionEnabled = false
|
||||||
self.textNode.contentMode = .left
|
self.textNode.textNode.contentMode = .left
|
||||||
self.textNode.contentsScale = UIScreen.main.scale
|
self.textNode.textNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
self.animationNode = DefaultAnimatedStickerNodeImpl()
|
self.animationNode = DefaultAnimatedStickerNodeImpl()
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode.textNode)
|
||||||
self.addSubnode(self.animationNode)
|
self.addSubnode(self.animationNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +113,7 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
func asyncLayout() -> (_ item: InviteLinkHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: InviteLinkHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||||
|
|
||||||
return { item, params, neighbors in
|
return { item, params, neighbors in
|
||||||
let leftInset: CGFloat = 24.0 + params.leftInset
|
let leftInset: CGFloat = 24.0 + params.leftInset
|
||||||
@ -131,9 +132,22 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
let attributedTitle = NSAttributedString(string: item.title ?? "", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
|
let attributedTitle = NSAttributedString(string: item.title ?? "", font: titleFont, textColor: item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
|
||||||
|
|
||||||
let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: item.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: Font.semibold(14.0), textColor: item.theme.list.freeTextColor), link: MarkdownAttributeSet(font: textFont, textColor: item.theme.list.itemAccentColor), linkAttribute: { contents in
|
let attributedText = NSMutableAttributedString(string: item.text.string)
|
||||||
return (TelegramTextAttributes.URL, contents)
|
attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: attributedText.length))
|
||||||
}))
|
attributedText.addAttribute(.foregroundColor, value: item.theme.list.freeTextColor, range: NSRange(location: 0, length: attributedText.length))
|
||||||
|
item.text.enumerateAttributes(in: NSRange(location: 0, length: item.text.length), using: { attributes, range, _ in
|
||||||
|
for (key, value) in attributes {
|
||||||
|
if key == ChatTextInputAttributes.bold {
|
||||||
|
attributedText.addAttribute(.font, value: Font.semibold(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.italic {
|
||||||
|
attributedText.addAttribute(.font, value: Font.italic(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.monospace {
|
||||||
|
attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range)
|
||||||
|
} else {
|
||||||
|
attributedText.addAttribute(key, value: value, range: range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
@ -168,8 +182,15 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
|||||||
origin += titleLayout.size.height + spacing
|
origin += titleLayout.size.height + spacing
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = textApply()
|
let _ = textApply(TextNodeWithEntities.Arguments(
|
||||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: origin), size: textLayout.size)
|
context: item.context,
|
||||||
|
cache: item.context.animationCache,
|
||||||
|
renderer: item.context.animationRenderer,
|
||||||
|
placeholderColor: item.theme.list.mediaPlaceholderColor,
|
||||||
|
attemptSynchronous: true
|
||||||
|
))
|
||||||
|
strongSelf.textNode.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: origin), size: textLayout.size)
|
||||||
|
strongSelf.textNode.visibilityRect = .infinite
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -189,9 +210,9 @@ class InviteLinkHeaderItemNode: ListViewItemNode {
|
|||||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||||
switch gesture {
|
switch gesture {
|
||||||
case .tap:
|
case .tap:
|
||||||
let textFrame = self.textNode.frame
|
let textFrame = self.textNode.textNode.frame
|
||||||
if let item = self.item, textFrame.contains(location) {
|
if let item = self.item, textFrame.contains(location) {
|
||||||
if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: location.x - textFrame.minX, y: location.y - textFrame.minY)) {
|
if let (_, attributes) = self.textNode.textNode.attributesAtPoint(CGPoint(x: location.x - textFrame.minX, y: location.y - textFrame.minY)) {
|
||||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
item.linkAction?(.tap(url))
|
item.linkAction?(.tap(url))
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ private enum InviteLinksListSection: Int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum InviteLinksListEntry: ItemListNodeEntry {
|
private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||||
case header(PresentationTheme, String)
|
case header(PresentationTheme, NSAttributedString)
|
||||||
|
|
||||||
case mainLinkHeader(PresentationTheme, String)
|
case mainLinkHeader(PresentationTheme, String)
|
||||||
case mainLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool)
|
case mainLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool)
|
||||||
@ -278,7 +278,7 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
|
|||||||
} else {
|
} else {
|
||||||
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
|
helpText = presentationData.strings.InviteLink_CreatePrivateLinkHelp
|
||||||
}
|
}
|
||||||
entries.append(.header(presentationData.theme, helpText))
|
entries.append(.header(presentationData.theme, NSAttributedString(string: helpText)))
|
||||||
}
|
}
|
||||||
|
|
||||||
let mainInvite: ExportedInvitation?
|
let mainInvite: ExportedInvitation?
|
||||||
|
@ -100,7 +100,7 @@ private enum InviteRequestsEntry: ItemListNodeEntry {
|
|||||||
let arguments = arguments as! InviteRequestsControllerArguments
|
let arguments = arguments as! InviteRequestsControllerArguments
|
||||||
switch self {
|
switch self {
|
||||||
case let .header(theme, text):
|
case let .header(theme, text):
|
||||||
return InviteLinkHeaderItem(context: arguments.context, theme: theme, text: text, animationName: "Requests", sectionId: self.section, linkAction: { _ in
|
return InviteLinkHeaderItem(context: arguments.context, theme: theme, text: NSAttributedString(string: text), animationName: "Requests", sectionId: self.section, linkAction: { _ in
|
||||||
arguments.openLinks()
|
arguments.openLinks()
|
||||||
})
|
})
|
||||||
case let .requestsHeader(_, text):
|
case let .requestsHeader(_, text):
|
||||||
|
@ -112,7 +112,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
let arguments = arguments as! PaymentMethodListScreenArguments
|
let arguments = arguments as! PaymentMethodListScreenArguments
|
||||||
switch self {
|
switch self {
|
||||||
case let .header(text):
|
case let .header(text):
|
||||||
return InviteLinkHeaderItem(context: arguments.context, theme: presentationData.theme, text: text, animationName: "Invite", sectionId: self.section)
|
return InviteLinkHeaderItem(context: arguments.context, theme: presentationData.theme, text: NSAttributedString(string: text), animationName: "Invite", sectionId: self.section)
|
||||||
case let .methodsHeader(text):
|
case let .methodsHeader(text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .addMethod(text):
|
case let .addMethod(text):
|
||||||
|
@ -158,7 +158,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable {
|
|||||||
case let .participant(participant, label, revealActions, revealed, enabled):
|
case let .participant(participant, label, revealActions, revealed, enabled):
|
||||||
let status: ContactsPeerItemStatus
|
let status: ContactsPeerItemStatus
|
||||||
if let label = label {
|
if let label = label {
|
||||||
status = .custom(string: label, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: label), multiline: false, isActive: false, icon: nil)
|
||||||
} else if let presence = participant.presences[participant.peer.id], self.addIcon {
|
} else if let presence = participant.presences[participant.peer.id], self.addIcon {
|
||||||
status = .presence(EnginePeer.Presence(presence), dateTimeFormat)
|
status = .presence(EnginePeer.Presence(presence), dateTimeFormat)
|
||||||
} else {
|
} else {
|
||||||
|
@ -129,7 +129,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
|||||||
case let .peer(_, participant, editing, label, enabled, isChannel, isContact):
|
case let .peer(_, participant, editing, label, enabled, isChannel, isContact):
|
||||||
let status: ContactsPeerItemStatus
|
let status: ContactsPeerItemStatus
|
||||||
if let label = label {
|
if let label = label {
|
||||||
status = .custom(string: label, multiline: false, isActive: false, icon: nil)
|
status = .custom(string: NSAttributedString(string: label), multiline: false, isActive: false, icon: nil)
|
||||||
} else if participant.peer.id != context.account.peerId {
|
} else if participant.peer.id != context.account.peerId {
|
||||||
let presence = participant.presences[participant.peer.id] ?? TelegramUserPresence(status: .none, lastActivity: 0)
|
let presence = participant.presences[participant.peer.id] ?? TelegramUserPresence(status: .none, lastActivity: 0)
|
||||||
status = .presence(EnginePeer.Presence(presence), presentationData.dateTimeFormat)
|
status = .presence(EnginePeer.Presence(presence), presentationData.dateTimeFormat)
|
||||||
|
@ -117,7 +117,7 @@ private enum DeleteAccountDataEntry: ItemListNodeEntry, Equatable {
|
|||||||
let arguments = arguments as! DeleteAccountDataArguments
|
let arguments = arguments as! DeleteAccountDataArguments
|
||||||
switch self {
|
switch self {
|
||||||
case let .header(theme, animation, title, text, hideOnSmallScreens):
|
case let .header(theme, animation, title, text, hideOnSmallScreens):
|
||||||
return InviteLinkHeaderItem(context: arguments.context, theme: theme, title: title, text: text, animationName: animation, hideOnSmallScreens: hideOnSmallScreens, sectionId: self.section, linkAction: nil)
|
return InviteLinkHeaderItem(context: arguments.context, theme: theme, title: title, text: NSAttributedString(string: text), animationName: animation, hideOnSmallScreens: hideOnSmallScreens, sectionId: self.section, linkAction: nil)
|
||||||
case let .peers(_, peers):
|
case let .peers(_, peers):
|
||||||
return DeleteAccountPeersItem(context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, peers: peers, sectionId: self.section)
|
return DeleteAccountPeersItem(context: arguments.context, theme: presentationData.theme, strings: presentationData.strings, peers: peers, sectionId: self.section)
|
||||||
case let .info(_, text):
|
case let .info(_, text):
|
||||||
|
@ -234,8 +234,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) }
|
dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) }
|
||||||
dict[-712374074] = { return Api.Dialog.parse_dialog($0) }
|
dict[-712374074] = { return Api.Dialog.parse_dialog($0) }
|
||||||
dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) }
|
dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) }
|
||||||
dict[1605718587] = { return Api.DialogFilter.parse_dialogFilter($0) }
|
dict[-1438177711] = { return Api.DialogFilter.parse_dialogFilter($0) }
|
||||||
dict[-1612542300] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) }
|
dict[-1772913705] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) }
|
||||||
dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) }
|
dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) }
|
||||||
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
|
dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) }
|
||||||
dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) }
|
dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) }
|
||||||
@ -582,7 +582,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) }
|
dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) }
|
||||||
dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) }
|
dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) }
|
||||||
dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
|
dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
|
||||||
dict[139818551] = { return Api.MessageAction.parse_messageActionStarGift($0) }
|
dict[-1253342558] = { return Api.MessageAction.parse_messageActionStarGift($0) }
|
||||||
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) }
|
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) }
|
||||||
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }
|
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }
|
||||||
dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) }
|
dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) }
|
||||||
@ -1225,7 +1225,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1044107055] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultAdsHidden($0) }
|
dict[1044107055] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultAdsHidden($0) }
|
||||||
dict[-2073059774] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultChooseOption($0) }
|
dict[-2073059774] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultChooseOption($0) }
|
||||||
dict[-1384544183] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultReported($0) }
|
dict[-1384544183] = { return Api.channels.SponsoredMessageReportResult.parse_sponsoredMessageReportResultReported($0) }
|
||||||
dict[500007837] = { return Api.chatlists.ChatlistInvite.parse_chatlistInvite($0) }
|
dict[-250687953] = { return Api.chatlists.ChatlistInvite.parse_chatlistInvite($0) }
|
||||||
dict[-91752871] = { return Api.chatlists.ChatlistInvite.parse_chatlistInviteAlready($0) }
|
dict[-91752871] = { return Api.chatlists.ChatlistInvite.parse_chatlistInviteAlready($0) }
|
||||||
dict[-1816295539] = { return Api.chatlists.ChatlistUpdates.parse_chatlistUpdates($0) }
|
dict[-1816295539] = { return Api.chatlists.ChatlistUpdates.parse_chatlistUpdates($0) }
|
||||||
dict[283567014] = { return Api.chatlists.ExportedChatlistInvite.parse_exportedChatlistInvite($0) }
|
dict[283567014] = { return Api.chatlists.ExportedChatlistInvite.parse_exportedChatlistInvite($0) }
|
||||||
|
@ -373,7 +373,7 @@ public extension Api {
|
|||||||
case messageActionSetChatTheme(emoticon: String)
|
case messageActionSetChatTheme(emoticon: String)
|
||||||
case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper)
|
case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper)
|
||||||
case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?)
|
case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?)
|
||||||
case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?)
|
case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, canExportAt: Int32?)
|
||||||
case messageActionSuggestProfilePhoto(photo: Api.Photo)
|
case messageActionSuggestProfilePhoto(photo: Api.Photo)
|
||||||
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
|
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
|
||||||
case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?)
|
case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?)
|
||||||
@ -719,14 +719,15 @@ public extension Api {
|
|||||||
serializeInt32(period, buffer: buffer, boxed: false)
|
serializeInt32(period, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
case .messageActionStarGift(let flags, let gift, let message, let convertStars):
|
case .messageActionStarGift(let flags, let gift, let message, let convertStars, let canExportAt):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(139818551)
|
buffer.appendInt32(-1253342558)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
gift.serialize(buffer, true)
|
gift.serialize(buffer, true)
|
||||||
if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(convertStars!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(convertStars!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 7) != 0 {serializeInt32(canExportAt!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
case .messageActionSuggestProfilePhoto(let photo):
|
case .messageActionSuggestProfilePhoto(let photo):
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -853,8 +854,8 @@ public extension Api {
|
|||||||
return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)])
|
return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)])
|
||||||
case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom):
|
case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom):
|
||||||
return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)])
|
return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)])
|
||||||
case .messageActionStarGift(let flags, let gift, let message, let convertStars):
|
case .messageActionStarGift(let flags, let gift, let message, let convertStars, let canExportAt):
|
||||||
return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any)])
|
return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("canExportAt", canExportAt as Any)])
|
||||||
case .messageActionSuggestProfilePhoto(let photo):
|
case .messageActionSuggestProfilePhoto(let photo):
|
||||||
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
|
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
|
||||||
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
|
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
|
||||||
@ -1515,12 +1516,15 @@ public extension Api {
|
|||||||
} }
|
} }
|
||||||
var _4: Int64?
|
var _4: Int64?
|
||||||
if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() }
|
if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() }
|
||||||
|
var _5: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 7) != 0 {_5 = reader.readInt32() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||||
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
|
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 {
|
let _c5 = (Int(_1!) & Int(1 << 7) == 0) || _5 != nil
|
||||||
return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4)
|
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||||
|
return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, canExportAt: _5)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -476,17 +476,17 @@ public extension Api.channels {
|
|||||||
}
|
}
|
||||||
public extension Api.chatlists {
|
public extension Api.chatlists {
|
||||||
enum ChatlistInvite: TypeConstructorDescription {
|
enum ChatlistInvite: TypeConstructorDescription {
|
||||||
case chatlistInvite(flags: Int32, title: String, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
case chatlistInvite(flags: Int32, title: Api.TextWithEntities, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
||||||
case chatlistInviteAlready(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
case chatlistInviteAlready(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users):
|
case .chatlistInvite(let flags, let title, let emoticon, let peers, let chats, let users):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(500007837)
|
buffer.appendInt32(-250687953)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeString(title, buffer: buffer, boxed: false)
|
title.serialize(buffer, true)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 0) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
buffer.appendInt32(Int32(peers.count))
|
buffer.appendInt32(Int32(peers.count))
|
||||||
@ -545,8 +545,10 @@ public extension Api.chatlists {
|
|||||||
public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? {
|
public static func parse_chatlistInvite(_ reader: BufferReader) -> ChatlistInvite? {
|
||||||
var _1: Int32?
|
var _1: Int32?
|
||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
var _2: String?
|
var _2: Api.TextWithEntities?
|
||||||
_2 = parseString(reader)
|
if let signature = reader.readInt32() {
|
||||||
|
_2 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||||
|
}
|
||||||
var _3: String?
|
var _3: String?
|
||||||
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
|
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
|
||||||
var _4: [Api.Peer]?
|
var _4: [Api.Peer]?
|
||||||
|
@ -1166,19 +1166,19 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum DialogFilter: TypeConstructorDescription {
|
enum DialogFilter: TypeConstructorDescription {
|
||||||
case dialogFilter(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer])
|
case dialogFilter(flags: Int32, id: Int32, title: Api.TextWithEntities, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer])
|
||||||
case dialogFilterChatlist(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer])
|
case dialogFilterChatlist(flags: Int32, id: Int32, title: Api.TextWithEntities, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer])
|
||||||
case dialogFilterDefault
|
case dialogFilterDefault
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers):
|
case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(1605718587)
|
buffer.appendInt32(-1438177711)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
serializeString(title, buffer: buffer, boxed: false)
|
title.serialize(buffer, true)
|
||||||
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
@ -1199,11 +1199,11 @@ public extension Api {
|
|||||||
break
|
break
|
||||||
case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers):
|
case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-1612542300)
|
buffer.appendInt32(-1772913705)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
serializeString(title, buffer: buffer, boxed: false)
|
title.serialize(buffer, true)
|
||||||
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)}
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
@ -1242,8 +1242,10 @@ public extension Api {
|
|||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
var _2: Int32?
|
var _2: Int32?
|
||||||
_2 = reader.readInt32()
|
_2 = reader.readInt32()
|
||||||
var _3: String?
|
var _3: Api.TextWithEntities?
|
||||||
_3 = parseString(reader)
|
if let signature = reader.readInt32() {
|
||||||
|
_3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||||
|
}
|
||||||
var _4: String?
|
var _4: String?
|
||||||
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
|
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
|
||||||
var _5: Int32?
|
var _5: Int32?
|
||||||
@ -1280,8 +1282,10 @@ public extension Api {
|
|||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
var _2: Int32?
|
var _2: Int32?
|
||||||
_2 = reader.readInt32()
|
_2 = reader.readInt32()
|
||||||
var _3: String?
|
var _3: Api.TextWithEntities?
|
||||||
_3 = parseString(reader)
|
if let signature = reader.readInt32() {
|
||||||
|
_3 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
|
||||||
|
}
|
||||||
var _4: String?
|
var _4: String?
|
||||||
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
|
if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) }
|
||||||
var _5: Int32?
|
var _5: Int32?
|
||||||
|
@ -171,7 +171,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||||||
return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId))
|
return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId))
|
||||||
case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId):
|
case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId):
|
||||||
return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)))
|
return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)))
|
||||||
case let .messageActionStarGift(flags, apiGift, message, convertStars):
|
case let .messageActionStarGift(flags, apiGift, message, convertStars, _):
|
||||||
let text: String?
|
let text: String?
|
||||||
let entities: [MessageTextEntity]?
|
let entities: [MessageTextEntity]?
|
||||||
switch message {
|
switch message {
|
||||||
|
@ -342,9 +342,17 @@ extension ChatListFilter {
|
|||||||
case .dialogFilterDefault:
|
case .dialogFilterDefault:
|
||||||
self = .allChats
|
self = .allChats
|
||||||
case let .dialogFilter(flags, id, title, emoticon, color, pinnedPeers, includePeers, excludePeers):
|
case let .dialogFilter(flags, id, title, emoticon, color, pinnedPeers, includePeers, excludePeers):
|
||||||
|
let titleText: String
|
||||||
|
let titleEntities: [MessageTextEntity]
|
||||||
|
switch title {
|
||||||
|
case let .textWithEntities(text, entities):
|
||||||
|
titleText = text
|
||||||
|
titleEntities = messageTextEntitiesFromApiEntities(entities)
|
||||||
|
}
|
||||||
|
let disableTitleAnimations = (flags & (1 << 28)) != 0
|
||||||
self = .filter(
|
self = .filter(
|
||||||
id: id,
|
id: id,
|
||||||
title: ChatFolderTitle(text: title, entities: [], enableAnimations: true),
|
title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimations),
|
||||||
emoticon: emoticon,
|
emoticon: emoticon,
|
||||||
data: ChatListFilterData(
|
data: ChatListFilterData(
|
||||||
isShared: false,
|
isShared: false,
|
||||||
@ -392,9 +400,18 @@ extension ChatListFilter {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
case let .dialogFilterChatlist(flags, id, title, emoticon, color, pinnedPeers, includePeers):
|
case let .dialogFilterChatlist(flags, id, title, emoticon, color, pinnedPeers, includePeers):
|
||||||
|
let titleText: String
|
||||||
|
let titleEntities: [MessageTextEntity]
|
||||||
|
switch title {
|
||||||
|
case let .textWithEntities(text, entities):
|
||||||
|
titleText = text
|
||||||
|
titleEntities = messageTextEntitiesFromApiEntities(entities)
|
||||||
|
}
|
||||||
|
let disableTitleAnimations = (flags & (1 << 28)) != 0
|
||||||
|
|
||||||
self = .filter(
|
self = .filter(
|
||||||
id: id,
|
id: id,
|
||||||
title: ChatFolderTitle(text: title, entities: [], enableAnimations: true),
|
title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimations),
|
||||||
emoticon: emoticon,
|
emoticon: emoticon,
|
||||||
data: ChatListFilterData(
|
data: ChatListFilterData(
|
||||||
isShared: true,
|
isShared: true,
|
||||||
@ -446,7 +463,10 @@ extension ChatListFilter {
|
|||||||
if data.color != nil {
|
if data.color != nil {
|
||||||
flags |= 1 << 27
|
flags |= 1 << 27
|
||||||
}
|
}
|
||||||
return .dialogFilterChatlist(flags: flags, id: id, title: title.text, emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
|
if !title.enableAnimations {
|
||||||
|
flags |= 1 << 28
|
||||||
|
}
|
||||||
|
return .dialogFilterChatlist(flags: flags, id: id, title: .textWithEntities(text: title.text, entities: apiEntitiesFromMessageTextEntities(title.entities, associatedPeers: SimpleDictionary())), emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
|
||||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
||||||
if data.includePeers.pinnedPeers.contains(peerId) {
|
if data.includePeers.pinnedPeers.contains(peerId) {
|
||||||
@ -472,7 +492,10 @@ extension ChatListFilter {
|
|||||||
if data.color != nil {
|
if data.color != nil {
|
||||||
flags |= 1 << 27
|
flags |= 1 << 27
|
||||||
}
|
}
|
||||||
return .dialogFilter(flags: flags, id: id, title: title.text, emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
|
if !title.enableAnimations {
|
||||||
|
flags |= 1 << 28
|
||||||
|
}
|
||||||
|
return .dialogFilter(flags: flags, id: id, title: .textWithEntities(text: title.text, entities: apiEntitiesFromMessageTextEntities(title.entities, associatedPeers: SimpleDictionary())), emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in
|
||||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
}, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in
|
||||||
if data.includePeers.pinnedPeers.contains(peerId) {
|
if data.includePeers.pinnedPeers.contains(peerId) {
|
||||||
|
@ -273,9 +273,11 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
|
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
|
||||||
return account.postbox.transaction { transaction -> ChatFolderLinkContents in
|
return account.postbox.transaction { transaction -> ChatFolderLinkContents in
|
||||||
switch result {
|
switch result {
|
||||||
case let .chatlistInvite(_, title, emoticon, peers, chats, users):
|
case let .chatlistInvite(flags, title, emoticon, peers, chats, users):
|
||||||
let _ = emoticon
|
let _ = emoticon
|
||||||
|
|
||||||
|
let disableTitleAnimation = (flags & (1 << 1)) != 0
|
||||||
|
|
||||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||||
var memberCounts: [PeerId: Int] = [:]
|
var memberCounts: [PeerId: Int] = [:]
|
||||||
|
|
||||||
@ -301,7 +303,15 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChatFolderLinkContents(localFilterId: nil, title: ChatFolderTitle(text: title, entities: [], enableAnimations: true), peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
|
let titleText: String
|
||||||
|
let titleEntities: [MessageTextEntity]
|
||||||
|
switch title {
|
||||||
|
case let .textWithEntities(text, entities):
|
||||||
|
titleText = text
|
||||||
|
titleEntities = messageTextEntitiesFromApiEntities(entities)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChatFolderLinkContents(localFilterId: nil, title: ChatFolderTitle(text: titleText, entities: titleEntities, enableAnimations: !disableTitleAnimation), peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds, memberCounts: memberCounts)
|
||||||
case let .chatlistInviteAlready(filterId, missingPeers, alreadyPeers, chats, users):
|
case let .chatlistInviteAlready(filterId, missingPeers, alreadyPeers, chats, users):
|
||||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||||
var memberCounts: [PeerId: Int] = [:]
|
var memberCounts: [PeerId: Int] = [:]
|
||||||
|
@ -22,6 +22,7 @@ swift_library(
|
|||||||
"//submodules/WallpaperBackgroundNode",
|
"//submodules/WallpaperBackgroundNode",
|
||||||
"//submodules/TelegramPresentationData",
|
"//submodules/TelegramPresentationData",
|
||||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||||
|
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -11,6 +11,7 @@ import Markdown
|
|||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
import EmojiStatusComponent
|
import EmojiStatusComponent
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import TextNodeWithEntities
|
||||||
|
|
||||||
final class BlurredRoundedRectangle: Component {
|
final class BlurredRoundedRectangle: Component {
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
@ -1210,21 +1211,42 @@ public final class ChatOverscrollControl: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class ChatInputPanelOverscrollNode: ASDisplayNode {
|
public final class ChatInputPanelOverscrollNode: ASDisplayNode {
|
||||||
public let text: (String, [(Int, NSRange)])
|
public let text: NSAttributedString
|
||||||
public let priority: Int
|
public let priority: Int
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNodeWithEntities
|
||||||
|
|
||||||
public init(text: (String, [(Int, NSRange)]), color: UIColor, priority: Int) {
|
public init(context: AccountContext, text: NSAttributedString, color: UIColor, priority: Int) {
|
||||||
self.text = text
|
self.text = text
|
||||||
self.priority = priority
|
self.priority = priority
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNodeWithEntities()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: color)
|
let attributedText = NSMutableAttributedString(string: text.string)
|
||||||
let bold = MarkdownAttributeSet(font: Font.bold(14.0), textColor: color)
|
attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: text.length))
|
||||||
|
attributedText.addAttribute(.foregroundColor, value: color, range: NSRange(location: 0, length: text.length))
|
||||||
self.titleNode.attributedText = addAttributesToStringWithRanges(text, body: body, argumentAttributes: [0: bold])
|
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
|
||||||
|
for (key, value) in attributes {
|
||||||
|
if key == ChatTextInputAttributes.bold {
|
||||||
|
attributedText.addAttribute(.font, value: Font.bold(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.italic {
|
||||||
|
attributedText.addAttribute(.font, value: Font.italic(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.monospace {
|
||||||
|
attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range)
|
||||||
|
} else {
|
||||||
|
attributedText.addAttribute(key, value: value, range: range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.titleNode.attributedText = attributedText
|
||||||
|
self.titleNode.visibility = true
|
||||||
|
self.titleNode.arguments = TextNodeWithEntities.Arguments(
|
||||||
|
context: context,
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
placeholderColor: color.withMultipliedAlpha(0.1),
|
||||||
|
attemptSynchronous: true
|
||||||
|
)
|
||||||
|
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ swift_library(
|
|||||||
"//submodules/PremiumUI",
|
"//submodules/PremiumUI",
|
||||||
"//submodules/QrCodeUI",
|
"//submodules/QrCodeUI",
|
||||||
"//submodules/InviteLinksUI",
|
"//submodules/InviteLinksUI",
|
||||||
|
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -7,6 +7,7 @@ import AccountContext
|
|||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import MultilineTextWithEntitiesComponent
|
||||||
|
|
||||||
final class BadgeComponent: Component {
|
final class BadgeComponent: Component {
|
||||||
let fillColor: UIColor
|
let fillColor: UIColor
|
||||||
@ -84,17 +85,20 @@ final class BadgeComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class ChatFolderLinkHeaderComponent: Component {
|
final class ChatFolderLinkHeaderComponent: Component {
|
||||||
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let title: ChatFolderTitle
|
let title: ChatFolderTitle
|
||||||
let badge: String?
|
let badge: String?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
title: ChatFolderTitle,
|
title: ChatFolderTitle,
|
||||||
badge: String?
|
badge: String?
|
||||||
) {
|
) {
|
||||||
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.title = title
|
self.title = title
|
||||||
@ -213,10 +217,16 @@ final class ChatFolderLinkHeaderComponent: Component {
|
|||||||
}
|
}
|
||||||
contentWidth += spacing
|
contentWidth += spacing
|
||||||
|
|
||||||
//TODO:release
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(Text(text: component.title.text, font: Font.semibold(17.0), color: component.theme.list.itemAccentColor)),
|
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||||
|
context: component.context,
|
||||||
|
animationCache: component.context.animationCache,
|
||||||
|
animationRenderer: component.context.animationRenderer,
|
||||||
|
placeholderColor: component.theme.list.itemAccentColor.withMultipliedAlpha(0.1),
|
||||||
|
text: .plain(component.title.attributedString(font: Font.semibold(17.0), textColor: component.theme.list.itemAccentColor)),
|
||||||
|
manualVisibilityControl: false
|
||||||
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||||
)
|
)
|
||||||
|
@ -10,6 +10,7 @@ import TelegramPresentationData
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
|
import MultilineTextWithEntitiesComponent
|
||||||
import SolidRoundedButtonComponent
|
import SolidRoundedButtonComponent
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import Markdown
|
import Markdown
|
||||||
@ -437,6 +438,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
let topIconSize = self.topIcon.update(
|
let topIconSize = self.topIcon.update(
|
||||||
transition: contentTransition,
|
transition: contentTransition,
|
||||||
component: AnyComponent(ChatFolderLinkHeaderComponent(
|
component: AnyComponent(ChatFolderLinkHeaderComponent(
|
||||||
|
context: component.context,
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
title: component.linkContents?.title ?? ChatFolderTitle(text: "Folder", entities: [], enableAnimations: true),
|
title: component.linkContents?.title ?? ChatFolderTitle(text: "Folder", entities: [], enableAnimations: true),
|
||||||
@ -457,37 +459,53 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
contentHeight += topIconSize.height
|
contentHeight += topIconSize.height
|
||||||
contentHeight += 20.0
|
contentHeight += 20.0
|
||||||
|
|
||||||
let text: String
|
let text: NSAttributedString
|
||||||
if case .linkList = component.subject {
|
if case .linkList = component.subject {
|
||||||
text = environment.strings.FolderLinkPreview_TextLinkList
|
text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextLinkList)
|
||||||
} else if let linkContents = component.linkContents {
|
} else if let linkContents = component.linkContents {
|
||||||
if case .remove = component.subject {
|
if case .remove = component.subject {
|
||||||
text = environment.strings.FolderLinkPreview_TextRemoveFolder
|
text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextRemoveFolder, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
|
||||||
} else if allChatsAdded {
|
} else if allChatsAdded {
|
||||||
text = environment.strings.FolderLinkPreview_TextAllAdded
|
text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextAllAdded, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
|
||||||
} else if linkContents.localFilterId == nil {
|
} else if linkContents.localFilterId == nil {
|
||||||
text = environment.strings.FolderLinkPreview_TextAddFolder
|
text = NSAttributedString(string: environment.strings.FolderLinkPreview_TextAddFolder, font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
|
||||||
} else {
|
} else if let title = linkContents.title {
|
||||||
let chatCountString: String = environment.strings.FolderLinkPreview_TextAddChatsCount(Int32(canAddChatCount))
|
let chatCountString: String = environment.strings.FolderLinkPreview_TextAddChatsCount(Int32(canAddChatCount))
|
||||||
//TODO:release
|
|
||||||
text = environment.strings.FolderLinkPreview_TextAddChats(chatCountString, linkContents.title?.text ?? "").string
|
let textValue = NSMutableAttributedString(string: environment.strings.FolderLinkPreview_TextAddChatsV2)
|
||||||
|
textValue.addAttributes([
|
||||||
|
.font: Font.regular(15.0),
|
||||||
|
.foregroundColor: environment.theme.list.freeTextColor
|
||||||
|
], range: NSRange(location: 0, length: textValue.length))
|
||||||
|
|
||||||
|
let folderRange = (textValue.string as NSString).range(of: "{folder}")
|
||||||
|
if folderRange.location != NSNotFound {
|
||||||
|
textValue.replaceCharacters(in: folderRange, with: "")
|
||||||
|
textValue.insert(title.attributedString(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor), at: folderRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
let chatsRange = (textValue.string as NSString).range(of: "{chats}")
|
||||||
|
if chatsRange.location != NSNotFound {
|
||||||
|
textValue.replaceCharacters(in: chatsRange, with: "")
|
||||||
|
textValue.insert(NSAttributedString(string: chatCountString, font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor), at: chatsRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
text = textValue
|
||||||
|
} else {
|
||||||
|
text = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
text = " "
|
text = NSAttributedString(string: " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor)
|
|
||||||
let bold = MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor)
|
|
||||||
|
|
||||||
let descriptionTextSize = self.descriptionText.update(
|
let descriptionTextSize = self.descriptionText.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||||
text: .markdown(text: text, attributes: MarkdownAttributes(
|
context: component.context,
|
||||||
body: body,
|
animationCache: component.context.animationCache,
|
||||||
bold: bold,
|
animationRenderer: component.context.animationRenderer,
|
||||||
link: body,
|
placeholderColor: environment.theme.list.freeTextColor.withMultipliedAlpha(0.1),
|
||||||
linkAttribute: { _ in nil }
|
text: .plain(text),
|
||||||
)),
|
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0
|
||||||
)),
|
)),
|
||||||
@ -982,8 +1000,12 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
disposable.add(component.context.account.postbox.addHiddenChatIds(peerIds: Array(self.selectedItems)))
|
disposable.add(component.context.account.postbox.addHiddenChatIds(peerIds: Array(self.selectedItems)))
|
||||||
disposable.add(component.context.account.viewTracker.addHiddenChatListFilterIds([folderId]))
|
disposable.add(component.context.account.viewTracker.addHiddenChatListFilterIds([folderId]))
|
||||||
|
|
||||||
//TODO:release
|
let folderTitle: ChatFolderTitle
|
||||||
let folderTitle = linkContents.title?.text ?? ""
|
if let title = linkContents.title {
|
||||||
|
folderTitle = title
|
||||||
|
} else {
|
||||||
|
folderTitle = ChatFolderTitle(text: "", entities: [], enableAnimations: true)
|
||||||
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
|
|
||||||
@ -1015,11 +1037,18 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let undoText = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastLeftTitleV2)
|
||||||
|
let folderRange = (undoText.string as NSString).range(of: "{folder}")
|
||||||
|
if folderRange.location != NSNotFound {
|
||||||
|
undoText.replaceCharacters(in: folderRange, with: "")
|
||||||
|
undoText.insert(folderTitle.rawAttributedString, at: folderRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
let context = component.context
|
let context = component.context
|
||||||
let selectedItems = self.selectedItems
|
let selectedItems = self.selectedItems
|
||||||
let undoOverlayController = UndoOverlayController(
|
let undoOverlayController = UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .removedChat(title: presentationData.strings.FolderLinkPreview_ToastLeftTitle(folderTitle).string, text: additionalText),
|
content: .removedChat(context: component.context, title: undoText, text: additionalText),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
action: { value in
|
action: { value in
|
||||||
if case .commit = value {
|
if case .commit = value {
|
||||||
@ -1112,8 +1141,14 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isUpdates {
|
if isUpdates {
|
||||||
//TODO:release
|
let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitleV2)
|
||||||
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitle(result.title.text).string, text: presentationData.strings.FolderLinkPreview_ToastChatsAddedText(Int32(result.newChatCount)), customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
|
let folderRange = (titleString.string as NSString).range(of: "{folder}")
|
||||||
|
if folderRange.location != NSNotFound {
|
||||||
|
titleString.replaceCharacters(in: folderRange, with: "")
|
||||||
|
titleString.insert(result.title.rawAttributedString, at: folderRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: titleString, text: NSAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedText(Int32(result.newChatCount))), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||||
} else if result.newChatCount != 0 {
|
} else if result.newChatCount != 0 {
|
||||||
let animationBackgroundColor: UIColor
|
let animationBackgroundColor: UIColor
|
||||||
if presentationData.theme.overallDarkAppearance {
|
if presentationData.theme.overallDarkAppearance {
|
||||||
@ -1121,8 +1156,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
animationBackgroundColor = UIColor(rgb: 0x474747)
|
animationBackgroundColor = UIColor(rgb: 0x474747)
|
||||||
}
|
}
|
||||||
//TODO:release
|
|
||||||
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitle(result.title.text).string, text: presentationData.strings.FolderLinkPreview_ToastFolderAddedText(Int32(result.newChatCount)), customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
|
let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitleV2)
|
||||||
|
let folderRange = (titleString.string as NSString).range(of: "{folder}")
|
||||||
|
if folderRange.location != NSNotFound {
|
||||||
|
titleString.replaceCharacters(in: folderRange, with: "")
|
||||||
|
titleString.insert(result.title.rawAttributedString, at: folderRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: titleString, text: NSAttributedString(string: presentationData.strings.FolderLinkPreview_ToastFolderAddedText(Int32(result.newChatCount))), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||||
} else {
|
} else {
|
||||||
let animationBackgroundColor: UIColor
|
let animationBackgroundColor: UIColor
|
||||||
if presentationData.theme.overallDarkAppearance {
|
if presentationData.theme.overallDarkAppearance {
|
||||||
@ -1130,8 +1172,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
} else {
|
} else {
|
||||||
animationBackgroundColor = UIColor(rgb: 0x474747)
|
animationBackgroundColor = UIColor(rgb: 0x474747)
|
||||||
}
|
}
|
||||||
//TODO:release
|
|
||||||
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitle(result.title.text).string, text: "", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
|
let titleString = NSMutableAttributedString(string: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitleV2)
|
||||||
|
let folderRange = (titleString.string as NSString).range(of: "{folder}")
|
||||||
|
if folderRange.location != NSNotFound {
|
||||||
|
titleString.replaceCharacters(in: folderRange, with: "")
|
||||||
|
titleString.insert(result.title.rawAttributedString, at: folderRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universalWithEntities(context: component.context, animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: titleString, text: NSAttributedString(string: ""), animateEntities: true, customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1310,7 +1359,6 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let navigationController = controller.navigationController
|
let navigationController = controller.navigationController
|
||||||
//TODO:release
|
|
||||||
controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: { _ in }, presentController: { [weak navigationController] c in
|
controller.push(folderInviteLinkListController(context: component.context, filterId: folderId, title: title, allPeerIds: peers.map(\.id), currentInvitation: link, linkUpdated: { _ in }, presentController: { [weak navigationController] c in
|
||||||
(navigationController?.topViewController as? ViewController)?.present(c, in: .window(.root))
|
(navigationController?.topViewController as? ViewController)?.present(c, in: .window(.root))
|
||||||
}))
|
}))
|
||||||
|
@ -90,7 +90,7 @@ public enum ChatTitleContent: Equatable {
|
|||||||
case replies
|
case replies
|
||||||
}
|
}
|
||||||
|
|
||||||
case peer(peerView: PeerData, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool)
|
case peer(peerView: PeerData, customTitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool)
|
||||||
case replyThread(type: ReplyThreadType, count: Int)
|
case replyThread(type: ReplyThreadType, count: Int)
|
||||||
case custom(String, String?, Bool)
|
case custom(String, String?, Bool)
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ public enum ChatTitleContent: Equatable {
|
|||||||
if customTitle != rhsCustomTitle {
|
if customTitle != rhsCustomTitle {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if onlineMemberCount != rhsOnlineMemberCount {
|
if onlineMemberCount.0 != rhsOnlineMemberCount.0 || onlineMemberCount.1 != rhsOnlineMemberCount.1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if isScheduledMessages != rhsIsScheduledMessages {
|
if isScheduledMessages != rhsIsScheduledMessages {
|
||||||
@ -616,7 +616,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
if channel.flags.contains(.isForum), customTitle != nil {
|
if channel.flags.contains(.isForum), customTitle != nil {
|
||||||
let string = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
|
let string = NSAttributedString(string: EnginePeer(peer).displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor)
|
||||||
state = .info(string, .generic)
|
state = .info(string, .generic)
|
||||||
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = onlineMemberCount.total ?? cachedChannelData.participantsSummary.memberCount {
|
||||||
if memberCount == 0 {
|
if memberCount == 0 {
|
||||||
let string: NSAttributedString
|
let string: NSAttributedString
|
||||||
if case .group = channel.info {
|
if case .group = channel.info {
|
||||||
@ -626,7 +626,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView {
|
|||||||
}
|
}
|
||||||
state = .info(string, .generic)
|
state = .info(string, .generic)
|
||||||
} else {
|
} else {
|
||||||
if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
|
if case .group = channel.info, let onlineMemberCount = onlineMemberCount.recent, onlineMemberCount > 1 {
|
||||||
let string = NSMutableAttributedString()
|
let string = NSMutableAttributedString()
|
||||||
|
|
||||||
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
|
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor))
|
||||||
|
@ -382,6 +382,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var enableAnimation: Bool = true
|
||||||
|
|
||||||
public weak var mirrorLayer: CALayer? {
|
public weak var mirrorLayer: CALayer? {
|
||||||
didSet {
|
didSet {
|
||||||
if let mirrorLayer = self.mirrorLayer {
|
if let mirrorLayer = self.mirrorLayer {
|
||||||
|
@ -330,7 +330,7 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, AS
|
|||||||
}
|
}
|
||||||
|
|
||||||
let context = self.context
|
let context = self.context
|
||||||
let undoController = UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: self.presentationData.strings.SavedMessages_SubChatDeleted, text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
let undoController = UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: self.presentationData.strings.SavedMessages_SubChatDeleted), text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||||
if value == .commit {
|
if value == .commit {
|
||||||
let _ = context.engine.messages.clearHistoryInteractively(peerId: context.account.peerId, threadId: peer.id.toInt64(), type: .forLocalPeer).startStandalone(completed: {
|
let _ = context.engine.messages.clearHistoryInteractively(peerId: context.account.peerId, threadId: peer.id.toInt64(), type: .forLocalPeer).startStandalone(completed: {
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
@ -1659,7 +1659,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
case let .group(groupId):
|
case let .group(groupId):
|
||||||
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
|
var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
|
||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
onlineMemberCount = context.account.viewTracker.peerView(groupId, updateData: false)
|
onlineMemberCount = context.account.viewTracker.peerView(groupId, updateData: false)
|
||||||
|> map { view -> Bool? in
|
|> map { view -> Bool? in
|
||||||
@ -1676,17 +1676,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
|
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
|
||||||
if let isLarge = isLarge {
|
if let isLarge = isLarge {
|
||||||
if isLarge {
|
if isLarge {
|
||||||
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|
||||||
|> map(Optional.init)
|
|> map { value -> (total: Int32?, recent: Int32?) in
|
||||||
|
return (nil, value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|
||||||
|> map(Optional.init)
|
|> map { value -> (total: Int32?, recent: Int32?) in
|
||||||
|
return (value.total, value.recent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single((nil, nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1695,9 +1699,11 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
context.account.viewTracker.peerView(groupId, updateData: false),
|
context.account.viewTracker.peerView(groupId, updateData: false),
|
||||||
onlineMemberCount
|
onlineMemberCount
|
||||||
)
|
)
|
||||||
|> map { peerView, onlineMemberCount -> PeerInfoStatusData? in
|
|> map { peerView, memberCountData -> PeerInfoStatusData? in
|
||||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
let (preciseTotalMemberCount, onlineMemberCount) = memberCountData
|
||||||
if let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
|
|
||||||
|
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = preciseTotalMemberCount ?? cachedChannelData.participantsSummary.memberCount {
|
||||||
|
if let onlineMemberCount, onlineMemberCount > 1 {
|
||||||
var string = ""
|
var string = ""
|
||||||
|
|
||||||
string.append("\(strings.Conversation_StatusMembers(Int32(memberCount))), ")
|
string.append("\(strings.Conversation_StatusMembers(Int32(memberCount))), ")
|
||||||
|
@ -172,7 +172,7 @@ private enum OldChannelsEntry: ItemListNodeEntry {
|
|||||||
case let .peersHeader(title):
|
case let .peersHeader(title):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||||
case let .peer(_, peer, selected):
|
case let .peer(_, peer, selected):
|
||||||
return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
||||||
arguments.togglePeer(peer.peer.id, true)
|
arguments.togglePeer(peer.peer.id, true)
|
||||||
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ private enum OldChannelsSearchEntry: Comparable, Identifiable {
|
|||||||
func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem {
|
func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
case let .peer(_, peer, selected):
|
case let .peer(_, peer, selected):
|
||||||
return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: NSAttributedString(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings)), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
||||||
interaction.togglePeer(peer.peer.id)
|
interaction.togglePeer(peer.peer.id)
|
||||||
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ final class PeerSelectionLoadingView: UIView {
|
|||||||
context: context,
|
context: context,
|
||||||
peerMode: .peer,
|
peerMode: .peer,
|
||||||
peer: .peer(peer: peer1, chatPeer: peer1),
|
peer: .peer(peer: peer1, chatPeer: peer1),
|
||||||
status: .custom(string: "status", multiline: false, isActive: false, icon: nil),
|
status: .custom(string: NSAttributedString(string: "status"), multiline: false, isActive: false, icon: nil),
|
||||||
badge: nil,
|
badge: nil,
|
||||||
requiresPremiumForMessaging: false,
|
requiresPremiumForMessaging: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -242,7 +242,7 @@ final class PeerSelectionLoadingView: UIView {
|
|||||||
let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
||||||
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 100.0)
|
fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 100.0)
|
||||||
|
|
||||||
let textFrame = itemNodes[sampleIndex].statusNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
let textFrame = itemNodes[sampleIndex].statusNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY)
|
||||||
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 40.0)
|
fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 40.0)
|
||||||
|
|
||||||
context.setBlendMode(.normal)
|
context.setBlendMode(.normal)
|
||||||
|
@ -119,7 +119,7 @@ final class PeerSelectionScreenComponent: Component {
|
|||||||
context: listNode.context,
|
context: listNode.context,
|
||||||
peerMode: .peer,
|
peerMode: .peer,
|
||||||
peer: .peer(peer: peer, chatPeer: peer),
|
peer: .peer(peer: peer, chatPeer: peer),
|
||||||
status: .custom(string: statusText, multiline: false, isActive: false, icon: nil),
|
status: .custom(string: NSAttributedString(string: statusText), multiline: false, isActive: false, icon: nil),
|
||||||
badge: nil,
|
badge: nil,
|
||||||
requiresPremiumForMessaging: false,
|
requiresPremiumForMessaging: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -20,11 +20,13 @@ private final class InlineStickerItem: Hashable {
|
|||||||
let emoji: ChatTextInputTextCustomEmojiAttribute
|
let emoji: ChatTextInputTextCustomEmojiAttribute
|
||||||
let file: TelegramMediaFile?
|
let file: TelegramMediaFile?
|
||||||
let fontSize: CGFloat
|
let fontSize: CGFloat
|
||||||
|
let enableAnimation: Bool
|
||||||
|
|
||||||
init(emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, fontSize: CGFloat) {
|
init(emoji: ChatTextInputTextCustomEmojiAttribute, file: TelegramMediaFile?, fontSize: CGFloat, enableAnimation: Bool) {
|
||||||
self.emoji = emoji
|
self.emoji = emoji
|
||||||
self.file = file
|
self.file = file
|
||||||
self.fontSize = fontSize
|
self.fontSize = fontSize
|
||||||
|
self.enableAnimation = enableAnimation
|
||||||
}
|
}
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
@ -42,6 +44,9 @@ private final class InlineStickerItem: Hashable {
|
|||||||
if lhs.fontSize != rhs.fontSize {
|
if lhs.fontSize != rhs.fontSize {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.enableAnimation != rhs.enableAnimation {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,19 +70,25 @@ public final class TextNodeWithEntities {
|
|||||||
public let renderer: MultiAnimationRenderer
|
public let renderer: MultiAnimationRenderer
|
||||||
public let placeholderColor: UIColor
|
public let placeholderColor: UIColor
|
||||||
public let attemptSynchronous: Bool
|
public let attemptSynchronous: Bool
|
||||||
|
public let emojiOffset: CGPoint
|
||||||
|
public let fontSizeNorm: CGFloat
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
cache: AnimationCache,
|
cache: AnimationCache,
|
||||||
renderer: MultiAnimationRenderer,
|
renderer: MultiAnimationRenderer,
|
||||||
placeholderColor: UIColor,
|
placeholderColor: UIColor,
|
||||||
attemptSynchronous: Bool
|
attemptSynchronous: Bool,
|
||||||
|
emojiOffset: CGPoint = CGPoint(),
|
||||||
|
fontSizeNorm: CGFloat = 17.0
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
self.renderer = renderer
|
self.renderer = renderer
|
||||||
self.placeholderColor = placeholderColor
|
self.placeholderColor = placeholderColor
|
||||||
self.attemptSynchronous = attemptSynchronous
|
self.attemptSynchronous = attemptSynchronous
|
||||||
|
self.emojiOffset = emojiOffset
|
||||||
|
self.fontSizeNorm = fontSizeNorm
|
||||||
}
|
}
|
||||||
|
|
||||||
public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments {
|
public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments {
|
||||||
@ -86,7 +97,8 @@ public final class TextNodeWithEntities {
|
|||||||
cache: self.cache,
|
cache: self.cache,
|
||||||
renderer: self.renderer,
|
renderer: self.renderer,
|
||||||
placeholderColor: color,
|
placeholderColor: color,
|
||||||
attemptSynchronous: self.attemptSynchronous
|
attemptSynchronous: self.attemptSynchronous,
|
||||||
|
emojiOffset: self.emojiOffset
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,6 +108,8 @@ public final class TextNodeWithEntities {
|
|||||||
|
|
||||||
private var enableLooping: Bool = true
|
private var enableLooping: Bool = true
|
||||||
|
|
||||||
|
public var resetEmojiToFirstFrameAutomatically: Bool = false
|
||||||
|
|
||||||
public var visibilityRect: CGRect? {
|
public var visibilityRect: CGRect? {
|
||||||
didSet {
|
didSet {
|
||||||
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibilityRect {
|
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibilityRect {
|
||||||
@ -110,7 +124,13 @@ public final class TextNodeWithEntities {
|
|||||||
} else {
|
} else {
|
||||||
isItemVisible = false
|
isItemVisible = false
|
||||||
}
|
}
|
||||||
itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible
|
let isVisibleForAnimations = self.enableLooping && isItemVisible && itemLayer.enableAnimation
|
||||||
|
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
|
||||||
|
itemLayer.isVisibleForAnimations = isVisibleForAnimations
|
||||||
|
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
|
||||||
|
itemLayer.reloadAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,7 +161,7 @@ public final class TextNodeWithEntities {
|
|||||||
|
|
||||||
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
|
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
|
||||||
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
|
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
|
||||||
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange)
|
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize, enableAnimation: value.enableAnimation), range: replacementRange)
|
||||||
updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange)
|
updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange)
|
||||||
originalTextId += 1
|
originalTextId += 1
|
||||||
|
|
||||||
@ -197,7 +217,7 @@ public final class TextNodeWithEntities {
|
|||||||
|
|
||||||
if let maybeNode = maybeNode {
|
if let maybeNode = maybeNode {
|
||||||
if let applyArguments = applyArguments {
|
if let applyArguments = applyArguments {
|
||||||
maybeNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false)
|
maybeNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, emojiOffset: applyArguments.emojiOffset, fontSizeNorm: applyArguments.fontSizeNorm)
|
||||||
}
|
}
|
||||||
|
|
||||||
return maybeNode
|
return maybeNode
|
||||||
@ -205,7 +225,7 @@ public final class TextNodeWithEntities {
|
|||||||
let resultNode = TextNodeWithEntities(textNode: result)
|
let resultNode = TextNodeWithEntities(textNode: result)
|
||||||
|
|
||||||
if let applyArguments = applyArguments {
|
if let applyArguments = applyArguments {
|
||||||
resultNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false)
|
resultNode.updateInlineStickers(context: applyArguments.context, cache: applyArguments.cache, renderer: applyArguments.renderer, textLayout: layout, placeholderColor: applyArguments.placeholderColor, attemptSynchronousLoad: false, emojiOffset: applyArguments.emojiOffset, fontSizeNorm: applyArguments.fontSizeNorm)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultNode
|
return resultNode
|
||||||
@ -222,7 +242,7 @@ public final class TextNodeWithEntities {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool) {
|
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, attemptSynchronousLoad: Bool, emojiOffset: CGPoint, fontSizeNorm: CGFloat) {
|
||||||
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
||||||
|
|
||||||
var nextIndexById: [Int64: Int] = [:]
|
var nextIndexById: [Int64: Int] = [:]
|
||||||
@ -241,9 +261,9 @@ public final class TextNodeWithEntities {
|
|||||||
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
|
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
|
||||||
validIds.append(id)
|
validIds.append(id)
|
||||||
|
|
||||||
let itemSize = floorToScreenPixels(stickerItem.fontSize * 24.0 / 17.0)
|
let itemSize = floorToScreenPixels(stickerItem.fontSize * 24.0 / fontSizeNorm)
|
||||||
|
|
||||||
var itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left, dy: textLayout.insets.top + 1.0).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
|
var itemFrame = CGRect(origin: item.rect.offsetBy(dx: textLayout.insets.left + emojiOffset.x, dy: textLayout.insets.top + 1.0 + emojiOffset.y).center, size: CGSize()).insetBy(dx: -itemSize / 2.0, dy: -itemSize / 2.0)
|
||||||
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
|
itemFrame.origin.x = floorToScreenPixels(itemFrame.origin.x)
|
||||||
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
|
itemFrame.origin.y = floorToScreenPixels(itemFrame.origin.y)
|
||||||
|
|
||||||
@ -256,8 +276,13 @@ public final class TextNodeWithEntities {
|
|||||||
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)
|
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: attemptSynchronousLoad, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)
|
||||||
self.inlineStickerItemLayers[id] = itemLayer
|
self.inlineStickerItemLayers[id] = itemLayer
|
||||||
self.textNode.layer.addSublayer(itemLayer)
|
self.textNode.layer.addSublayer(itemLayer)
|
||||||
|
}
|
||||||
itemLayer.isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame)
|
itemLayer.enableAnimation = stickerItem.enableAnimation
|
||||||
|
let isVisibleForAnimations = self.enableLooping && self.isItemVisible(itemRect: itemFrame) && itemLayer.enableAnimation
|
||||||
|
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
|
||||||
|
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
|
||||||
|
itemLayer.reloadAnimation()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemLayer.frame = itemFrame
|
itemLayer.frame = itemFrame
|
||||||
@ -301,12 +326,19 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||||||
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:]
|
private var inlineStickerItemLayers: [InlineStickerItemLayer.Key: InlineStickerItemLayer] = [:]
|
||||||
public private(set) var dustNode: InvisibleInkDustNode?
|
public private(set) var dustNode: InvisibleInkDustNode?
|
||||||
|
|
||||||
|
public var resetEmojiToFirstFrameAutomatically: Bool = false
|
||||||
|
|
||||||
public var visibility: Bool = false {
|
public var visibility: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility {
|
if !self.inlineStickerItemLayers.isEmpty && oldValue != self.visibility {
|
||||||
for (_, itemLayer) in self.inlineStickerItemLayers {
|
for (_, itemLayer) in self.inlineStickerItemLayers {
|
||||||
let isItemVisible: Bool = self.visibility
|
let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
|
||||||
itemLayer.isVisibleForAnimations = self.enableLooping && isItemVisible
|
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
|
||||||
|
itemLayer.isVisibleForAnimations = isVisibleForAnimations
|
||||||
|
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
|
||||||
|
itemLayer.reloadAnimation()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -375,7 +407,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||||||
|
|
||||||
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
|
let replacementRange = NSRange(location: 0, length: updatedSubstring.length)
|
||||||
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
|
updatedSubstring.addAttributes(string.attributes(at: range.location, effectiveRange: nil), range: replacementRange)
|
||||||
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize), range: replacementRange)
|
updatedSubstring.addAttribute(NSAttributedString.Key("Attribute__EmbeddedItem"), value: InlineStickerItem(emoji: value, file: value.file, fontSize: font.pointSize, enableAnimation: value.enableAnimation), range: replacementRange)
|
||||||
updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange)
|
updatedSubstring.addAttribute(originalTextAttributeKey, value: OriginalTextAttribute(id: originalTextId, string: string.attributedSubstring(from: range).string), range: replacementRange)
|
||||||
originalTextId += 1
|
originalTextId += 1
|
||||||
|
|
||||||
@ -437,7 +469,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||||||
|
|
||||||
var enableAnimations = true
|
var enableAnimations = true
|
||||||
if let arguments = self.arguments {
|
if let arguments = self.arguments {
|
||||||
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor)
|
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
|
||||||
enableAnimations = arguments.context.sharedContext.energyUsageSettings.fullTranslucency
|
enableAnimations = arguments.context.sharedContext.energyUsageSettings.fullTranslucency
|
||||||
}
|
}
|
||||||
self.updateSpoilers(enableAnimations: enableAnimations, textLayout: layout)
|
self.updateSpoilers(enableAnimations: enableAnimations, textLayout: layout)
|
||||||
@ -450,7 +482,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||||||
return layout.size
|
return layout.size
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor) {
|
private func updateInlineStickers(context: AccountContext, cache: AnimationCache, renderer: MultiAnimationRenderer, textLayout: TextNodeLayout?, placeholderColor: UIColor, fontSizeNorm: CGFloat) {
|
||||||
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
self.enableLooping = context.sharedContext.energyUsageSettings.loopEmoji
|
||||||
|
|
||||||
var nextIndexById: [Int64: Int] = [:]
|
var nextIndexById: [Int64: Int] = [:]
|
||||||
@ -469,7 +501,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||||||
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
|
let id = InlineStickerItemLayer.Key(id: stickerItem.emoji.fileId, index: index)
|
||||||
validIds.append(id)
|
validIds.append(id)
|
||||||
|
|
||||||
let itemSide = floor(stickerItem.fontSize * 24.0 / 17.0)
|
let itemSide = floor(stickerItem.fontSize * 24.0 / fontSizeNorm)
|
||||||
var itemSize = CGSize(width: itemSide, height: itemSide)
|
var itemSize = CGSize(width: itemSide, height: itemSide)
|
||||||
if let file = stickerItem.file, let customItemLayout = self.customItemLayout {
|
if let file = stickerItem.file, let customItemLayout = self.customItemLayout {
|
||||||
itemSize = customItemLayout(itemSize, file)
|
itemSize = customItemLayout(itemSize, file)
|
||||||
@ -486,8 +518,15 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||||||
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: false, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)
|
itemLayer = InlineStickerItemLayer(context: context, userLocation: .other, attemptSynchronousLoad: false, emoji: stickerItem.emoji, file: stickerItem.file, cache: cache, renderer: renderer, placeholderColor: placeholderColor, pointSize: CGSize(width: pointSize, height: pointSize), dynamicColor: item.textColor)
|
||||||
self.inlineStickerItemLayers[id] = itemLayer
|
self.inlineStickerItemLayers[id] = itemLayer
|
||||||
self.layer.addSublayer(itemLayer)
|
self.layer.addSublayer(itemLayer)
|
||||||
|
}
|
||||||
itemLayer.isVisibleForAnimations = self.enableLooping && self.visibility
|
|
||||||
|
itemLayer.enableAnimation = stickerItem.enableAnimation
|
||||||
|
let isVisibleForAnimations = self.enableLooping && self.visibility && itemLayer.enableAnimation
|
||||||
|
if itemLayer.isVisibleForAnimations != isVisibleForAnimations {
|
||||||
|
itemLayer.isVisibleForAnimations = isVisibleForAnimations
|
||||||
|
if !isVisibleForAnimations && self.resetEmojiToFirstFrameAutomatically {
|
||||||
|
itemLayer.reloadAnimation()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemLayer.frame = itemFrame
|
itemLayer.frame = itemFrame
|
||||||
@ -535,7 +574,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||||||
let _ = apply()
|
let _ = apply()
|
||||||
|
|
||||||
if let arguments = self.arguments {
|
if let arguments = self.arguments {
|
||||||
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor)
|
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated, numberOfLines: layout.numberOfLines)
|
return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated, numberOfLines: layout.numberOfLines)
|
||||||
@ -550,7 +589,7 @@ public class ImmediateTextNodeWithEntities: TextNode {
|
|||||||
let _ = apply()
|
let _ = apply()
|
||||||
|
|
||||||
if let arguments = self.arguments {
|
if let arguments = self.arguments {
|
||||||
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor)
|
self.updateInlineStickers(context: arguments.context, cache: arguments.cache, renderer: arguments.renderer, textLayout: layout, placeholderColor: arguments.placeholderColor, fontSizeNorm: arguments.fontSizeNorm)
|
||||||
}
|
}
|
||||||
|
|
||||||
return layout
|
return layout
|
||||||
|
@ -5221,11 +5221,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let peerId = chatLocationPeerId
|
let peerId = chatLocationPeerId
|
||||||
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
|
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
|
||||||
peerView.set(context.account.viewTracker.peerView(peerId))
|
peerView.set(context.account.viewTracker.peerView(peerId))
|
||||||
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
|
var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
|
||||||
var hasScheduledMessages: Signal<Bool, NoError> = .single(false)
|
var hasScheduledMessages: Signal<Bool, NoError> = .single(false)
|
||||||
|
|
||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
let recentOnlineSignal: Signal<Int32?, NoError> = peerView.get()
|
let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView.get()
|
||||||
|> map { view -> Bool? in
|
|> map { view -> Bool? in
|
||||||
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
||||||
if case .broadcast = peer.info {
|
if case .broadcast = peer.info {
|
||||||
@ -5240,17 +5240,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
|
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
|
||||||
if let isLarge = isLarge {
|
if let isLarge = isLarge {
|
||||||
if isLarge {
|
if isLarge {
|
||||||
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|
||||||
|> map(Optional.init)
|
|> map { value -> (total: Int32?, recent: Int32?) in
|
||||||
|
return (nil, value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|
||||||
|> map(Optional.init)
|
|> map { value -> (total: Int32?, recent: Int32?) in
|
||||||
|
return (value.total, value.recent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single((nil, nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onlineMemberCount = recentOnlineSignal
|
onlineMemberCount = recentOnlineSignal
|
||||||
@ -6192,9 +6196,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var onlineMemberCount: Signal<Int32?, NoError> = .single(nil)
|
var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
|
||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
let recentOnlineSignal: Signal<Int32?, NoError> = peerView
|
let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView
|
||||||
|> map { view -> Bool? in
|
|> map { view -> Bool? in
|
||||||
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
|
||||||
if case .broadcast = peer.info {
|
if case .broadcast = peer.info {
|
||||||
@ -6209,17 +6213,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> mapToSignal { isLarge -> Signal<Int32?, NoError> in
|
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
|
||||||
if let isLarge = isLarge {
|
if let isLarge = isLarge {
|
||||||
if isLarge {
|
if isLarge {
|
||||||
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|
||||||
|> map(Optional.init)
|
|> map { value -> (total: Int32?, recent: Int32?) in
|
||||||
|
return (nil, value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|
||||||
|> map(Optional.init)
|
|> map { value -> (total: Int32?, recent: Int32?) in
|
||||||
|
return (value.total, value.recent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return .single(nil)
|
return .single((nil, nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onlineMemberCount = recentOnlineSignal
|
onlineMemberCount = recentOnlineSignal
|
||||||
@ -6321,7 +6329,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
peerPresences: [:],
|
peerPresences: [:],
|
||||||
cachedData: nil
|
cachedData: nil
|
||||||
)
|
)
|
||||||
strongSelf.chatTitleView?.titleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: nil, isScheduledMessages: false, isMuted: false, customMessageCount: savedMessagesPeer?.messageCount ?? 0, isEnabled: true)
|
strongSelf.chatTitleView?.titleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: savedMessagesPeer?.messageCount ?? 0, isEnabled: true)
|
||||||
|
|
||||||
strongSelf.peerView = peerView
|
strongSelf.peerView = peerView
|
||||||
|
|
||||||
@ -8551,7 +8559,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
statusText = self.presentationData.strings.Undo_ChatCleared
|
statusText = self.presentationData.strings.Undo_ChatCleared
|
||||||
}
|
}
|
||||||
|
|
||||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, action: { [weak self] value in
|
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, action: { [weak self] value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ extension ChatControllerImpl {
|
|||||||
self.present(
|
self.present(
|
||||||
UndoOverlayController(
|
UndoOverlayController(
|
||||||
presentationData: self.presentationData,
|
presentationData: self.presentationData,
|
||||||
content: undoRights.isEmpty ? .actionSucceeded(title: title, text: text, cancel: nil, destructive: false) : .removedChat(title: title ?? text, text: title == nil ? nil : text),
|
content: undoRights.isEmpty ? .actionSucceeded(title: title, text: text, cancel: nil, destructive: false) : .removedChat(context: self.context, title: NSAttributedString(string: title ?? text), text: title == nil ? nil : text),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
action: { [weak self] action in
|
action: { [weak self] action in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -357,7 +357,7 @@ extension ChatControllerImpl {
|
|||||||
self.chatDisplayNode.historyNode.ignoreMessageIds = Set(messageIds)
|
self.chatDisplayNode.historyNode.ignoreMessageIds = Set(messageIds)
|
||||||
|
|
||||||
let undoTitle = self.presentationData.strings.Chat_MessagesDeletedToast_Text(Int32(messageIds.count))
|
let undoTitle = self.presentationData.strings.Chat_MessagesDeletedToast_Text(Int32(messageIds.count))
|
||||||
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: undoTitle, text: nil), elevatedLayout: false, position: .top, action: { [weak self] value in
|
self.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: self.context, title: NSAttributedString(string: undoTitle), text: nil), elevatedLayout: false, position: .top, action: { [weak self] value in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ extension ChatControllerImpl {
|
|||||||
|
|
||||||
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = range
|
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = range
|
||||||
|
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: statusText, text: nil), elevatedLayout: false, action: { value in
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, action: { value in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import ChatMessageTransitionNode
|
|||||||
import ChatControllerInteraction
|
import ChatControllerInteraction
|
||||||
import DustEffect
|
import DustEffect
|
||||||
import UrlHandling
|
import UrlHandling
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
struct ChatTopVisibleMessageRange: Equatable {
|
struct ChatTopVisibleMessageRange: Equatable {
|
||||||
var lowerBound: MessageIndex
|
var lowerBound: MessageIndex
|
||||||
@ -2313,41 +2314,57 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
self.currentOverscrollExpandProgress = expandProgress
|
self.currentOverscrollExpandProgress = expandProgress
|
||||||
|
|
||||||
if let nextChannelToRead = self.nextChannelToRead {
|
if let nextChannelToRead = self.nextChannelToRead {
|
||||||
let swipeText: (String, [(Int, NSRange)])
|
let swipeText: NSAttributedString
|
||||||
let releaseText: (String, [(Int, NSRange)])
|
let releaseText: NSAttributedString
|
||||||
switch nextChannelToRead.location {
|
switch nextChannelToRead.location {
|
||||||
case .same:
|
case .same:
|
||||||
if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl, chatController.customChatNavigationStack != nil {
|
if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl, chatController.customChatNavigationStack != nil {
|
||||||
swipeText = (self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress, [])
|
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress)
|
||||||
releaseText = (self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction, [])
|
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction)
|
||||||
} else if nextChannelToRead.threadData != nil {
|
} else if nextChannelToRead.threadData != nil {
|
||||||
swipeText = (self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress, [])
|
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress)
|
||||||
releaseText = (self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction, [])
|
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction)
|
||||||
} else {
|
} else {
|
||||||
swipeText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress, [])
|
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress)
|
||||||
releaseText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction, [])
|
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction)
|
||||||
}
|
}
|
||||||
case .archived:
|
case .archived:
|
||||||
swipeText = (self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress, [])
|
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeProgress)
|
||||||
releaseText = (self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction, [])
|
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelArchivedSwipeAction)
|
||||||
case .unarchived:
|
case .unarchived:
|
||||||
swipeText = (self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress, [])
|
swipeText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeProgress)
|
||||||
releaseText = (self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction, [])
|
releaseText = NSAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelUnarchivedSwipeAction)
|
||||||
case let .folder(_, title):
|
case let .folder(_, title):
|
||||||
//TODO:release
|
let swipeTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgressV2)
|
||||||
swipeText = self.currentPresentationData.strings.Chat_NextChannelFolderSwipeProgress(title.text)._tuple
|
let swipeFolderRange = (swipeTextValue.string as NSString).range(of: "{folder}")
|
||||||
releaseText = self.currentPresentationData.strings.Chat_NextChannelFolderSwipeAction(title.text)._tuple
|
if swipeFolderRange.location != NSNotFound {
|
||||||
|
swipeTextValue.replaceCharacters(in: swipeFolderRange, with: "")
|
||||||
|
swipeTextValue.insert(title.attributedString(attributes: [
|
||||||
|
ChatTextInputAttributes.bold: true
|
||||||
|
]), at: swipeFolderRange.location)
|
||||||
|
}
|
||||||
|
swipeText = swipeTextValue
|
||||||
|
|
||||||
|
let releaseTextValue = NSMutableAttributedString(string: self.currentPresentationData.strings.Chat_NextChannelFolderSwipeActionV2)
|
||||||
|
let releaseTextFolderRange = (releaseTextValue.string as NSString).range(of: "{folder}")
|
||||||
|
if releaseTextFolderRange.location != NSNotFound {
|
||||||
|
releaseTextValue.replaceCharacters(in: releaseTextFolderRange, with: "")
|
||||||
|
releaseTextValue.insert(title.attributedString(attributes: [
|
||||||
|
ChatTextInputAttributes.bold: true
|
||||||
|
]), at: releaseTextFolderRange.location)
|
||||||
|
}
|
||||||
|
releaseText = releaseTextValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if expandProgress < 0.1 {
|
if expandProgress < 0.1 {
|
||||||
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil)
|
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: nil)
|
||||||
} else if expandProgress >= 1.0 {
|
} else if expandProgress >= 1.0 {
|
||||||
if chatControllerNode.inputPanelOverscrollNode?.text.0 != releaseText.0 {
|
if chatControllerNode.inputPanelOverscrollNode?.text.string != releaseText.string {
|
||||||
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1))
|
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: releaseText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 1))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if chatControllerNode.inputPanelOverscrollNode?.text.0 != swipeText.0 {
|
if chatControllerNode.inputPanelOverscrollNode?.text.string != swipeText.string {
|
||||||
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2))
|
chatControllerNode.setChatInputPanelOverscrollNode(overscrollNode: ChatInputPanelOverscrollNode(context: self.context, text: swipeText, color: self.currentPresentationData.theme.theme.rootController.navigationBar.secondaryTextColor, priority: 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -550,7 +550,7 @@ public final class PeerChannelMemberCategoriesContextsManager {
|
|||||||
|> runOn(Queue.mainQueue())
|
|> runOn(Queue.mainQueue())
|
||||||
}
|
}
|
||||||
|
|
||||||
public func recentOnlineSmall(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal<Int32, NoError> {
|
public func recentOnlineSmall(engine: TelegramEngine, postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId) -> Signal<(total: Int32, recent: Int32), NoError> {
|
||||||
return Signal { [weak self] subscriber in
|
return Signal { [weak self] subscriber in
|
||||||
var previousIds: Set<PeerId>?
|
var previousIds: Set<PeerId>?
|
||||||
let statusesDisposable = MetaDisposable()
|
let statusesDisposable = MetaDisposable()
|
||||||
@ -587,7 +587,7 @@ public final class PeerChannelMemberCategoriesContextsManager {
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|> deliverOnMainQueue).start(next: { count in
|
|> deliverOnMainQueue).start(next: { count in
|
||||||
subscriber.putNext(count)
|
subscriber.putNext((Int32(updatedIds.count), count))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -402,6 +402,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
|
|||||||
case file
|
case file
|
||||||
case topicId
|
case topicId
|
||||||
case topicInfo
|
case topicInfo
|
||||||
|
case enableAnimation
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Custom: Codable {
|
public enum Custom: Codable {
|
||||||
@ -417,12 +418,14 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
|
|||||||
public let fileId: Int64
|
public let fileId: Int64
|
||||||
public let file: TelegramMediaFile?
|
public let file: TelegramMediaFile?
|
||||||
public let custom: Custom?
|
public let custom: Custom?
|
||||||
|
public let enableAnimation: Bool
|
||||||
|
|
||||||
public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, custom: Custom? = nil) {
|
public init(interactivelySelectedFromPackId: ItemCollectionId?, fileId: Int64, file: TelegramMediaFile?, custom: Custom? = nil, enableAnimation: Bool = true) {
|
||||||
self.interactivelySelectedFromPackId = interactivelySelectedFromPackId
|
self.interactivelySelectedFromPackId = interactivelySelectedFromPackId
|
||||||
self.fileId = fileId
|
self.fileId = fileId
|
||||||
self.file = file
|
self.file = file
|
||||||
self.custom = custom
|
self.custom = custom
|
||||||
|
self.enableAnimation = enableAnimation
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
@ -433,6 +436,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
|
|||||||
self.fileId = try container.decode(Int64.self, forKey: .fileId)
|
self.fileId = try container.decode(Int64.self, forKey: .fileId)
|
||||||
self.file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file)
|
self.file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file)
|
||||||
self.custom = nil
|
self.custom = nil
|
||||||
|
self.enableAnimation = try container.decodeIfPresent(Bool.self, forKey: .enableAnimation) ?? true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -440,6 +444,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
|
|||||||
try container.encodeIfPresent(self.interactivelySelectedFromPackId, forKey: .interactivelySelectedFromPackId)
|
try container.encodeIfPresent(self.interactivelySelectedFromPackId, forKey: .interactivelySelectedFromPackId)
|
||||||
try container.encode(self.fileId, forKey: .fileId)
|
try container.encode(self.fileId, forKey: .fileId)
|
||||||
try container.encodeIfPresent(self.file, forKey: .file)
|
try container.encodeIfPresent(self.file, forKey: .file)
|
||||||
|
try container.encode(self.enableAnimation, forKey: .enableAnimation)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func isEqual(_ object: Any?) -> Bool {
|
override public func isEqual(_ object: Any?) -> Bool {
|
||||||
|
@ -8,7 +8,7 @@ import ComponentFlow
|
|||||||
import AnimatedTextComponent
|
import AnimatedTextComponent
|
||||||
|
|
||||||
public enum UndoOverlayContent {
|
public enum UndoOverlayContent {
|
||||||
case removedChat(title: String, text: String?)
|
case removedChat(context: AccountContext, title: NSAttributedString, text: String?)
|
||||||
case archivedChat(peerId: Int64, title: String, text: String, undo: Bool)
|
case archivedChat(peerId: Int64, title: String, text: String, undo: Bool)
|
||||||
case hidArchive(title: String, text: String, undo: Bool)
|
case hidArchive(title: String, text: String, undo: Bool)
|
||||||
case revealedArchive(title: String, text: String, undo: Bool)
|
case revealedArchive(title: String, text: String, undo: Bool)
|
||||||
@ -19,8 +19,8 @@ public enum UndoOverlayContent {
|
|||||||
case actionSucceeded(title: String?, text: String, cancel: String?, destructive: Bool)
|
case actionSucceeded(title: String?, text: String, cancel: String?, destructive: Bool)
|
||||||
case stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext)
|
case stickersModified(title: String, text: String, undo: Bool, info: StickerPackCollectionInfo, topItem: StickerPackItem?, context: AccountContext)
|
||||||
case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?)
|
case dice(dice: TelegramMediaDice, context: AccountContext, text: String, action: String?)
|
||||||
case chatAddedToFolder(chatTitle: String, folderTitle: String)
|
case chatAddedToFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString)
|
||||||
case chatRemovedFromFolder(chatTitle: String, folderTitle: String)
|
case chatRemovedFromFolder(context: AccountContext, chatTitle: String, folderTitle: NSAttributedString)
|
||||||
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
|
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
|
||||||
case setProximityAlert(title: String, text: String, cancelled: Bool)
|
case setProximityAlert(title: String, text: String, cancelled: Bool)
|
||||||
case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, title: String?, text: String, action: String?, duration: Double)
|
case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, title: String?, text: String, action: String?, duration: Double)
|
||||||
@ -45,6 +45,7 @@ public enum UndoOverlayContent {
|
|||||||
case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?)
|
case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?)
|
||||||
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)
|
case notificationSoundAdded(title: String, text: String, action: (() -> Void)?)
|
||||||
case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?, timeout: Double?)
|
case universal(animation: String, scale: CGFloat, colors: [String: UIColor], title: String?, text: String, customUndoText: String?, timeout: Double?)
|
||||||
|
case universalWithEntities(context: AccountContext, animation: String, scale: CGFloat, colors: [String: UIColor], title: NSAttributedString?, text: NSAttributedString, animateEntities: Bool, customUndoText: String?, timeout: Double?)
|
||||||
case universalImage(image: UIImage, size: CGSize?, title: String?, text: String, customUndoText: String?, timeout: Double?)
|
case universalImage(image: UIImage, size: CGSize?, title: String?, text: String, customUndoText: String?, timeout: Double?)
|
||||||
case premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?)
|
case premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?)
|
||||||
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)
|
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)
|
||||||
|
@ -46,7 +46,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
private var stickerSourceSize: CGSize?
|
private var stickerSourceSize: CGSize?
|
||||||
private var stickerOffset: CGPoint?
|
private var stickerOffset: CGPoint?
|
||||||
private var emojiStatus: ComponentView<Empty>?
|
private var emojiStatus: ComponentView<Empty>?
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleNode: ImmediateTextNodeWithEntities
|
||||||
private let textNode: ImmediateTextNodeWithEntities
|
private let textNode: ImmediateTextNodeWithEntities
|
||||||
private var textComponent: ComponentView<Empty>?
|
private var textComponent: ComponentView<Empty>?
|
||||||
private var animatedTextItems: [AnimatedTextComponent.Item]?
|
private var animatedTextItems: [AnimatedTextComponent.Item]?
|
||||||
@ -92,7 +92,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.timerTextNode = ImmediateTextNode()
|
self.timerTextNode = ImmediateTextNode()
|
||||||
self.timerTextNode.displaysAsynchronously = false
|
self.timerTextNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleNode = ImmediateTextNodeWithEntities()
|
||||||
self.titleNode.layer.anchorPoint = CGPoint()
|
self.titleNode.layer.anchorPoint = CGPoint()
|
||||||
self.titleNode.displaysAsynchronously = false
|
self.titleNode.displaysAsynchronously = false
|
||||||
self.titleNode.maximumNumberOfLines = 0
|
self.titleNode.maximumNumberOfLines = 0
|
||||||
@ -115,22 +115,51 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
var isUserInteractionEnabled = false
|
var isUserInteractionEnabled = false
|
||||||
switch content {
|
switch content {
|
||||||
case let .removedChat(title, text):
|
case let .removedChat(context, title, text):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
self.iconCheckNode = nil
|
self.iconCheckNode = nil
|
||||||
self.animationNode = nil
|
self.animationNode = nil
|
||||||
self.animatedStickerNode = nil
|
self.animatedStickerNode = nil
|
||||||
|
|
||||||
|
let attributedTitle = NSMutableAttributedString(string: title.string)
|
||||||
|
attributedTitle.addAttribute(.font, value: Font.semibold(14.0), range: NSRange(location: 0, length: title.length))
|
||||||
|
attributedTitle.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(location: 0, length: title.length))
|
||||||
|
title.enumerateAttributes(in: NSRange(location: 0, length: title.length), using: { attributes, range, _ in
|
||||||
|
for (key, value) in attributes {
|
||||||
|
attributedTitle.addAttribute(key, value: value, range: range)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if let text {
|
if let text {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
self.titleNode.attributedText = attributedTitle
|
||||||
|
|
||||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||||
self.textNode.attributedText = attributedText
|
self.textNode.attributedText = attributedText
|
||||||
} else {
|
} else {
|
||||||
self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
self.textNode.attributedText = attributedTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.titleNode.visibility = true
|
||||||
|
self.titleNode.arguments = TextNodeWithEntities.Arguments(
|
||||||
|
context: context,
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
|
||||||
|
attemptSynchronous: false
|
||||||
|
)
|
||||||
|
|
||||||
|
self.textNode.visibility = true
|
||||||
|
self.textNode.arguments = TextNodeWithEntities.Arguments(
|
||||||
|
context: context,
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
|
||||||
|
attemptSynchronous: false
|
||||||
|
)
|
||||||
|
|
||||||
displayUndo = true
|
displayUndo = true
|
||||||
self.originalRemainingSeconds = 5
|
self.originalRemainingSeconds = 5
|
||||||
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||||
@ -343,38 +372,67 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 5
|
self.originalRemainingSeconds = 5
|
||||||
case let .chatAddedToFolder(chatTitle, folderTitle):
|
case let .chatAddedToFolder(context, chatTitle, folderTitle), let .chatRemovedFromFolder(context, chatTitle, folderTitle):
|
||||||
self.avatarNode = nil
|
self.avatarNode = nil
|
||||||
self.iconNode = nil
|
self.iconNode = nil
|
||||||
self.iconCheckNode = nil
|
self.iconCheckNode = nil
|
||||||
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
|
||||||
|
let baseString: String
|
||||||
|
if case .chatAddedToFolder = content {
|
||||||
|
baseString = presentationData.strings.ChatList_AddedToFolderTooltipV2
|
||||||
|
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||||
|
} else {
|
||||||
|
baseString = presentationData.strings.ChatList_RemovedFromFolderTooltipV2
|
||||||
|
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
||||||
|
}
|
||||||
self.animatedStickerNode = nil
|
self.animatedStickerNode = nil
|
||||||
|
|
||||||
let formattedString = presentationData.strings.ChatList_AddedToFolderTooltip(chatTitle, folderTitle)
|
let string = NSMutableAttributedString(string: baseString)
|
||||||
|
string.addAttributes([
|
||||||
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
|
.font: Font.regular(14.0),
|
||||||
for range in formattedString.ranges {
|
.foregroundColor: UIColor.white
|
||||||
string.addAttribute(.font, value: Font.regular(14.0), range: range.range)
|
], range: NSRange(location: 0, length: string.length))
|
||||||
}
|
|
||||||
|
let folderRange = (string.string as NSString).range(of: "{folder}")
|
||||||
self.textNode.attributedText = string
|
if folderRange.location != NSNotFound {
|
||||||
displayUndo = false
|
string.replaceCharacters(in: folderRange, with: "")
|
||||||
self.originalRemainingSeconds = 5
|
let processedFolderTitle = NSMutableAttributedString(string: folderTitle.string)
|
||||||
case let .chatRemovedFromFolder(chatTitle, folderTitle):
|
processedFolderTitle.addAttributes([
|
||||||
self.avatarNode = nil
|
.font: Font.semibold(14.0),
|
||||||
self.iconNode = nil
|
.foregroundColor: UIColor.white
|
||||||
self.iconCheckNode = nil
|
], range: NSRange(location: 0, length: processedFolderTitle.length))
|
||||||
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
|
folderTitle.enumerateAttributes(in: NSRange(location: 0, length: folderTitle.length), using: { attributes, range, _ in
|
||||||
self.animatedStickerNode = nil
|
for (key, value) in attributes {
|
||||||
|
if key == ChatTextInputAttributes.bold {
|
||||||
let formattedString = presentationData.strings.ChatList_RemovedFromFolderTooltip(chatTitle, folderTitle)
|
processedFolderTitle.addAttribute(.font, value: Font.semibold(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.italic {
|
||||||
let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString.string, font: Font.regular(14.0), textColor: .white))
|
processedFolderTitle.addAttribute(.font, value: Font.italic(14.0), range: range)
|
||||||
for range in formattedString.ranges {
|
} else if key == ChatTextInputAttributes.monospace {
|
||||||
string.addAttribute(.font, value: Font.regular(14.0), range: range.range)
|
processedFolderTitle.addAttribute(.font, value: Font.monospace(14.0), range: range)
|
||||||
|
} else {
|
||||||
|
processedFolderTitle.addAttribute(key, value: value, range: range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
string.insert(processedFolderTitle, at: folderRange.location)
|
||||||
|
}
|
||||||
|
|
||||||
|
let chatRange = (string.string as NSString).range(of: "{chat}")
|
||||||
|
if chatRange.location != NSNotFound {
|
||||||
|
string.replaceCharacters(in: chatRange, with: "")
|
||||||
|
string.insert(NSAttributedString(string: chatTitle, font: Font.semibold(14.0), textColor: .white), at: chatRange.location)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.textNode.attributedText = string
|
self.textNode.attributedText = string
|
||||||
|
self.textNode.visibility = true
|
||||||
|
self.textNode.arguments = TextNodeWithEntities.Arguments(
|
||||||
|
context: context,
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
|
||||||
|
attemptSynchronous: false
|
||||||
|
)
|
||||||
|
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 5
|
self.originalRemainingSeconds = 5
|
||||||
case let .paymentSent(currencyValue, itemTitle):
|
case let .paymentSent(currencyValue, itemTitle):
|
||||||
@ -1072,6 +1130,86 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
self.textNode.maximumNumberOfLines = 5
|
self.textNode.maximumNumberOfLines = 5
|
||||||
|
|
||||||
|
if let customUndoText = customUndoText {
|
||||||
|
undoText = customUndoText
|
||||||
|
displayUndo = true
|
||||||
|
} else {
|
||||||
|
displayUndo = false
|
||||||
|
}
|
||||||
|
case let .universalWithEntities(context, animation, scale, colors, title, text, animateEntities, customUndoText, timeout):
|
||||||
|
self.avatarNode = nil
|
||||||
|
self.iconNode = nil
|
||||||
|
self.iconCheckNode = nil
|
||||||
|
self.animationNode = AnimationNode(animation: animation, colors: colors, scale: scale)
|
||||||
|
self.animatedStickerNode = nil
|
||||||
|
|
||||||
|
var attributedTitle: NSAttributedString?
|
||||||
|
if let title {
|
||||||
|
let attributedTitleValue = NSMutableAttributedString(string: title.string)
|
||||||
|
attributedTitleValue.addAttribute(.font, value: Font.semibold(14.0), range: NSRange(location: 0, length: title.length))
|
||||||
|
attributedTitleValue.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(location: 0, length: title.length))
|
||||||
|
title.enumerateAttributes(in: NSRange(location: 0, length: title.length), using: { attributes, range, _ in
|
||||||
|
for (key, value) in attributes {
|
||||||
|
attributedTitleValue.addAttribute(key, value: value, range: range)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
attributedTitle = attributedTitleValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let attributedTitle, text.length == 0 {
|
||||||
|
self.titleNode.attributedText = nil
|
||||||
|
self.textNode.attributedText = attributedTitle
|
||||||
|
} else {
|
||||||
|
if let attributedTitle {
|
||||||
|
self.titleNode.attributedText = attributedTitle
|
||||||
|
} else {
|
||||||
|
self.titleNode.attributedText = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let attributedText = NSMutableAttributedString(string: text.string)
|
||||||
|
attributedText.addAttribute(.font, value: Font.regular(14.0), range: NSRange(location: 0, length: text.length))
|
||||||
|
attributedText.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(location: 0, length: text.length))
|
||||||
|
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), using: { attributes, range, _ in
|
||||||
|
for (key, value) in attributes {
|
||||||
|
if key == ChatTextInputAttributes.bold {
|
||||||
|
attributedText.addAttribute(.font, value: Font.semibold(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.italic {
|
||||||
|
attributedText.addAttribute(.font, value: Font.italic(14.0), range: range)
|
||||||
|
} else if key == ChatTextInputAttributes.monospace {
|
||||||
|
attributedText.addAttribute(.font, value: Font.monospace(14.0), range: range)
|
||||||
|
} else {
|
||||||
|
attributedText.addAttribute(key, value: value, range: range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.textNode.attributedText = attributedText
|
||||||
|
}
|
||||||
|
|
||||||
|
if text.string.contains("](") {
|
||||||
|
isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
self.originalRemainingSeconds = timeout ?? (isUserInteractionEnabled ? 5 : 3)
|
||||||
|
|
||||||
|
self.titleNode.visibility = animateEntities
|
||||||
|
self.titleNode.arguments = TextNodeWithEntities.Arguments(
|
||||||
|
context: context,
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
|
||||||
|
attemptSynchronous: false
|
||||||
|
)
|
||||||
|
|
||||||
|
self.textNode.maximumNumberOfLines = 5
|
||||||
|
self.textNode.visibility = animateEntities
|
||||||
|
self.textNode.arguments = TextNodeWithEntities.Arguments(
|
||||||
|
context: context,
|
||||||
|
cache: context.animationCache,
|
||||||
|
renderer: context.animationRenderer,
|
||||||
|
placeholderColor: UIColor(white: 1.0, alpha: 0.1),
|
||||||
|
attemptSynchronous: false
|
||||||
|
)
|
||||||
|
|
||||||
if let customUndoText = customUndoText {
|
if let customUndoText = customUndoText {
|
||||||
undoText = customUndoText
|
undoText = customUndoText
|
||||||
displayUndo = true
|
displayUndo = true
|
||||||
@ -1431,7 +1569,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
} else {
|
} else {
|
||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal,. universalImage, .premiumPaywall, .peers, .messageTagged:
|
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal, .universalWithEntities, .universalImage, .premiumPaywall, .peers, .messageTagged:
|
||||||
if self.textNode.tapAttributeAction != nil || displayUndo {
|
if self.textNode.tapAttributeAction != nil || displayUndo {
|
||||||
self.isUserInteractionEnabled = true
|
self.isUserInteractionEnabled = true
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user