diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 6d79803d62..6af430ca12 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -94,11 +94,13 @@ public enum ChatListItemContent { public var commandPrefix: String? public var searchQuery: String? public var messageCount: Int? + public var hideSeparator: Bool - public init(commandPrefix: String?, searchQuery: String?, messageCount: Int?) { + public init(commandPrefix: String?, searchQuery: String?, messageCount: Int?, hideSeparator: Bool) { self.commandPrefix = commandPrefix self.searchQuery = searchQuery self.messageCount = messageCount + self.hideSeparator = hideSeparator } } @@ -1166,6 +1168,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { private var currentItemHeight: CGFloat? let forwardedIconNode: ASImageNode let textNode: TextNodeWithEntities + var trailingTextBadgeNode: TextNode? + var trailingTextBadgeBackground: UIImageView? var dustNode: InvisibleInkDustNode? let inputActivitiesNode: ChatListInputActivitiesNode let dateNode: TextNode @@ -1437,6 +1441,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.textNode = TextNodeWithEntities() self.textNode.textNode.isUserInteractionEnabled = false self.textNode.textNode.displaysAsynchronously = true + self.textNode.textNode.layer.anchorPoint = CGPoint() self.inputActivitiesNode = ChatListInputActivitiesNode() self.inputActivitiesNode.isUserInteractionEnabled = false @@ -1823,6 +1828,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { func asyncLayout() -> (_ item: ChatListItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool, _ firstWithHeader: Bool, _ nextIsPinned: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) { let dateLayout = TextNode.asyncLayout(self.dateNode) let textLayout = TextNodeWithEntities.asyncLayout(self.textNode) + let makeTrailingTextBadgeLayout = TextNode.asyncLayout(self.trailingTextBadgeNode) let titleLayout = TextNode.asyncLayout(self.titleNode) let authorLayout = self.authorNode.asyncLayout() let makeMeasureLayout = TextNode.asyncLayout(self.measureNode) @@ -2073,7 +2079,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData, customMessageListData.commandPrefix != nil { avatarDiameter = 40.0 - avatarLeftInset = 18.0 + avatarDiameter + avatarLeftInset = 17.0 + avatarDiameter } else { if item.interaction.isInlineMode { avatarLeftInset = 12.0 @@ -2666,7 +2672,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { topIndex = peerData.messages.first?.index } if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData { - if let messageCount = customMessageListData.messageCount { + if let messageCount = customMessageListData.messageCount, customMessageListData.commandPrefix == nil { dateText = "\(messageCount)" } else { dateText = " " @@ -2990,9 +2996,23 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let (authorLayout, authorApply) = authorLayout(item.context, rawContentWidth - badgeSize, item.presentationData.theme, effectiveAuthorTitle, forumThreads) + var textBottomRightCutout: CGFloat = 0.0 + + let trailingTextBadgeInsets = UIEdgeInsets(top: 2.0 - UIScreenPixel, left: 5.0, bottom: 2.0 - UIScreenPixel, right: 5.0) + var trailingTextBadgeLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData, customMessageListData.commandPrefix != nil, let messageCount = customMessageListData.messageCount, messageCount > 1 { + let trailingText: String + //TODO:localize + trailingText = "+\(messageCount - 1) MORE" + let trailingAttributedText = NSAttributedString(string: trailingText, font: Font.regular(12.0), textColor: theme.messageTextColor) + let (layout, apply) = makeTrailingTextBadgeLayout(TextNodeLayoutArguments(attributedString: trailingAttributedText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + trailingTextBadgeLayoutAndApply = (layout, apply) + textBottomRightCutout += layout.size.width + 4.0 + trailingTextBadgeInsets.left + trailingTextBadgeInsets.right + } + var textCutout: TextNodeCutout? - if !textLeftCutout.isZero { - textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: nil) + if !textLeftCutout.isZero || !textBottomRightCutout.isZero { + textCutout = TextNodeCutout(topLeft: textLeftCutout.isZero ? nil : CGSize(width: textLeftCutout, height: 10.0), topRight: nil, bottomRight: textBottomRightCutout.isZero ? nil : CGSize(width: textBottomRightCutout, height: 10.0)) } var textMaxWidth = rawContentWidth - badgeSize @@ -3552,6 +3572,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let _ = measureApply() let _ = dateApply() + var currentTextSnapshotView: UIView? + if transition.isAnimated, let currentItem, currentItem.editing != item.editing, strongSelf.textNode.textNode.cachedLayout?.linesRects() != textLayout.linesRects() { + if let textSnapshotView = strongSelf.textNode.textNode.view.snapshotContentTree() { + textSnapshotView.layer.anchorPoint = CGPoint() + currentTextSnapshotView = textSnapshotView + strongSelf.textNode.textNode.view.superview?.insertSubview(textSnapshotView, aboveSubview: strongSelf.textNode.textNode.view) + textSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak textSnapshotView] _ in + textSnapshotView?.removeFromSuperview() + }) + strongSelf.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + } + } + let _ = textApply(TextNodeWithEntities.Arguments( context: item.context, cache: item.interaction.animationCache, @@ -3572,7 +3605,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var dateFrame = CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateLayout.size.width, y: contentRect.origin.y + 2.0), size: dateLayout.size) - if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData, customMessageListData.messageCount != nil { + if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData, customMessageListData.messageCount != nil, customMessageListData.commandPrefix == nil { dateFrame.origin.x -= 10.0 let dateDisclosureIconView: UIImageView @@ -3818,6 +3851,60 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.authorNode.assignParentNode(parentNode: nil) } + if let currentTextSnapshotView { + transition.updatePosition(layer: currentTextSnapshotView.layer, position: textNodeFrame.origin) + } + + if let trailingTextBadgeLayoutAndApply { + let badgeSize = CGSize(width: trailingTextBadgeLayoutAndApply.0.size.width + trailingTextBadgeInsets.left + trailingTextBadgeInsets.right, height: trailingTextBadgeLayoutAndApply.0.size.height + trailingTextBadgeInsets.top + trailingTextBadgeInsets.bottom - UIScreenPixel) + + var badgeFrame: CGRect + if textLayout.numberOfLines > 1 { + badgeFrame = CGRect(origin: CGPoint(x: textLayout.trailingLineWidth, y: textNodeFrame.height - 3.0 - badgeSize.height), size: badgeSize) + } else { + let firstLineFrame = textLayout.linesRects().first ?? CGRect(origin: CGPoint(), size: textNodeFrame.size) + badgeFrame = CGRect(origin: CGPoint(x: 0.0, y: firstLineFrame.height + 5.0), size: badgeSize) + } + + if badgeFrame.origin.x + badgeFrame.width >= textNodeFrame.width - 2.0 - 10.0 { + badgeFrame.origin.x = textNodeFrame.width - 2.0 - badgeFrame.width + } + + let trailingTextBadgeBackground: UIImageView + if let current = strongSelf.trailingTextBadgeBackground { + trailingTextBadgeBackground = current + } else { + trailingTextBadgeBackground = UIImageView(image: tagBackgroundImage) + strongSelf.trailingTextBadgeBackground = trailingTextBadgeBackground + strongSelf.textNode.textNode.view.addSubview(trailingTextBadgeBackground) + } + trailingTextBadgeBackground.tintColor = theme.pinnedItemBackgroundColor.mixedWith(theme.unreadBadgeInactiveBackgroundColor, alpha: 0.1) + + trailingTextBadgeBackground.frame = badgeFrame + + let trailingTextBadgeFrame = CGRect(origin: CGPoint(x: badgeFrame.minX + trailingTextBadgeInsets.left, y: badgeFrame.minY + trailingTextBadgeInsets.top), size: trailingTextBadgeLayoutAndApply.0.size) + let trailingTextBadgeNode = trailingTextBadgeLayoutAndApply.1() + if strongSelf.trailingTextBadgeNode !== trailingTextBadgeNode { + strongSelf.trailingTextBadgeNode?.removeFromSupernode() + strongSelf.trailingTextBadgeNode = trailingTextBadgeNode + + strongSelf.textNode.textNode.addSubnode(trailingTextBadgeNode) + + trailingTextBadgeNode.layer.anchorPoint = CGPoint() + } + + trailingTextBadgeNode.frame = trailingTextBadgeFrame + } else { + if let trailingTextBadgeNode = strongSelf.trailingTextBadgeNode { + strongSelf.trailingTextBadgeNode = nil + trailingTextBadgeNode.removeFromSupernode() + } + if let trailingTextBadgeBackground = strongSelf.trailingTextBadgeBackground { + strongSelf.trailingTextBadgeBackground = nil + trailingTextBadgeBackground.removeFromSuperview() + } + } + if !itemTags.isEmpty { let itemTagListFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: contentRect.maxY - 12.0), size: CGSize(width: contentRect.width, height: 20.0)) @@ -4127,7 +4214,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } if case let .peer(peerData) = item.content, let customMessageListData = peerData.customMessageListData { - if customMessageListData.messageCount != nil { + if customMessageListData.hideSeparator { strongSelf.separatorNode.isHidden = true } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 7306140ff1..82c0cfe1fc 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -433,7 +433,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL }, requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, displayAsTopicList: peerEntry.displayAsTopicList, - tags: chatListItemTags(accountPeerId: context.account.peerId, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters) + tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters) )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -811,7 +811,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL }, requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, displayAsTopicList: peerEntry.displayAsTopicList, - tags: chatListItemTags(accountPeerId: context.account.peerId, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters) + tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters) )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -4206,7 +4206,11 @@ func hideChatListContacts(context: AccountContext) { let _ = ApplicationSpecificNotice.setDisplayChatListContacts(accountManager: context.sharedContext.accountManager).startStandalone() } -func chatListItemTags(accountPeerId: EnginePeer.Id, peer: EnginePeer?, isUnread: Bool, isMuted: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> [ChatListItemContent.Tag] { +func chatListItemTags(location: ChatListControllerLocation, accountPeerId: EnginePeer.Id, peer: EnginePeer?, isUnread: Bool, isMuted: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> [ChatListItemContent.Tag] { + if case .chatList = location { + } else { + return [] + } guard let chatListFilters, !chatListFilters.isEmpty else { return [] } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index 35ccabb4ea..75406cdcf6 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -1202,6 +1202,8 @@ public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bo } else { return false } + } else if case .customChatContents = state.chatLocation { + return true } else { return false } diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index f1262a1777..62da3c1a45 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -816,6 +816,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false) { self.currentMessage = message + var displayInfo = displayInfo + if Namespaces.Message.allNonRegular.contains(message.id.namespace) { + displayInfo = false + } + var canDelete: Bool var canShare = !message.containsSecretMedia diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 70d9a83496..958ac84482 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -285,6 +285,7 @@ private enum PreferencesKeyValues: Int32 { case didCacheSavedMessageTagsPrefix = 34 case displaySavedChatsAsTopics = 35 case shortcutMessages = 37 + case timezoneList = 38 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -474,6 +475,12 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.shortcutMessages.rawValue) return key } + + public static func timezoneList() -> ValueBoxKey { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.timezoneList.rawValue) + return key + } } private enum SharedDataKeyValues: Int32 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift index c2986ca224..46cfb507e1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift @@ -185,5 +185,13 @@ public extension TelegramEngine { public func sendMessageShortcut(peerId: EnginePeer.Id, id: Int32) { let _ = _internal_sendMessageShortcut(account: self.account, peerId: peerId, id: id).startStandalone() } + + public func cachedTimeZoneList() -> Signal { + return _internal_cachedTimeZoneList(account: self.account) + } + + public func keepCachedTimeZoneListUpdated() -> Signal { + return _internal_keepCachedTimeZoneListUpdated(account: self.account) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift index e8d6cbab5e..a6b052fb01 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift @@ -229,7 +229,7 @@ func _internal_shortcutMessageList(account: Account) -> Signal Bool { + if lhs === rhs { + return true + } + if lhs.id != rhs.id { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.utcOffset != rhs.utcOffset { + return false + } + return true + } + } + + public let items: [Item] + public let hashValue: Int32 + + public init(items: [Item], hashValue: Int32) { + self.items = items + self.hashValue = hashValue + } + + public static func ==(lhs: TimeZoneList, rhs: TimeZoneList) -> Bool { + if lhs === rhs { + return true + } + if lhs.items != rhs.items { + return false + } + if lhs.hashValue != rhs.hashValue { + return false + } + return true + } +} + +func _internal_cachedTimeZoneList(account: Account) -> Signal { + let viewKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.timezoneList()])) + return account.postbox.combinedView(keys: [viewKey]) + |> map { views -> TimeZoneList? in + guard let view = views.views[viewKey] as? PreferencesView else { + return nil + } + guard let value = view.values[PreferencesKeys.timezoneList()]?.get(TimeZoneList.self) else { + return nil + } + return value + } +} + +func _internal_keepCachedTimeZoneListUpdated(account: Account) -> Signal { + let updateSignal = _internal_cachedTimeZoneList(account: account) + |> take(1) + |> mapToSignal { list -> Signal in + return account.network.request(Api.functions.help.getTimezonesList(hash: list?.hashValue ?? 0)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result else { + return .complete() + } + + return account.postbox.transaction { transaction in + switch result { + case let .timezonesList(timezones, hash): + var items: [TimeZoneList.Item] = [] + for item in timezones { + switch item { + case let .timezone(id, name, utcOffset): + items.append(TimeZoneList.Item(id: id, title: name, utcOffset: utcOffset)) + } + } + transaction.setPreferencesEntry(key: PreferencesKeys.timezoneList(), value: PreferencesEntry(TimeZoneList(items: items, hashValue: hash))) + case .timezonesListNotModified: + break + } + } + |> ignoreValues + } + } + + return updateSignal +} diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index e41442d0ae..ae0f24162d 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -267,7 +267,9 @@ public final class ChatListHeaderComponent: Component { } func update(title: String, theme: PresentationTheme, availableSize: CGSize, transition: Transition) -> CGSize { - self.titleView.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor) + let titleText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor) + let titleTextUpdated = self.titleView.attributedText != titleText + self.titleView.attributedText = titleText let titleSize = self.titleView.updateLayout(CGSize(width: 100.0, height: 44.0)) self.accessibilityLabel = title @@ -287,7 +289,12 @@ public final class ChatListHeaderComponent: Component { transition.setPosition(view: self.arrowView, position: arrowFrame.center) transition.setBounds(view: self.arrowView, bounds: CGRect(origin: CGPoint(), size: arrowFrame.size)) - transition.setFrame(view: self.titleView, frame: CGRect(origin: CGPoint(x: iconOffset - 3.0 + arrowSize.width + iconSpacing, y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)) + let titleFrame = CGRect(origin: CGPoint(x: iconOffset - 3.0 + arrowSize.width + iconSpacing, y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize) + if titleTextUpdated { + self.titleView.frame = titleFrame + } else { + transition.setFrame(view: self.titleView, frame: titleFrame) + } return CGSize(width: iconOffset + arrowSize.width + iconSpacing + titleSize.width, height: availableSize.height) } @@ -479,7 +486,9 @@ public final class ChatListHeaderComponent: Component { transition.setPosition(view: self.titleScaleContainer, position: CGPoint(x: size.width * 0.5, y: size.height * 0.5)) transition.setBounds(view: self.titleScaleContainer, bounds: CGRect(origin: self.titleScaleContainer.bounds.origin, size: size)) - self.titleTextView.attributedText = NSAttributedString(string: content.title, font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) + let titleText = NSAttributedString(string: content.title, font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) + let titleTextUpdated = self.titleTextView.attributedText != titleText + self.titleTextView.attributedText = titleText let buttonSpacing: CGFloat = 8.0 @@ -616,7 +625,11 @@ public final class ChatListHeaderComponent: Component { let titleTextSize = self.titleTextView.updateLayout(CGSize(width: remainingWidth, height: size.height)) let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleTextSize.width) / 2.0) + sideContentWidth, y: floor((size.height - titleTextSize.height) / 2.0)), size: titleTextSize) - transition.setFrame(view: self.titleTextView, frame: titleFrame) + if titleTextUpdated { + self.titleTextView.frame = titleFrame + } else { + transition.setFrame(view: self.titleTextView, frame: titleFrame) + } if let titleComponent = content.titleComponent { var titleContentTransition = transition diff --git a/submodules/TelegramUI/Components/PlainButtonComponent/Sources/PlainButtonComponent.swift b/submodules/TelegramUI/Components/PlainButtonComponent/Sources/PlainButtonComponent.swift index cb3ee96e76..f91488ec89 100644 --- a/submodules/TelegramUI/Components/PlainButtonComponent/Sources/PlainButtonComponent.swift +++ b/submodules/TelegramUI/Components/PlainButtonComponent/Sources/PlainButtonComponent.swift @@ -227,7 +227,12 @@ public final class PlainButtonComponent: Component { } let contentFrame = CGRect(origin: CGPoint(x: component.contentInsets.left + floor((size.width - component.contentInsets.left - component.contentInsets.right - contentSize.width) * 0.5), y: component.contentInsets.top + floor((size.height - component.contentInsets.top - component.contentInsets.bottom - contentSize.height) * 0.5)), size: contentSize) - contentTransition.setPosition(view: contentView, position: CGPoint(x: contentFrame.minX + contentFrame.width * contentView.layer.anchorPoint.x, y: contentFrame.minY + contentFrame.height * contentView.layer.anchorPoint.y)) + let contentPosition = CGPoint(x: contentFrame.minX + contentFrame.width * contentView.layer.anchorPoint.x, y: contentFrame.minY + contentFrame.height * contentView.layer.anchorPoint.y) + if !component.animateContents && (abs(contentView.center.x - contentPosition.x) <= 2.0 && abs(contentView.center.y - contentPosition.y) <= 2.0){ + contentView.center = contentPosition + } else { + contentTransition.setPosition(view: contentView, position: contentPosition) + } if component.animateContents { contentTransition.setBounds(view: contentView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size)) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift index 123a1d0087..ad53e21b11 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift @@ -238,7 +238,8 @@ final class GreetingMessageListItemComponent: Component { customMessageListData: ChatListItemContent.CustomMessageListData( commandPrefix: nil, searchQuery: nil, - messageCount: component.count + messageCount: component.count, + hideSeparator: true ) )), editing: false, diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift index a145ec7be2..f0a9d10fca 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift @@ -109,7 +109,7 @@ final class QuickReplyEmptyStateComponent: Component { transition: .immediate, component: AnyComponent(LottieComponent( content: LottieComponent.AppBundleContent(name: "WriteEmoji"), - loop: false + loop: true )), environment: {}, containerSize: CGSize(width: 120.0, height: 120.0) diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 010f4c6fca..595483c17d 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -250,7 +250,8 @@ final class QuickReplySetupScreenComponent: Component { customMessageListData: ChatListItemContent.CustomMessageListData( commandPrefix: "/\(item.shortcut)", searchQuery: nil, - messageCount: nil + messageCount: item.totalCount, + hideSeparator: false ) )), editing: isEditing, @@ -744,13 +745,14 @@ final class QuickReplySetupScreenComponent: Component { tabsNodeIsSearch: false, accessoryPanelContainer: nil, accessoryPanelContainerHeight: 0.0, - activateSearch: { [weak self] searchContentNode in + activateSearch: { [weak self] _ in guard let self else { return } - self.isSearchDisplayControllerActive = true - self.state?.updated(transition: .spring(duration: 0.4)) + let _ = self + //self.isSearchDisplayControllerActive = true + //self.state?.updated(transition: .spring(duration: 0.4)) }, openStatusSetup: { _ in }, diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift index fb5ffcfabc..5f8839c110 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift @@ -82,11 +82,14 @@ final class BusinessHoursSetupScreenComponent: Component { } var timezoneId: String - var days: [Day] + private(set) var days: [Day] + private(set) var intersectingDays = Set() init(timezoneId: String, days: [Day]) { self.timezoneId = timezoneId self.days = days + + self.validate() } init(businessHours: TelegramBusinessHours) { @@ -107,6 +110,19 @@ final class BusinessHoursSetupScreenComponent: Component { }) } } + + self.validate() + } + + mutating func validate() { + self.intersectingDays.removeAll() + + + } + + mutating func update(days: [Day]) { + self.days = days + self.validate() } func asBusinessHours() throws -> TelegramBusinessHours { @@ -165,6 +181,10 @@ final class BusinessHoursSetupScreenComponent: Component { private var showHours: Bool = false private var daysState = DaysState(timezoneId: "", days: []) + private var timeZoneList: TimeZoneList? + private var timezonesDisposable: Disposable? + private var keepTimezonesUpdatedDisposable: Disposable? + override init(frame: CGRect) { self.scrollView = ScrollView() self.scrollView.showsVerticalScrollIndicator = true @@ -191,6 +211,8 @@ final class BusinessHoursSetupScreenComponent: Component { } deinit { + self.timezonesDisposable?.dispose() + self.keepTimezonesUpdatedDisposable?.dispose() } func scrollToTop() { @@ -210,6 +232,21 @@ final class BusinessHoursSetupScreenComponent: Component { } catch let error { let _ = error //TODO:localize + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + //TODO:localize + self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "Business hours are intersecting. Reset?", actions: [ + TextAlertAction(type: .genericAction, title: "Cancel", action: { + }), + TextAlertAction(type: .defaultAction, title: "Reset", action: { [weak self] in + guard let self else { + return + } + let _ = self + complete() + }) + ]), in: .window(.root)) + return false } } else { @@ -271,10 +308,20 @@ final class BusinessHoursSetupScreenComponent: Component { } else { self.showHours = false self.daysState.timezoneId = TimeZone.current.identifier - self.daysState.days = (0 ..< 7).map { _ in + self.daysState.update(days: (0 ..< 7).map { _ in return Day(ranges: []) - } + }) } + + self.timezonesDisposable = (component.context.engine.accountData.cachedTimeZoneList() + |> deliverOnMainQueue).start(next: { [weak self] timeZoneList in + guard let self else { + return + } + self.timeZoneList = timeZoneList + self.state?.updated(transition: .immediate) + }) + self.keepTimezonesUpdatedDisposable = component.context.engine.accountData.keepCachedTimeZoneListUpdated().startStrict() } let environment = environment[EnvironmentType.self].value @@ -508,11 +555,13 @@ final class BusinessHoursSetupScreenComponent: Component { return } if dayIndex < self.daysState.days.count { - if self.daysState.days[dayIndex].ranges == nil { - self.daysState.days[dayIndex].ranges = [] + var days = self.daysState.days + if days[dayIndex].ranges == nil { + days[dayIndex].ranges = [] } else { - self.daysState.days[dayIndex].ranges = nil + days[dayIndex].ranges = nil } + self.daysState.update(days: days) } self.state?.updated(transition: .immediate) })), @@ -529,7 +578,9 @@ final class BusinessHoursSetupScreenComponent: Component { return } if self.daysState.days[dayIndex] != day { - self.daysState.days[dayIndex] = day + var days = self.daysState.days + days[dayIndex] = day + self.daysState.update(days: days) self.state?.updated(transition: .immediate) } } @@ -569,6 +620,18 @@ final class BusinessHoursSetupScreenComponent: Component { daysContentHeight += daysSectionSize.height daysContentHeight += sectionSpacing + let timezoneValueText: String + if let timeZoneList = self.timeZoneList { + if let item = timeZoneList.items.first(where: { $0.id == self.daysState.timezoneId }) { + timezoneValueText = item.title + } else { + timezoneValueText = TimeZone(identifier: self.daysState.timezoneId)?.localizedName(for: .shortStandard, locale: Locale.current) ?? " " + } + } else { + //TODO:localize + timezoneValueText = "Loading..." + } + let timezoneSectionSize = self.timezoneSection.update( transition: transition, component: AnyComponent(ListSectionComponent( @@ -588,7 +651,7 @@ final class BusinessHoursSetupScreenComponent: Component { )), icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: TimeZone(identifier: self.daysState.timezoneId)?.localizedName(for: .shortStandard, locale: Locale.current) ?? self.daysState.timezoneId, + string: timezoneValueText, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemSecondaryTextColor )), diff --git a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift index 8656ab32ea..07cc080138 100644 --- a/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift +++ b/submodules/TelegramUI/Components/Settings/TimezoneSelectionScreen/Sources/TimezoneSelectionScreenNode.swift @@ -58,8 +58,7 @@ private func preparedLanguageListSearchContainerTransition(presentationData: Pre } private final class TimezoneListSearchContainerNode: SearchDisplayControllerContentNode { - private let timezoneData: TimezoneData - + private let timeZoneList: TimeZoneList private let dimNode: ASDisplayNode private let listNode: ListView @@ -78,8 +77,8 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont return true } - init(context: AccountContext, timezoneData: TimezoneData, action: @escaping (String) -> Void) { - self.timezoneData = timezoneData + init(context: AccountContext, timeZoneList: TimeZoneList, action: @escaping (String) -> Void) { + self.timeZoneList = timeZoneList let presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = presentationData @@ -102,16 +101,18 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont self.addSubnode(self.dimNode) self.addSubnode(self.listNode) + let querySplitCharacterSet: CharacterSet = CharacterSet(charactersIn: " /.+") + let foundItems = self.searchQuery.get() - |> mapToSignal { query -> Signal<[TimezoneData.Item]?, NoError> in + |> mapToSignal { query -> Signal<[TimeZoneList.Item]?, NoError> in if let query, !query.isEmpty { let query = query.lowercased() - return .single(timezoneData.items.filter { item in + return .single(timeZoneList.items.filter { item in if item.id.lowercased().hasPrefix(query) { return true } - if item.title.lowercased().split(separator: " ").contains(where: { $0.hasPrefix(query) }) { + if item.title.lowercased().components(separatedBy: querySplitCharacterSet).contains(where: { $0.hasPrefix(query) }) { return true } @@ -132,7 +133,7 @@ private final class TimezoneListSearchContainerNode: SearchDisplayControllerCont for item in items { entries.append(TimezoneListEntry( id: item.id, - offset: item.offset, + offset: Int(item.utcOffset), title: item.title )) } @@ -301,7 +302,7 @@ final class TimezoneSelectionScreenNode: ViewControllerTracingNode { private let requestDeactivateSearch: () -> Void private let present: (ViewController, Any?) -> Void private let push: (ViewController) -> Void - private let timezoneData: TimezoneData + private var timeZoneList: TimeZoneList? private var didSetReady = false let _ready = ValuePromise() @@ -331,28 +332,32 @@ final class TimezoneSelectionScreenNode: ViewControllerTracingNode { return presentationData.strings.VoiceOver_ScrollStatus(row, count).string } - let timezoneData = TimezoneData() - self.timezoneData = timezoneData - super.init() self.backgroundColor = presentationData.theme.list.plainBackgroundColor self.addSubnode(self.listNode) let previousEntriesHolder = Atomic<([TimezoneListEntry], PresentationTheme, PresentationStrings)?>(value: nil) - self.listDisposable = (self.presentationDataValue.get() - |> deliverOnMainQueue).start(next: { [weak self] presentationData in + self.listDisposable = (combineLatest(queue: .mainQueue(), + self.presentationDataValue.get(), + context.engine.accountData.cachedTimeZoneList() + ) + |> deliverOnMainQueue).start(next: { [weak self] presentationData, timeZoneList in guard let strongSelf = self else { return } - + + strongSelf.timeZoneList = timeZoneList + var entries: [TimezoneListEntry] = [] - for item in timezoneData.items { - entries.append(TimezoneListEntry( - id: item.id, - offset: item.offset, - title: item.title - )) + if let timeZoneList { + for item in timeZoneList.items { + entries.append(TimezoneListEntry( + id: item.id, + offset: Int(item.utcOffset), + title: item.title + )) + } } entries.sort() @@ -447,8 +452,11 @@ final class TimezoneSelectionScreenNode: ViewControllerTracingNode { guard let (containerLayout, navigationBarHeight) = self.containerLayout, self.searchDisplayController == nil else { return } + guard let timeZoneList = self.timeZoneList else { + return + } - self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: TimezoneListSearchContainerNode(context: self.context, timezoneData: self.timezoneData, action: self.action), inline: true, cancel: { [weak self] in + self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: TimezoneListSearchContainerNode(context: self.context, timeZoneList: timeZoneList, action: self.action), inline: true, cancel: { [weak self] in self?.requestDeactivateSearch() }) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift index ef430bc3d9..092847181d 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift @@ -104,6 +104,9 @@ extension ChatControllerImpl { if case let .peer(peerId) = self.chatLocation, messageLocation.peerId == peerId, !isPinnedMessages, !isScheduledMessages { forceInCurrentChat = true } + if case .customChatContents = self.chatLocation { + forceInCurrentChat = true + } if isPinnedMessages, let messageId = messageLocation.messageId { let _ = (combineLatest( @@ -205,6 +208,7 @@ extension ChatControllerImpl { if case let .id(_, params) = messageLocation { quote = params.quote.flatMap { quote in (string: quote.string, offset: quote.offset) } } + self.chatDisplayNode.historyNode.scrollToMessage(from: scrollFromIndex, to: message.index, animated: animated, quote: quote, scrollPosition: scrollPosition) if delayCompletion { diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 50e8551bb6..74e5ff6665 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -1883,7 +1883,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { overlayNavigationBar.updateLayout(size: barFrame.size, transition: transition) } - var listInsets = UIEdgeInsets(top: containerInsets.bottom + contentBottomInset, left: containerInsets.right, bottom: containerInsets.top, right: containerInsets.left) + var listInsets = UIEdgeInsets(top: containerInsets.bottom + contentBottomInset, left: containerInsets.right, bottom: containerInsets.top + 6.0, right: containerInsets.left) let listScrollIndicatorInsets = UIEdgeInsets(top: containerInsets.bottom + inputPanelsHeight, left: containerInsets.right, bottom: containerInsets.top, right: containerInsets.left) var childContentInsets: UIEdgeInsets = containerInsets diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 332d725dd7..060de3e3ec 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -546,12 +546,12 @@ func chatHistoryEntriesForView( let message = Message( stableId: UInt32.max - 1001 - UInt32(i), stableVersion: 0, - id: MessageId(peerId: context.account.peerId, namespace: Namespaces.Message.Local, id: 123 - Int32(i)), + id: MessageId(peerId: context.account.peerId, namespace: Namespaces.Message.Local, id: Int32.max - 100 - Int32(i)), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, - timestamp: Int32(i), + timestamp: -Int32(i), flags: [.Incoming], tags: [], globalTags: [], diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index bbd4200418..055439b3f5 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -1283,10 +1283,15 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } return true }) - |> mapToSignal { _ in + |> mapToSignal { location, _ -> Signal<((MessageHistoryView, ViewUpdateType), ChatHistoryLocationInput?), NoError> in return historyView + |> map { historyView in + return (historyView, location) + } } - |> map { view, update in + |> map { viewAndUpdate, location in + let (view, update) = viewAndUpdate + let version = currentViewVersion.modify({ value in if let value = value { return value + 1 @@ -1295,11 +1300,21 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } })! + var scrollPositionValue: ChatHistoryViewScrollPosition? + if let location { + switch location.content { + case let .Scroll(subject, _, _, scrollPosition, animated, highlight): + scrollPositionValue = .index(subject: subject, position: scrollPosition, directionHint: .Up, animated: animated, highlight: highlight, displayLink: false) + default: + break + } + } + return ( ChatHistoryViewUpdate.HistoryView( view: view, type: .Generic(type: update), - scrollPosition: nil, + scrollPosition: scrollPositionValue, flashIndicators: false, originalScrollPosition: nil, initialData: ChatHistoryCombinedInitialData( @@ -1309,10 +1324,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto cachedDataMessages: nil, readStateData: nil ), - id: 0 + id: location?.id ?? 0 ), version, - nil, + location, nil ) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift index fe7d320454..ac201532fe 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift @@ -76,12 +76,21 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS replyPanelNode.interfaceInteraction = interfaceInteraction replyPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings) return replyPanelNode - } else if let peerId = chatPresentationInterfaceState.chatLocation.peerId { - let panelNode = ReplyAccessoryPanelNode(context: context, chatPeerId: peerId, messageId: replyMessageSubject.messageId, quote: replyMessageSubject.quote, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, dateTimeFormat: chatPresentationInterfaceState.dateTimeFormat, animationCache: chatControllerInteraction?.presentationContext.animationCache, animationRenderer: chatControllerInteraction?.presentationContext.animationRenderer) - panelNode.interfaceInteraction = interfaceInteraction - return panelNode } else { - return nil + var chatPeerId: EnginePeer.Id? + if let peerId = chatPresentationInterfaceState.chatLocation.peerId { + chatPeerId = peerId + } else if case .customChatContents = chatPresentationInterfaceState.chatLocation { + chatPeerId = context.account.peerId + } + + if let chatPeerId { + let panelNode = ReplyAccessoryPanelNode(context: context, chatPeerId: chatPeerId, messageId: replyMessageSubject.messageId, quote: replyMessageSubject.quote, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, dateTimeFormat: chatPresentationInterfaceState.dateTimeFormat, animationCache: chatControllerInteraction?.presentationContext.animationCache, animationRenderer: chatControllerInteraction?.presentationContext.animationRenderer) + panelNode.interfaceInteraction = interfaceInteraction + return panelNode + } else { + return nil + } } } else { return nil diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 4f03d1f2c4..510ae5cca2 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -271,6 +271,10 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag } func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: PeerId) -> Bool { + if case .customChatContents = chatPresentationInterfaceState.chatLocation { + return true + } + guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else { return false } @@ -338,7 +342,7 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS case .replyThread: canReply = true case .customChatContents: - canReply = false + canReply = true } return canReply } diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index 6ecb5d7693..4b6334f13e 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -220,7 +220,8 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { customMessageListData: ChatListItemContent.CustomMessageListData( commandPrefix: "/\(shortcut.shortcut)", searchQuery: command.searchQuery.flatMap { "/\($0)"}, - messageCount: nil + messageCount: shortcut.totalCount, + hideSeparator: false ) )), editing: false,