Search filters fixes

This commit is contained in:
Ilya Laktyushin 2020-09-27 23:35:05 +04:00
parent bf1489eef8
commit 8e22244eab
10 changed files with 142 additions and 18 deletions

View File

@ -1682,12 +1682,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
scrollToTop()
}
var displaySearchFilters = true
if strongSelf.chatListDisplayNode.containerNode.mainItemNode.entriesCount < 10 {
displaySearchFilters = false
}
if let searchContentNode = strongSelf.searchContentNode {
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, navigationController: strongSelf.navigationController as? NavigationController) {
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, displaySearchFilters: displaySearchFilters, navigationController: strongSelf.navigationController as? NavigationController) {
let (filterContainerNode, activate) = filterContainerNodeAndActivate
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
if let parentController = strongSelf.parent as? TabBarController {
parentController.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: true)
if displaySearchFilters {
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
if let parentController = strongSelf.parent as? TabBarController {
parentController.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: true)
}
}
activate()
}

View File

@ -431,6 +431,10 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
return self.currentItemNodeValue!.listNode
}
var mainItemNode: ChatListNode {
return self.itemNodes[.all]!.listNode
}
private let currentItemStateValue = Promise<(state: ChatListNodeState, filterId: Int32?)>()
var currentItemState: Signal<(state: ChatListNodeState, filterId: Int32?), NoError> {
return self.currentItemStateValue.get()
@ -1146,7 +1150,7 @@ final class ChatListControllerNode: ASDisplayNode {
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? {
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? {
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return nil
}

View File

@ -504,7 +504,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case let .message(message, peer, readState, presentationData, totalCount, selected, displayCustomHeader):
let header = ChatListSearchItemHeader(type: .messages(totalCount), theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none
if let tagMask = tagMask, tagMask != .photoOrVideo && (searchQuery?.isEmpty ?? true) {
if let tagMask = tagMask, tagMask != .photoOrVideo {
return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: true)
} else {
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
@ -779,9 +779,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.addSubnode(self.recentListNode)
self.addSubnode(self.listNode)
self.addSubnode(self.mediaNode)
if key != .chats {
self.addSubnode(self.shimmerNode)
}
self.addSubnode(self.shimmerNode)
self.addSubnode(self.mediaAccessoryPanelContainer)
self.addSubnode(self.emptyResultsAnimationNode)
@ -1285,8 +1283,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let previousSelectedMessages = Atomic<Set<MessageId>?>(value: nil)
let _ = (searchQuery
|> deliverOnMainQueue).start(next: { [weak self, weak chatListInteraction] query in
|> deliverOnMainQueue).start(next: { [weak self, weak listInteraction, weak chatListInteraction] query in
self?.searchQueryValue = query
listInteraction?.searchTextHighightState = query
chatListInteraction?.searchTextHighightState = query
})
@ -1817,7 +1816,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let insets = UIEdgeInsets(top: topPanelHeight, left: sideInset, bottom: bottomInset, right: sideInset)
self.shimmerNode.frame = CGRect(origin: CGPoint(x: overflowInset, y: topInset), size: CGSize(width: size.width - overflowInset * 2.0, height: size.height))
self.shimmerNode.update(context: self.context, size: CGSize(width: size.width - overflowInset * 2.0, height: size.height), presentationData: self.presentationData, key: (self.searchQueryValue?.isEmpty ?? true) ? self.key : .chats, transition: transition)
self.shimmerNode.update(context: self.context, size: CGSize(width: size.width - overflowInset * 2.0, height: size.height), presentationData: self.presentationData, key: !(self.searchQueryValue?.isEmpty ?? true) && self.key == .media ? .chats : self.key, transition: transition)
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
self.recentListNode.frame = CGRect(origin: CGPoint(), size: size)
@ -1995,7 +1994,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
strongSelf.emptyResultsTextNode.isHidden = !emptyResults
strongSelf.emptyResultsAnimationNode.visibility = emptyResults
ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateAlpha(node: strongSelf.shimmerNode, alpha: transition.isLoading ? 1.0 : 0.0)
let displayPlaceholder = transition.isLoading && (strongSelf.key != .chats || strongSelf.searchOptionsValue?.peer != nil || strongSelf.searchOptionsValue?.minDate != nil || strongSelf.searchOptionsValue?.maxDate != nil)
ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).updateAlpha(node: strongSelf.shimmerNode, alpha: displayPlaceholder ? 1.0 : 0.0)
strongSelf.recentListNode.isHidden = displayingResults || strongSelf.peersFilter.contains(.excludeRecent)
// strongSelf.dimNode.isHidden = displayingResults

View File

@ -83,8 +83,7 @@ func suggestDates(for string: String, strings: PresentationStrings, dateTimeForm
}
} else {
do {
let dd = try NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue)
if let match = dd.firstMatch(in: string, options: [], range: NSMakeRange(0, string.utf16.count)), let date = match.date, date > telegramReleaseDate {
func process(_ date: Date) {
var resultDate = date
if resultDate > now && !calendar.isDate(resultDate, equalTo: now, toGranularity: .year) {
if let date = calendar.date(byAdding: .year, value: -1, to: resultDate) {
@ -103,6 +102,12 @@ func suggestDates(for string: String, strings: PresentationStrings, dateTimeForm
result.append((resultDate, nil))
}
}
let dd = try NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue)
if let match = dd.firstMatch(in: string, options: [], range: NSMakeRange(0, string.utf16.count)), let date = match.date, date > telegramReleaseDate {
process(date)
} else if let match = dd.firstMatch(in: string.replacingOccurrences(of: ".", with: "/"), options: [], range: NSMakeRange(0, string.utf16.count)), let date = match.date, date > telegramReleaseDate {
process(date)
}
} catch {
}

View File

@ -442,6 +442,13 @@ public final class ChatListNode: ListView {
private let viewProcessingQueue = Queue()
private var chatListView: ChatListNodeView?
var entriesCount: Int {
if let chatListView = self.chatListView {
return chatListView.filteredEntries.count
} else {
return 0
}
}
private var interaction: ChatListNodeInteraction?
private var dequeuedInitialTransitionOnLayout = false

View File

@ -220,7 +220,7 @@ class ContactsAddItemNode: ListViewItemNode {
if let updatedIcon = updatedIcon {
strongSelf.iconNode.image = updatedIcon
}
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(x: 14.0, y: 5.0, width: 40.0, height: 40.0))
transition.updateFrame(node: strongSelf.iconNode, frame: CGRect(x: params.leftInset + 14.0, y: 5.0, width: 40.0, height: 40.0))
let topHighlightInset: CGFloat = (first || !nodeLayout.insets.top.isZero) ? 0.0 : separatorHeight
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))

View File

@ -383,7 +383,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)))
self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom, right: layout.safeInsets.right), duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
if !hadValidLayout {
while !self.enqueuedTransitions.isEmpty {

View File

@ -123,6 +123,28 @@ private enum FileIconImage: Equatable {
}
}
final class CachedChatListSearchResult {
let text: String
let searchQuery: String
let resultRanges: [Range<String.Index>]
init(text: String, searchQuery: String, resultRanges: [Range<String.Index>]) {
self.text = text
self.searchQuery = searchQuery
self.resultRanges = resultRanges
}
func matches(text: String, searchQuery: String) -> Bool {
if self.text != text {
return false
}
if self.searchQuery != searchQuery {
return false
}
return true
}
}
public final class ListMessageFileItemNode: ListMessageNode {
private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
@ -139,6 +161,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
private var selectionNode: ItemListSelectableControlNode?
public let titleNode: TextNode
public let textNode: TextNode
public let descriptionNode: TextNode
private let descriptionProgressNode: ImmediateTextNode
public let dateNode: TextNode
@ -170,6 +193,8 @@ public final class ListMessageFileItemNode: ListMessageNode {
private var contentSizeValue: CGSize?
private var currentLeftOffset: CGFloat = 0.0
private var cachedChatListSearchResult: CachedChatListSearchResult?
public required init() {
self.contextSourceNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
@ -189,6 +214,9 @@ public final class ListMessageFileItemNode: ListMessageNode {
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.textNode = TextNode()
self.textNode.isUserInteractionEnabled = false
self.descriptionNode = TextNode()
self.descriptionNode.isUserInteractionEnabled = false
@ -231,6 +259,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
self.contextSourceNode.contentNode.addSubnode(self.extractedBackgroundImageNode)
self.contextSourceNode.contentNode.addSubnode(self.offsetContainerNode)
self.offsetContainerNode.addSubnode(self.titleNode)
self.offsetContainerNode.addSubnode(self.textNode)
self.offsetContainerNode.addSubnode(self.descriptionNode)
self.offsetContainerNode.addSubnode(self.descriptionProgressNode)
self.offsetContainerNode.addSubnode(self.dateNode)
@ -307,6 +336,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
override public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode)
let textNodeMakeLayout = TextNode.asyncLayout(self.textNode)
let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode)
let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText)
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
@ -315,6 +345,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
let currentMedia = self.currentMedia
let currentMessage = self.message
let currentIconImage = self.currentIconImage
let currentChatListSearchResult = self.cachedChatListSearchResult
let currentItem = self.appliedItem
@ -547,6 +578,36 @@ public final class ListMessageFileItemNode: ListMessageNode {
}
}
var chatListSearchResult: CachedChatListSearchResult?
if let searchQuery = item.interaction.searchTextHighightState {
if let cached = currentChatListSearchResult, cached.matches(text: item.message.text, searchQuery: searchQuery) {
chatListSearchResult = cached
} else {
let (ranges, text) = findSubstringRanges(in: item.message.text, query: searchQuery)
chatListSearchResult = CachedChatListSearchResult(text: text, searchQuery: searchQuery, resultRanges: ranges)
}
} else {
chatListSearchResult = nil
}
var captionText: NSMutableAttributedString?
if let chatListSearchResult = chatListSearchResult, !chatListSearchResult.resultRanges.isEmpty, let firstRange = chatListSearchResult.resultRanges.first {
let text = NSMutableAttributedString(string: item.message.text, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
for range in chatListSearchResult.resultRanges {
let stringRange = NSRange(range, in: chatListSearchResult.text)
if stringRange.location >= 0 && stringRange.location + stringRange.length <= text.length {
text.addAttribute(.foregroundColor, value: item.presentationData.theme.theme.chatList.messageHighlightedTextColor, range: stringRange)
}
}
captionText = text
let firstRangeOrigin = item.message.text.distance(from: item.message.text.startIndex, to: firstRange.lowerBound)
if firstRangeOrigin > 20 {
captionText = text.attributedSubstring(from: NSMakeRange(firstRangeOrigin - 10, text.length - firstRangeOrigin + 10)).mutableCopy() as? NSMutableAttributedString
captionText?.insert(NSAttributedString(string: "\u{2026}", attributes: [NSAttributedString.Key.font: descriptionFont, NSAttributedString.Key.foregroundColor: item.presentationData.theme.theme.list.itemSecondaryTextColor]), at: 0)
}
}
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.message.timestamp, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat)
let dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
@ -555,6 +616,8 @@ public final class ListMessageFileItemNode: ListMessageNode {
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - leftOffset - rightInset - dateNodeLayout.size.width - 4.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (textNodeLayout, textNodeApply) = textNodeMakeLayout(TextNodeLayoutArguments(attributedString: captionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -600,7 +663,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
insets.top += header.height
}
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 8.0 * 2.0 + titleNodeLayout.size.height + 3.0 + descriptionNodeLayout.size.height), insets: insets)
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 8.0 * 2.0 + titleNodeLayout.size.height + 3.0 + descriptionNodeLayout.size.height + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), insets: insets)
return (nodeLayout, { animation in
if let strongSelf = self {
@ -683,7 +746,10 @@ public final class ListMessageFileItemNode: ListMessageNode {
}
}
transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0), size: descriptionNodeLayout.size))
transition.updateFrame(node: strongSelf.textNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0), size: textNodeLayout.size))
let _ = textNodeApply()
transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0 + (textNodeLayout.size.height > 0.0 ? textNodeLayout.size.height + 3.0 : 0.0)), size: descriptionNodeLayout.size))
let _ = descriptionNodeApply()
let _ = dateNodeApply()

View File

@ -19,6 +19,8 @@ public final class ListMessageItemInteraction {
let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void
let getHiddenMedia: () -> [MessageId: [Media]]
public var searchTextHighightState: String?
public init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, getHiddenMedia: @escaping () -> [MessageId: [Media]]) {
self.openMessage = openMessage
self.openMessageContextMenu = openMessageContextMenu

View File

@ -54,6 +54,8 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
private var appliedItem: ListMessageItem?
private var cachedChatListSearchResult: CachedChatListSearchResult?
public required init() {
self.contextSourceNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
@ -212,6 +214,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
let currentIconImageRepresentation = self.currentIconImageRepresentation
let currentItem = self.appliedItem
let currentChatListSearchResult = self.cachedChatListSearchResult
let selectionNodeLayout = ItemListSelectableControlNode.asyncLayout(self.selectionNode)
@ -439,6 +442,36 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
}
}
var chatListSearchResult: CachedChatListSearchResult?
if let searchQuery = item.interaction.searchTextHighightState {
if let cached = currentChatListSearchResult, cached.matches(text: item.message.text, searchQuery: searchQuery) {
chatListSearchResult = cached
} else {
let (ranges, text) = findSubstringRanges(in: item.message.text, query: searchQuery)
chatListSearchResult = CachedChatListSearchResult(text: text, searchQuery: searchQuery, resultRanges: ranges)
}
} else {
chatListSearchResult = nil
}
if let chatListSearchResult = chatListSearchResult, !chatListSearchResult.resultRanges.isEmpty, let firstRange = chatListSearchResult.resultRanges.first {
var text = NSMutableAttributedString(string: item.message.text, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
for range in chatListSearchResult.resultRanges {
let stringRange = NSRange(range, in: chatListSearchResult.text)
if stringRange.location >= 0 && stringRange.location + stringRange.length <= text.length {
text.addAttribute(.foregroundColor, value: item.presentationData.theme.theme.chatList.messageHighlightedTextColor, range: stringRange)
}
}
let firstRangeOrigin = item.message.text.distance(from: item.message.text.startIndex, to: firstRange.lowerBound)
if firstRangeOrigin > 20 {
text = text.attributedSubstring(from: NSMakeRange(firstRangeOrigin - 10, text.length - firstRangeOrigin + 10)).mutableCopy() as! NSMutableAttributedString
text.insert(NSAttributedString(string: "\u{2026}", attributes: [NSAttributedString.Key.font: descriptionFont, NSAttributedString.Key.foregroundColor: item.presentationData.theme.theme.list.itemSecondaryTextColor]), at: 0)
}
descriptionText = text
}
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.message.timestamp, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat)
let dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)