From d2e3c9d54a99e97f06fc75b16e44dfb64ddff505 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 24 Jan 2020 13:01:49 +0400 Subject: [PATCH] Experimental chat list filtering --- .../Sources/ChatListController.swift | 18 +- .../Sources/Node/ChatListItem.swift | 2 +- .../Sources/Node/ChatListNode.swift | 58 ++- .../Sources/Node/ChatListNodeLocation.swift | 80 ++- .../Sources/Node/ChatListViewTransition.swift | 1 + .../TabBarChatListFilterController.swift | 477 ++++++++++++++++++ .../Postbox/Sources/ChatListTable.swift | 103 +++- submodules/Postbox/Sources/ChatListView.swift | 74 ++- .../PeerNotificationSettingsTable.swift | 8 +- .../PeerNotificationSettingsView.swift | 2 +- submodules/Postbox/Sources/PeerView.swift | 4 +- submodules/Postbox/Sources/Postbox.swift | 66 ++- .../Postbox/Sources/PostboxTransaction.swift | 4 +- .../Postbox/Sources/SqliteValueBox.swift | 32 ++ submodules/Postbox/Sources/ValueBox.swift | 7 + .../Sources/AccountViewTracker.swift | 8 +- .../Sources/ChatMessageBubbleImages.swift | 21 +- .../TelegramUI/ChatMessageBackground.swift | 2 +- 18 files changed, 880 insertions(+), 87 deletions(-) create mode 100644 submodules/ChatListUI/Sources/TabBarChatListFilterController.swift diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index f2049711c5..44110de78e 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -99,7 +99,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } -public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate { +public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate, TabBarContainedController { private var validLayout: ContainerViewLayout? public let context: AccountContext @@ -1790,4 +1790,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, return nil } } + + public func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) { + if self.isNodeLoaded { + let controller = TabBarChatListFilterController(context: self.context, sourceNodes: sourceNodes, currentFilter: self.chatListDisplayNode.chatListNode.chatListFilter, updateFilter: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.chatListDisplayNode.chatListNode.chatListFilter = value + }) + self.context.sharedContext.mainWindow?.present(controller, on: .root) + } + } + + public func updateTabBarPreviewingControllerPresentation(_ update: TabBarContainedControllerPresentationUpdate) { + + } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 8803effaeb..0996cf6aff 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -699,7 +699,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { isPeerGroup = false isAd = isAdValue displayAsMessage = displayAsMessageValue - hasFailedMessages = hasFailedMessagesValue + hasFailedMessages = messageValue?.flags.contains(.Failed) ?? false // hasFailedMessagesValue case let .groupReference(_, peers, messageValue, unreadState, hiddenByDefault): if let _ = messageValue, !peers.isEmpty { contentPeer = .chat(peers[0].peer) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 0276a2bd8b..a585454f05 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -366,6 +366,14 @@ public final class ChatListNode: ListView { } private var currentLocation: ChatListNodeLocation? + var chatListFilter: ChatListNodeFilter = .all { + didSet { + if self.chatListFilter != oldValue { + self.chatListFilterValue.set(self.chatListFilter) + } + } + } + private let chatListFilterValue = ValuePromise(.all) private let chatListLocation = ValuePromise() private let chatListDisposable = MetaDisposable() private var activityStatusesDisposable: Disposable? @@ -522,10 +530,21 @@ public final class ChatListNode: ListView { let viewProcessingQueue = self.viewProcessingQueue - let chatListViewUpdate = self.chatListLocation.get() - |> distinctUntilChanged - |> mapToSignal { location in - return chatListViewForLocation(groupId: groupId, location: location, account: context.account) + let chatListViewUpdate = combineLatest(self.chatListLocation.get(), self.chatListFilterValue.get()) + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs.0 != rhs.0 { + return false + } + if lhs.1 != rhs.1 { + return false + } + return true + }) + |> mapToSignal { location, filter -> Signal<(ChatListNodeViewUpdate, ChatListNodeFilter), NoError> in + return chatListViewForLocation(groupId: groupId, filter: filter, location: location, account: context.account) + |> map { update in + return (update, filter) + } } let previousState = Atomic(value: self.currentState) @@ -575,7 +594,8 @@ public final class ChatListNode: ListView { let currentPeerId: PeerId = context.account.peerId let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, chatListViewUpdate, self.statePromise.get()) - |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, update, state) -> Signal in + |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, updateAndFilter, state) -> Signal in + let (update, filter) = updateAndFilter let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault) @@ -633,7 +653,7 @@ public final class ChatListNode: ListView { } } - let processedView = ChatListNodeView(originalView: update.view, filteredEntries: entries, isLoading: isLoading) + let processedView = ChatListNodeView(originalView: update.view, filteredEntries: entries, isLoading: isLoading, filter: filter) let previousView = previousView.swap(processedView) let previousState = previousState.swap(state) @@ -746,6 +766,10 @@ public final class ChatListNode: ListView { searchMode = true } + if filter != previousView?.filter { + disableAnimations = true + } + return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode) |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, peerGroupId: groupId, mode: mode, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue) @@ -1439,17 +1463,23 @@ public final class ChatListNode: ListView { guard index < 10 else { return } - let _ = (chatListViewForLocation(groupId: self.groupId, location: .initial(count: 10), account: self.context.account) + let _ = (self.chatListFilterValue.get() |> take(1) - |> deliverOnMainQueue).start(next: { update in - let entries = update.view.entries - if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _) = entries[10 - index - 1] { - let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true) - self.setChatListLocation(location) - self.peerSelected?(renderedPeer.peerId, false, false) + |> deliverOnMainQueue).start(next: { [weak self] filter in + guard let self = self else { + return } + let _ = (chatListViewForLocation(groupId: self.groupId, filter: filter, location: .initial(count: 10), account: self.context.account) + |> take(1) + |> deliverOnMainQueue).start(next: { update in + let entries = update.view.entries + if entries.count > index, case let .MessageEntry(index, _, _, _, _, renderedPeer, _, _, _) = entries[10 - index - 1] { + let location: ChatListNodeLocation = .scroll(index: index, sourceIndex: .absoluteLowerBound, scrollPosition: .center(.top), animated: true) + self.setChatListLocation(location) + self.peerSelected?(renderedPeer.peerId, false, false) + } + }) }) - break } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift index 45a78f684b..93e631b8f6 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeLocation.swift @@ -31,18 +31,90 @@ struct ChatListNodeViewUpdate { let scrollPosition: ChatListNodeViewScrollPosition? } -func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal { +struct ChatListNodeFilter: OptionSet { + var rawValue: Int32 + + init(rawValue: Int32) { + self.rawValue = rawValue + } + + static let muted = ChatListNodeFilter(rawValue: 1 << 1) + static let privateChats = ChatListNodeFilter(rawValue: 1 << 2) + static let groups = ChatListNodeFilter(rawValue: 1 << 3) + static let bots = ChatListNodeFilter(rawValue: 1 << 4) + static let channels = ChatListNodeFilter(rawValue: 1 << 5) + + static let all: ChatListNodeFilter = [ + .muted, + .privateChats, + .groups, + .bots, + .channels + ] +} + +func chatListViewForLocation(groupId: PeerGroupId, filter: ChatListNodeFilter, location: ChatListNodeLocation, account: Account) -> Signal { + let filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)? + if filter == .all { + filterPredicate = nil + } else { + filterPredicate = { peer, notificationSettings in + if !filter.contains(.muted) { + if let notificationSettings = notificationSettings as? TelegramPeerNotificationSettings { + if case .muted = notificationSettings.muteState { + return false + } + } else { + return false + } + } + if !filter.contains(.privateChats) { + if let user = peer as? TelegramUser { + if user.botInfo == nil { + return false + } + } else if let _ = peer as? TelegramSecretChat { + return false + } + } + if !filter.contains(.bots) { + if let user = peer as? TelegramUser { + if user.botInfo != nil { + return false + } + } + } + if !filter.contains(.groups) { + if let _ = peer as? TelegramGroup { + return false + } else if let channel = peer as? TelegramChannel { + if case .group = channel.info { + return false + } + } + } + if !filter.contains(.channels) { + if let channel = peer as? TelegramChannel { + if case .broadcast = channel.info { + return false + } + } + } + return true + } + } + switch location { case let .initial(count): let signal: Signal<(ChatListView, ViewUpdateType), NoError> - signal = account.viewTracker.tailChatListView(groupId: groupId, count: count) + signal = account.viewTracker.tailChatListView(groupId: groupId, filterPredicate: filterPredicate, count: count) return signal |> map { view, updateType -> ChatListNodeViewUpdate in return ChatListNodeViewUpdate(view: view, type: updateType, scrollPosition: nil) } case let .navigation(index): var first = true - return account.viewTracker.aroundChatListView(groupId: groupId, index: index, count: 80) + return account.viewTracker.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: 80) |> map { view, updateType -> ChatListNodeViewUpdate in let genericType: ViewUpdateType if first { @@ -57,7 +129,7 @@ func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocatio let directionHint: ListViewScrollToItemDirectionHint = sourceIndex > index ? .Down : .Up let chatScrollPosition: ChatListNodeViewScrollPosition = .index(index: index, position: scrollPosition, directionHint: directionHint, animated: animated) var first = true - return account.viewTracker.aroundChatListView(groupId: groupId, index: index, count: 80) + return account.viewTracker.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: 80) |> map { view, updateType -> ChatListNodeViewUpdate in let genericType: ViewUpdateType let scrollPosition: ChatListNodeViewScrollPosition? = first ? chatScrollPosition : nil diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index 6c10849cca..17350a0f77 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -11,6 +11,7 @@ struct ChatListNodeView { let originalView: ChatListView let filteredEntries: [ChatListNodeEntry] let isLoading: Bool + let filter: ChatListNodeFilter } enum ChatListNodeViewTransitionReason { diff --git a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift new file mode 100644 index 0000000000..8048568cbd --- /dev/null +++ b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift @@ -0,0 +1,477 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import AsyncDisplayKit +import TelegramPresentationData +import AccountContext + +final class TabBarChatListFilterController: ViewController { + private var controllerNode: TabBarChatListFilterControllerNode { + return self.displayNode as! TabBarChatListFilterControllerNode + } + + private let _ready = Promise(true) + override public var ready: Promise { + return self._ready + } + + private let context: AccountContext + private let sourceNodes: [ASDisplayNode] + private let currentFilter: ChatListNodeFilter + private let updateFilter: (ChatListNodeFilter) -> Void + + private var presentationData: PresentationData + private var didPlayPresentationAnimation = false + + private let hapticFeedback = HapticFeedback() + + public init(context: AccountContext, sourceNodes: [ASDisplayNode], currentFilter: ChatListNodeFilter, updateFilter: @escaping (ChatListNodeFilter) -> Void) { + self.context = context + self.sourceNodes = sourceNodes + self.currentFilter = currentFilter + self.updateFilter = updateFilter + + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + + super.init(navigationBarPresentationData: nil) + + self.statusBar.statusBarStyle = .Ignore + self.statusBar.ignoreInCall = true + + self.lockOrientation = true + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + override public func loadDisplayNode() { + self.displayNode = TabBarChatListFilterControllerNode(context: self.context, presentationData: self.presentationData, cancel: { [weak self] in + self?.dismiss() + }, sourceNodes: self.sourceNodes, currentFilter: self.currentFilter, updateFilter: self.updateFilter) + self.displayNodeDidLoad() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.didPlayPresentationAnimation { + self.didPlayPresentationAnimation = true + + self.hapticFeedback.impact() + self.controllerNode.animateIn() + } + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, transition: transition) + } + + override public func dismiss(completion: (() -> Void)? = nil) { + self.dismiss(sourceNodes: []) + } + + public func dismiss(sourceNodes: [ASDisplayNode]) { + self.controllerNode.animateOut(sourceNodes: sourceNodes, completion: { [weak self] in + self?.didPlayPresentationAnimation = false + self?.presentingViewController?.dismiss(animated: false, completion: nil) + }) + } +} + +private let animationDurationFactor: Double = 1.0 + +private protocol AbstractTabBarChatListFilterItemNode { + func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) +} + +private final class FilterItemNode: ASDisplayNode, AbstractTabBarChatListFilterItemNode { + private let context: AccountContext + private let title: String + private let isCurrent: Bool + private let presentationData: PresentationData + private let action: () -> Bool + + private let separatorNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private let buttonNode: HighlightTrackingButtonNode + private let titleNode: ImmediateTextNode + private let checkNode: ASImageNode + + private let badgeBackgroundNode: ASImageNode + private let badgeTitleNode: ImmediateTextNode + + init(context: AccountContext, title: String, isCurrent: Bool, displaySeparator: Bool, presentationData: PresentationData, action: @escaping () -> Bool) { + self.context = context + self.title = title + self.isCurrent = isCurrent + self.presentationData = presentationData + self.action = action + + self.separatorNode = ASDisplayNode() + self.separatorNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemSeparatorColor + self.separatorNode.isHidden = !displaySeparator + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.backgroundColor = presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor + self.highlightedBackgroundNode.alpha = 0.0 + + self.buttonNode = HighlightTrackingButtonNode() + + self.titleNode = ImmediateTextNode() + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: presentationData.theme.actionSheet.primaryTextColor) + + self.checkNode = ASImageNode() + self.checkNode.image = generateItemListCheckIcon(color: presentationData.theme.actionSheet.primaryTextColor) + self.checkNode.isHidden = !isCurrent + + self.badgeBackgroundNode = ASImageNode() + self.badgeBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: presentationData.theme.list.itemCheckColors.fillColor) + self.badgeTitleNode = ImmediateTextNode() + self.badgeBackgroundNode.isHidden = true + self.badgeTitleNode.isHidden = true + + super.init() + + self.addSubnode(self.separatorNode) + self.addSubnode(self.highlightedBackgroundNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.checkNode) + self.addSubnode(self.badgeBackgroundNode) + self.addSubnode(self.badgeTitleNode) + self.addSubnode(self.buttonNode) + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.highlightedBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.highlightedBackgroundNode.alpha = 1.0 + } else { + strongSelf.highlightedBackgroundNode.alpha = 0.0 + strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + } + } + } + } + + func updateLayout(maxWidth: CGFloat) -> (CGFloat, CGFloat, (CGFloat) -> Void) { + let leftInset: CGFloat = 16.0 + + let badgeTitleSize = self.badgeTitleNode.updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude)) + let badgeMinSize = self.badgeBackgroundNode.image?.size.width ?? 20.0 + let badgeSize = CGSize(width: max(badgeMinSize, badgeTitleSize.width + 12.0), height: badgeMinSize) + + let rightInset: CGFloat = max(60.0, badgeSize.width + 40.0) + + let titleSize = self.titleNode.updateLayout(CGSize(width: maxWidth - leftInset - rightInset, height: .greatestFiniteMagnitude)) + + let height: CGFloat = 61.0 + + return (titleSize.width + leftInset + rightInset, height, { width in + self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize) + + if let image = self.checkNode.image { + self.checkNode.frame = CGRect(origin: CGPoint(x: width - rightInset + floor((rightInset - image.size.width) / 2.0), y: floor((height - image.size.height) / 2.0)), size: image.size) + } + + let badgeBackgroundFrame = CGRect(origin: CGPoint(x: width - rightInset + floor((rightInset - badgeSize.width) / 2.0), y: floor((height - badgeSize.height) / 2.0)), size: badgeSize) + self.badgeBackgroundNode.frame = badgeBackgroundFrame + self.badgeTitleNode.frame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX + floor((badgeBackgroundFrame.width - badgeTitleSize.width) / 2.0), y: badgeBackgroundFrame.minY + floor((badgeBackgroundFrame.height - badgeTitleSize.height) / 2.0)), size: badgeTitleSize) + + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: height - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)) + self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) + self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: height)) + }) + } + + @objc private func buttonPressed() { + let isCurrent = self.action() + self.checkNode.isHidden = !isCurrent + } +} + +private final class TabBarChatListFilterControllerNode: ViewControllerTracingNode { + private let presentationData: PresentationData + private let cancel: () -> Void + + private let effectView: UIVisualEffectView + private var propertyAnimator: AnyObject? + private var displayLinkAnimator: DisplayLinkAnimator? + private let dimNode: ASDisplayNode + + private let contentContainerNode: ASDisplayNode + private let contentNodes: [ASDisplayNode & AbstractTabBarChatListFilterItemNode] + + private var sourceNodes: [ASDisplayNode] + private var snapshotViews: [UIView] = [] + + private var validLayout: ContainerViewLayout? + + init(context: AccountContext, presentationData: PresentationData, cancel: @escaping () -> Void, sourceNodes: [ASDisplayNode], currentFilter: ChatListNodeFilter, updateFilter: @escaping (ChatListNodeFilter) -> Void) { + self.presentationData = presentationData + self.cancel = cancel + self.sourceNodes = sourceNodes + + self.effectView = UIVisualEffectView() + if #available(iOS 9.0, *) { + } else { + if presentationData.theme.rootController.keyboardColor == .dark { + self.effectView.effect = UIBlurEffect(style: .dark) + } else { + self.effectView.effect = UIBlurEffect(style: .light) + } + self.effectView.alpha = 0.0 + } + + self.dimNode = ASDisplayNode() + self.dimNode.alpha = 1.0 + if presentationData.theme.rootController.keyboardColor == .light { + self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.04) + } else { + self.dimNode.backgroundColor = presentationData.theme.chatList.backgroundColor.withAlphaComponent(0.2) + } + + self.contentContainerNode = ASDisplayNode() + self.contentContainerNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor + self.contentContainerNode.cornerRadius = 20.0 + self.contentContainerNode.clipsToBounds = true + + var contentNodes: [ASDisplayNode & AbstractTabBarChatListFilterItemNode] = [] + + let labels: [(String, ChatListNodeFilter)] = [ + ("Private Chats", .privateChats), + ("Groups", .groups), + ("Bots", .bots), + ("Channels", .channels), + ("Muted", .muted) + ] + + var updatedFilter = currentFilter + let toggleFilter: (ChatListNodeFilter) -> Void = { filter in + if updatedFilter.contains(filter) { + updatedFilter.remove(filter) + } else { + updatedFilter.insert(filter) + } + updateFilter(updatedFilter) + } + + for i in 0 ..< labels.count { + let filter = labels[i].1 + contentNodes.append(FilterItemNode(context: context, title: labels[i].0, isCurrent: updatedFilter.contains(filter), displaySeparator: i != labels.count - 1, presentationData: presentationData, action: { + toggleFilter(filter) + return updatedFilter.contains(filter) + })) + } + self.contentNodes = contentNodes + + super.init() + + self.view.addSubview(self.effectView) + self.addSubnode(self.dimNode) + self.addSubnode(self.contentContainerNode) + self.contentNodes.forEach(self.contentContainerNode.addSubnode) + + self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + } + + deinit { + if let propertyAnimator = self.propertyAnimator { + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) + } + } + } + + func animateIn() { + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + + if #available(iOS 10.0, *) { + if let propertyAnimator = self.propertyAnimator { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) + } + self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2 * animationDurationFactor, curve: .easeInOut, animations: { [weak self] in + self?.effectView.effect = makeCustomZoomBlurEffect() + }) + } + + if let _ = self.propertyAnimator { + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 1.0, update: { [weak self] value in + (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value + }, completion: { + }) + } + } else { + UIView.animate(withDuration: 0.2 * animationDurationFactor, animations: { + self.effectView.effect = makeCustomZoomBlurEffect() + }, completion: { _ in + }) + } + + self.contentContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + + if let _ = self.validLayout, let sourceNode = self.sourceNodes.first { + let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view) + self.contentContainerNode.layer.animateFrame(from: sourceFrame, to: self.contentContainerNode.frame, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } + + for sourceNode in self.sourceNodes { + if let imageNode = sourceNode as? ASImageNode { + let snapshot = UIImageView() + snapshot.image = imageNode.image + snapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view) + snapshot.isUserInteractionEnabled = false + self.view.addSubview(snapshot) + self.snapshotViews.append(snapshot) + } else if let snapshot = sourceNode.view.snapshotContentTree() { + snapshot.frame = sourceNode.view.convert(sourceNode.bounds, to: self.view) + snapshot.isUserInteractionEnabled = false + self.view.addSubview(snapshot) + self.snapshotViews.append(snapshot) + } + sourceNode.alpha = 0.0 + } + } + + func animateOut(sourceNodes: [ASDisplayNode], completion: @escaping () -> Void) { + self.isUserInteractionEnabled = false + + var completedEffect = false + var completedSourceNodes = false + + let intermediateCompletion: () -> Void = { + if completedEffect && completedSourceNodes { + completion() + } + } + + if #available(iOS 10.0, *) { + if let propertyAnimator = self.propertyAnimator { + let propertyAnimator = propertyAnimator as? UIViewPropertyAnimator + propertyAnimator?.stopAnimation(true) + } + self.propertyAnimator = UIViewPropertyAnimator(duration: 0.2, curve: .easeInOut, animations: { [weak self] in + self?.effectView.effect = nil + }) + } + + if let _ = self.propertyAnimator { + if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { + self.displayLinkAnimator = DisplayLinkAnimator(duration: 0.2 * animationDurationFactor, from: 0.0, to: 0.999, update: { [weak self] value in + (self?.propertyAnimator as? UIViewPropertyAnimator)?.fractionComplete = value + }, completion: { [weak self] in + if let strongSelf = self { + for sourceNode in strongSelf.sourceNodes { + sourceNode.alpha = 1.0 + } + } + + completedEffect = true + intermediateCompletion() + }) + } + self.effectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.05 * animationDurationFactor, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false) + } else { + UIView.animate(withDuration: 0.21 * animationDurationFactor, animations: { + if #available(iOS 9.0, *) { + self.effectView.effect = nil + } else { + self.effectView.alpha = 0.0 + } + }, completion: { [weak self] _ in + if let strongSelf = self { + for sourceNode in strongSelf.sourceNodes { + sourceNode.alpha = 1.0 + } + } + + completedEffect = true + intermediateCompletion() + }) + } + + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.contentContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { _ in + }) + if let _ = self.validLayout, let sourceNode = self.sourceNodes.first { + let sourceFrame = sourceNode.view.convert(sourceNode.bounds, to: self.view) + self.contentContainerNode.layer.animateFrame(from: self.contentContainerNode.frame, to: sourceFrame, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, removeOnCompletion: false) + } + completedSourceNodes = true + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + transition.updateFrame(view: self.effectView, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + + let sideInset: CGFloat = 18.0 + + var contentSize = CGSize() + contentSize.width = min(layout.size.width - 40.0, 250.0) + var applyNodes: [(ASDisplayNode, CGFloat, (CGFloat) -> Void)] = [] + for itemNode in self.contentNodes { + let (width, height, apply) = itemNode.updateLayout(maxWidth: layout.size.width - sideInset * 2.0) + applyNodes.append((itemNode, height, apply)) + contentSize.width = max(contentSize.width, width) + contentSize.height += height + } + + let insets = layout.insets(options: .input) + + let contentOrigin: CGPoint + if let sourceNode = self.sourceNodes.first, let screenFrame = sourceNode.supernode?.convert(sourceNode.frame, to: nil) { + contentOrigin = CGPoint(x: screenFrame.maxX - contentSize.width + 8.0, y: layout.size.height - 66.0 - insets.bottom - contentSize.height) + } else { + contentOrigin = CGPoint(x: layout.size.width - sideInset - contentSize.width, y: layout.size.height - 66.0 - layout.intrinsicInsets.bottom - contentSize.height) + } + + transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: contentOrigin, size: contentSize)) + var nextY: CGFloat = 0.0 + for (itemNode, height, apply) in applyNodes { + transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: nextY), size: CGSize(width: contentSize.width, height: height))) + apply(contentSize.width) + nextY += height + } + } + + @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.cancel() + } + } +} + +private func setAnchorPoint(anchorPoint: CGPoint, forView view: UIView) { + var newPoint = CGPoint(x: view.bounds.size.width * anchorPoint.x, + y: view.bounds.size.height * anchorPoint.y) + + + var oldPoint = CGPoint(x: view.bounds.size.width * view.layer.anchorPoint.x, + y: view.bounds.size.height * view.layer.anchorPoint.y) + + newPoint = newPoint.applying(view.transform) + oldPoint = oldPoint.applying(view.transform) + + var position = view.layer.position + position.x -= oldPoint.x + position.x += newPoint.x + + position.y -= oldPoint.y + position.y += newPoint.y + + view.layer.position = position + view.layer.anchorPoint = anchorPoint +} diff --git a/submodules/Postbox/Sources/ChatListTable.swift b/submodules/Postbox/Sources/ChatListTable.swift index 6b9682e432..b0c84fb807 100644 --- a/submodules/Postbox/Sources/ChatListTable.swift +++ b/submodules/Postbox/Sources/ChatListTable.swift @@ -381,7 +381,7 @@ final class ChatListTable: Table { self.valueBox.remove(self.table, key: self.key(groupId: groupId, index: ChatListIndex(pinningIndex: nil, messageIndex: index), type: .hole), secure: false) } - func entriesAround(groupId: PeerGroupId, index: ChatListIndex, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable, count: Int) -> (entries: [ChatListIntermediateEntry], lower: ChatListIntermediateEntry?, upper: ChatListIntermediateEntry?) { + func entriesAround(groupId: PeerGroupId, index: ChatListIndex, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable, count: Int, predicate: ((ChatListIntermediateEntry) -> Bool)?) -> (entries: [ChatListIntermediateEntry], lower: ChatListIntermediateEntry?, upper: ChatListIntermediateEntry?) { self.ensureInitialized(groupId: groupId) var lowerEntries: [ChatListIntermediateEntry] = [] @@ -389,18 +389,38 @@ final class ChatListTable: Table { var lower: ChatListIntermediateEntry? var upper: ChatListIntermediateEntry? - self.valueBox.range(self.table, start: self.key(groupId: groupId, index: index, type: .message), end: self.lowerBound(groupId: groupId), values: { key, value in - lowerEntries.append(readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value)) - return true + self.valueBox.filteredRange(self.table, start: self.key(groupId: groupId, index: index, type: .message), end: self.lowerBound(groupId: groupId), values: { key, value in + let entry = readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value) + if let predicate = predicate { + if predicate(entry) { + lowerEntries.append(entry) + return .accept + } else { + return .skip + } + } else { + lowerEntries.append(entry) + return .accept + } }, limit: count / 2 + 1) if lowerEntries.count >= count / 2 + 1 { lower = lowerEntries.last lowerEntries.removeLast() } - self.valueBox.range(self.table, start: self.key(groupId: groupId, index: index, type: .message).predecessor, end: self.upperBound(groupId: groupId), values: { key, value in - upperEntries.append(readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value)) - return true + self.valueBox.filteredRange(self.table, start: self.key(groupId: groupId, index: index, type: .message).predecessor, end: self.upperBound(groupId: groupId), values: { key, value in + let entry = readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value) + if let predicate = predicate { + if predicate(entry) { + upperEntries.append(entry) + return .accept + } else { + return .skip + } + } else { + upperEntries.append(entry) + return .accept + } }, limit: count - lowerEntries.count + 1) if upperEntries.count >= count - lowerEntries.count + 1 { upper = upperEntries.last @@ -415,12 +435,20 @@ final class ChatListTable: Table { startEntryType = .message case .hole: startEntryType = .hole - /*case .groupReference: - startEntryType = .groupReference*/ } - self.valueBox.range(self.table, start: self.key(groupId: groupId, index: lowerEntries.last!.index, type: startEntryType), end: self.lowerBound(groupId: groupId), values: { key, value in - additionalLowerEntries.append(readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value)) - return true + self.valueBox.filteredRange(self.table, start: self.key(groupId: groupId, index: lowerEntries.last!.index, type: startEntryType), end: self.lowerBound(groupId: groupId), values: { key, value in + let entry = readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value) + if let predicate = predicate { + if predicate(entry) { + additionalLowerEntries.append(entry) + return .accept + } else { + return .skip + } + } else { + additionalLowerEntries.append(entry) + return .accept + } }, limit: count - lowerEntries.count - upperEntries.count + 1) if additionalLowerEntries.count >= count - lowerEntries.count + upperEntries.count + 1 { lower = additionalLowerEntries.last @@ -502,7 +530,7 @@ final class ChatListTable: Table { return result } - func earlierEntries(groupId: PeerGroupId, index: (ChatListIndex, Bool)?, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable, count: Int) -> [ChatListIntermediateEntry] { + func earlierEntries(groupId: PeerGroupId, index: (ChatListIndex, Bool)?, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable, count: Int, predicate: ((ChatListIntermediateEntry) -> Bool)?) -> [ChatListIntermediateEntry] { self.ensureInitialized(groupId: groupId) var entries: [ChatListIntermediateEntry] = [] @@ -513,9 +541,19 @@ final class ChatListTable: Table { key = self.upperBound(groupId: groupId) } - self.valueBox.range(self.table, start: key, end: self.lowerBound(groupId: groupId), values: { key, value in - entries.append(readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value)) - return true + self.valueBox.filteredRange(self.table, start: key, end: self.lowerBound(groupId: groupId), values: { key, value in + let entry = readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value) + if let predicate = predicate { + if predicate(entry) { + entries.append(entry) + return .accept + } else { + return .skip + } + } else { + entries.append(entry) + return .accept + } }, limit: count) return entries } @@ -556,7 +594,7 @@ final class ChatListTable: Table { return entries } - func laterEntries(groupId: PeerGroupId, index: (ChatListIndex, Bool)?, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable, count: Int) -> [ChatListIntermediateEntry] { + func laterEntries(groupId: PeerGroupId, index: (ChatListIndex, Bool)?, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable, count: Int, predicate: ((ChatListIntermediateEntry) -> Bool)?) -> [ChatListIntermediateEntry] { self.ensureInitialized(groupId: groupId) var entries: [ChatListIntermediateEntry] = [] @@ -567,9 +605,19 @@ final class ChatListTable: Table { key = self.lowerBound(groupId: groupId) } - self.valueBox.range(self.table, start: key, end: self.upperBound(groupId: groupId), values: { key, value in - entries.append(readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value)) - return true + self.valueBox.filteredRange(self.table, start: key, end: self.upperBound(groupId: groupId), values: { key, value in + let entry = readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value) + if let predicate = predicate { + if predicate(entry) { + entries.append(entry) + return .accept + } else { + return .skip + } + } else { + entries.append(entry) + return .accept + } }, limit: count) return entries } @@ -590,6 +638,19 @@ final class ChatListTable: Table { return nil } + func getEntry(groupId: PeerGroupId, peerId: PeerId, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable) -> ChatListIntermediateEntry? { + if let (peerGroupId, index) = self.getPeerChatListIndex(peerId: peerId), peerGroupId == groupId { + let key = self.key(groupId: groupId, index: index, type: .message) + if let value = self.valueBox.get(self.table, key: key) { + return readEntry(groupId: groupId, messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, key: key, value: value) + } else { + return nil + } + } else { + return nil + } + } + func allEntries(groupId: PeerGroupId) -> [ChatListEntryInfo] { var entries: [ChatListEntryInfo] = [] self.valueBox.range(self.table, start: self.upperBound(groupId: groupId), end: self.lowerBound(groupId: groupId), values: { key, value in @@ -710,7 +771,7 @@ final class ChatListTable: Table { } func debugList(groupId: PeerGroupId, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable) -> [ChatListIntermediateEntry] { - return self.laterEntries(groupId: groupId, index: (ChatListIndex.absoluteLowerBound, true), messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, count: 1000) + return self.laterEntries(groupId: groupId, index: (ChatListIndex.absoluteLowerBound, true), messageHistoryTable: messageHistoryTable, peerChatInterfaceStateTable: peerChatInterfaceStateTable, count: 1000, predicate: nil) } func getNamespaceEntries(groupId: PeerGroupId, namespace: MessageId.Namespace, summaryTag: MessageTags?, messageIndexTable: MessageHistoryIndexTable, messageHistoryTable: MessageHistoryTable, peerChatInterfaceStateTable: PeerChatInterfaceStateTable, readStateTable: MessageHistoryReadStateTable, summaryTable: MessageHistoryTagsSummaryTable) -> [ChatListNamespaceEntry] { diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index e8b09b758e..5c2ee9f22f 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -297,6 +297,7 @@ private func updatedRenderedPeer(_ renderedPeer: RenderedPeer, updatedPeers: [Pe final class MutableChatListView { let groupId: PeerGroupId + let filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)? private let summaryComponents: ChatListEntrySummaryComponents fileprivate var additionalItemIds: Set fileprivate var additionalItemEntries: [MutableChatListEntry] @@ -306,8 +307,11 @@ final class MutableChatListView { fileprivate var groupEntries: [ChatListGroupReferenceEntry] private var count: Int - init(postbox: Postbox, groupId: PeerGroupId, earlier: MutableChatListEntry?, entries: [MutableChatListEntry], later: MutableChatListEntry?, count: Int, summaryComponents: ChatListEntrySummaryComponents) { + init(postbox: Postbox, groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)?, aroundIndex: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents) { + let (entries, earlier, later) = postbox.fetchAroundChatEntries(groupId: groupId, index: aroundIndex, count: count, filterPredicate: filterPredicate) + self.groupId = groupId + self.filterPredicate = filterPredicate self.earlier = earlier self.entries = entries self.later = later @@ -405,7 +409,7 @@ final class MutableChatListView { index = self.entries[self.entries.count / 2].index } - let (entries, earlier, later) = postbox.fetchAroundChatEntries(groupId: self.groupId, index: index, count: self.count) + let (entries, earlier, later) = postbox.fetchAroundChatEntries(groupId: self.groupId, index: index, count: self.count, filterPredicate: self.filterPredicate) let currentGroupEntries = self.groupEntries self.reloadGroups(postbox: postbox) @@ -426,7 +430,7 @@ final class MutableChatListView { return updated } - func replay(postbox: Postbox, operations: [PeerGroupId: [ChatListOperation]], updatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], updatedPeers: [PeerId: Peer], updatedPeerPresences: [PeerId: PeerPresence], transaction: PostboxTransaction, context: MutableChatListViewReplayContext) -> Bool { + func replay(postbox: Postbox, operations: [PeerGroupId: [ChatListOperation]], updatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], updatedPeers: [PeerId: Peer], updatedPeerPresences: [PeerId: PeerPresence], transaction: PostboxTransaction, context: MutableChatListViewReplayContext) -> Bool { var hasChanges = false if let groupOperations = operations[self.groupId] { @@ -489,6 +493,43 @@ final class MutableChatListView { } if !updatedPeerNotificationSettings.isEmpty { + if let filterPredicate = self.filterPredicate { + for (peerId, settingsChange) in updatedPeerNotificationSettings { + if let peer = postbox.peerTable.get(peerId) { + let wasIncluded = filterPredicate(peer, settingsChange.0) + let isIncluded = filterPredicate(peer, settingsChange.1) + if wasIncluded != isIncluded { + if isIncluded { + if let entry = postbox.chatListTable.getEntry(groupId: self.groupId, peerId: peerId, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable) { + switch entry { + case let .message(index, message, embeddedState): + let combinedReadState = postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId) + if self.add(.IntermediateMessageEntry(entry.index, message, combinedReadState, embeddedState), postbox: postbox) { + hasChanges = true + } + default: + break + } + } + } else { + loop: for i in 0 ..< self.entries.count { + switch self.entries[i] { + case .MessageEntry(let index, _, _, _, _, _, _, _, _), .IntermediateMessageEntry(let index, _, _, _): + if index.messageIndex.id.peerId == peerId { + self.entries.remove(at: i) + hasChanges = true + break loop + } + default: + break + } + } + } + } + } + } + } + for i in 0 ..< self.entries.count { switch self.entries[i] { case let .MessageEntry(index, message, readState, _, embeddedState, peer, peerPresence, summaryInfo, hasFailed): @@ -496,7 +537,7 @@ final class MutableChatListView { if let peer = peer.peers[peer.peerId], let associatedPeerId = peer.associatedPeerId { notificationSettingsPeerId = associatedPeerId } - if let settings = updatedPeerNotificationSettings[notificationSettingsPeerId] { + if let (_, settings) = updatedPeerNotificationSettings[notificationSettingsPeerId] { self.entries[i] = .MessageEntry(index, message, readState, settings, embeddedState, peer, peerPresence, summaryInfo, hasFailed) hasChanges = true } @@ -619,7 +660,24 @@ final class MutableChatListView { } func add(_ initialEntry: MutableChatListEntry, postbox: Postbox) -> Bool { + if let filterPredicate = self.filterPredicate { + switch initialEntry { + case .IntermediateMessageEntry(let index, _, _, _), .MessageEntry(let index, _, _, _, _, _, _, _, _): + if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) { + if !filterPredicate(peer, postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId)) { + return false + } + } else { + return false + } + break + default: + break + } + } + let entry = processedChatListEntry(initialEntry, cachedDataTable: postbox.cachedPeerDataTable, readStateTable: postbox.readStateTable, messageHistoryTable: postbox.messageHistoryTable) + if self.entries.count == 0 { self.entries.append(entry) return true @@ -756,10 +814,10 @@ final class MutableChatListView { } if let later = self.later { - addedEntries += postbox.fetchLaterChatEntries(groupId: self.groupId, index: later.index.predecessor, count: self.count) + addedEntries += postbox.fetchLaterChatEntries(groupId: self.groupId, index: later.index.predecessor, count: self.count, filterPredicate: filterPredicate) } if let earlier = self.earlier { - addedEntries += postbox.fetchEarlierChatEntries(groupId: self.groupId, index: earlier.index.successor, count: self.count) + addedEntries += postbox.fetchEarlierChatEntries(groupId: self.groupId, index: earlier.index.successor, count: self.count, filterPredicate: filterPredicate) } addedEntries += self.entries @@ -808,7 +866,7 @@ final class MutableChatListView { earlyId = self.entries[i].index } - let earlierEntries = postbox.fetchEarlierChatEntries(groupId: self.groupId, index: earlyId, count: 1) + let earlierEntries = postbox.fetchEarlierChatEntries(groupId: self.groupId, index: earlyId, count: 1, filterPredicate: self.filterPredicate) self.earlier = earlierEntries.first } @@ -819,7 +877,7 @@ final class MutableChatListView { laterId = self.entries[i].index } - let laterEntries = postbox.fetchLaterChatEntries(groupId: self.groupId, index: laterId, count: 1) + let laterEntries = postbox.fetchLaterChatEntries(groupId: self.groupId, index: laterId, count: 1, filterPredicate: self.filterPredicate) self.later = laterEntries.first } } diff --git a/submodules/Postbox/Sources/PeerNotificationSettingsTable.swift b/submodules/Postbox/Sources/PeerNotificationSettingsTable.swift index 2d45e0e054..7fea4ea0d4 100644 --- a/submodules/Postbox/Sources/PeerNotificationSettingsTable.swift +++ b/submodules/Postbox/Sources/PeerNotificationSettingsTable.swift @@ -225,7 +225,7 @@ final class PeerNotificationSettingsTable: Table { return (added, removed) } - func resetAll(to settings: PeerNotificationSettings, updatedSettings: inout Set, updatedTimestamps: inout [PeerId: PeerNotificationSettingsBehaviorTimestamp]) -> [PeerId] { + func resetAll(to settings: PeerNotificationSettings, updatedSettings: inout Set, updatedTimestamps: inout [PeerId: PeerNotificationSettingsBehaviorTimestamp]) -> [PeerId: PeerNotificationSettings?] { let lowerBound = ValueBoxKey(length: 8) lowerBound.setInt64(0, value: 0) let upperBound = ValueBoxKey(length: 8) @@ -236,17 +236,17 @@ final class PeerNotificationSettingsTable: Table { return true }, limit: 0) - var updatedPeerIds: [PeerId] = [] + var updatedPeers: [PeerId: PeerNotificationSettings?] = [:] for peerId in peerIds { let entry = self.getEntry(peerId) if let current = entry.current, !current.isEqual(to: settings) || entry.pending != nil { let _ = self.setCurrent(id: peerId, settings: settings, updatedTimestamps: &updatedTimestamps) let _ = self.setPending(id: peerId, settings: nil, updatedSettings: &updatedSettings) - updatedPeerIds.append(peerId) + updatedPeers[peerId] = entry.effective } } - return updatedPeerIds + return updatedPeers } override func beforeCommit() { diff --git a/submodules/Postbox/Sources/PeerNotificationSettingsView.swift b/submodules/Postbox/Sources/PeerNotificationSettingsView.swift index c22d0b4a93..22a0825d02 100644 --- a/submodules/Postbox/Sources/PeerNotificationSettingsView.swift +++ b/submodules/Postbox/Sources/PeerNotificationSettingsView.swift @@ -26,7 +26,7 @@ final class MutablePeerNotificationSettingsView: MutablePostboxView { if let peer = postbox.peerTable.get(peerId), let associatedPeerId = peer.associatedPeerId { notificationPeerId = associatedPeerId } - if let settings = transaction.currentUpdatedPeerNotificationSettings[notificationPeerId] { + if let (_, settings) = transaction.currentUpdatedPeerNotificationSettings[notificationPeerId] { self.notificationSettings[peerId] = settings updated = true } diff --git a/submodules/Postbox/Sources/PeerView.swift b/submodules/Postbox/Sources/PeerView.swift index fd389a7fa5..0c5d610e2f 100644 --- a/submodules/Postbox/Sources/PeerView.swift +++ b/submodules/Postbox/Sources/PeerView.swift @@ -214,12 +214,12 @@ final class MutablePeerView: MutablePostboxView { if let peer = self.peers[self.peerId] { if let associatedPeerId = peer.associatedPeerId { - if let notificationSettings = updatedNotificationSettings[associatedPeerId] { + if let (_, notificationSettings) = updatedNotificationSettings[associatedPeerId] { self.notificationSettings = notificationSettings updated = true } } else { - if let notificationSettings = updatedNotificationSettings[peer.id] { + if let (_, notificationSettings) = updatedNotificationSettings[peer.id] { self.notificationSettings = notificationSettings updated = true } diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 187939b9ab..8ce0ed2c6e 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1077,7 +1077,7 @@ public final class Postbox { private var currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:] private var currentUpdatedPeers: [PeerId: Peer] = [:] - private var currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings] = [:] + private var currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)] = [:] private var currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp] = [:] private var currentUpdatedCachedPeerData: [PeerId: CachedPeerData] = [:] private var currentUpdatedPeerPresences: [PeerId: PeerPresence] = [:] @@ -1650,8 +1650,28 @@ public final class Postbox { self.synchronizeGroupMessageStatsTable.set(groupId: groupId, namespace: namespace, needsValidation: false, operations: &self.currentUpdatedGroupSummarySynchronizeOperations) } - func fetchAroundChatEntries(groupId: PeerGroupId, index: ChatListIndex, count: Int) -> (entries: [MutableChatListEntry], earlier: MutableChatListEntry?, later: MutableChatListEntry?) { - let (intermediateEntries, intermediateLower, intermediateUpper) = self.chatListTable.entriesAround(groupId: groupId, index: index, messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count) + private func mappedChatListFilterPredicate(_ predicate: @escaping (Peer, PeerNotificationSettings?) -> Bool) -> (ChatListIntermediateEntry) -> Bool { + return { entry in + switch entry { + case let .message(index, _, _): + if let peer = self.peerTable.get(index.messageIndex.id.peerId) { + if predicate(peer, self.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId)) { + return true + } else { + return false + } + } else { + return false + } + case .hole: + return true + } + } + } + + func fetchAroundChatEntries(groupId: PeerGroupId, index: ChatListIndex, count: Int, filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)?) -> (entries: [MutableChatListEntry], earlier: MutableChatListEntry?, later: MutableChatListEntry?) { + let mappedPredicate = filterPredicate.flatMap(self.mappedChatListFilterPredicate) + let (intermediateEntries, intermediateLower, intermediateUpper) = self.chatListTable.entriesAround(groupId: groupId, index: index, messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count, predicate: mappedPredicate) let entries: [MutableChatListEntry] = intermediateEntries.map { entry in return MutableChatListEntry(entry, cachedDataTable: self.cachedPeerDataTable, readStateTable: self.readStateTable, messageHistoryTable: self.messageHistoryTable) } @@ -1665,16 +1685,18 @@ public final class Postbox { return (entries, lower, upper) } - func fetchEarlierChatEntries(groupId: PeerGroupId, index: ChatListIndex?, count: Int) -> [MutableChatListEntry] { - let intermediateEntries = self.chatListTable.earlierEntries(groupId: groupId, index: index.flatMap({ ($0, true) }), messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count) + func fetchEarlierChatEntries(groupId: PeerGroupId, index: ChatListIndex?, count: Int, filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)?) -> [MutableChatListEntry] { + let mappedPredicate = filterPredicate.flatMap(self.mappedChatListFilterPredicate) + let intermediateEntries = self.chatListTable.earlierEntries(groupId: groupId, index: index.flatMap({ ($0, true) }), messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count, predicate: mappedPredicate) let entries: [MutableChatListEntry] = intermediateEntries.map { entry in return MutableChatListEntry(entry, cachedDataTable: self.cachedPeerDataTable, readStateTable: self.readStateTable, messageHistoryTable: self.messageHistoryTable) } return entries } - func fetchLaterChatEntries(groupId: PeerGroupId, index: ChatListIndex?, count: Int) -> [MutableChatListEntry] { - let intermediateEntries = self.chatListTable.laterEntries(groupId: groupId, index: index.flatMap({ ($0, true) }), messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count) + func fetchLaterChatEntries(groupId: PeerGroupId, index: ChatListIndex?, count: Int, filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)?) -> [MutableChatListEntry] { + let mappedPredicate = filterPredicate.flatMap(self.mappedChatListFilterPredicate) + let intermediateEntries = self.chatListTable.laterEntries(groupId: groupId, index: index.flatMap({ ($0, true) }), messageHistoryTable: self.messageHistoryTable, peerChatInterfaceStateTable: self.peerChatInterfaceStateTable, count: count, predicate: mappedPredicate) let entries: [MutableChatListEntry] = intermediateEntries.map { entry in return MutableChatListEntry(entry, cachedDataTable: self.cachedPeerDataTable, readStateTable: self.readStateTable, messageHistoryTable: self.messageHistoryTable) } @@ -1891,21 +1913,33 @@ public final class Postbox { fileprivate func updateCurrentPeerNotificationSettings(_ notificationSettings: [PeerId: PeerNotificationSettings]) { for (peerId, settings) in notificationSettings { + let previous: PeerNotificationSettings? + if let (value, _) = self.currentUpdatedPeerNotificationSettings[peerId] { + previous = value + } else { + previous = self.peerNotificationSettingsTable.getEffective(peerId) + } if let updated = self.peerNotificationSettingsTable.setCurrent(id: peerId, settings: settings, updatedTimestamps: &self.currentUpdatedPeerNotificationBehaviorTimestamps) { - self.currentUpdatedPeerNotificationSettings[peerId] = updated + self.currentUpdatedPeerNotificationSettings[peerId] = (previous, updated) } } } fileprivate func updatePendingPeerNotificationSettings(peerId: PeerId, settings: PeerNotificationSettings?) { + let previous: PeerNotificationSettings? + if let (value, _) = self.currentUpdatedPeerNotificationSettings[peerId] { + previous = value + } else { + previous = self.peerNotificationSettingsTable.getEffective(peerId) + } if let updated = self.peerNotificationSettingsTable.setPending(id: peerId, settings: settings, updatedSettings: &self.currentUpdatedPendingPeerNotificationSettings) { - self.currentUpdatedPeerNotificationSettings[peerId] = updated + self.currentUpdatedPeerNotificationSettings[peerId] = (previous, updated) } } fileprivate func resetAllPeerNotificationSettings(_ notificationSettings: PeerNotificationSettings) { - for peerId in self.peerNotificationSettingsTable.resetAll(to: notificationSettings, updatedSettings: &self.currentUpdatedPendingPeerNotificationSettings, updatedTimestamps: &self.currentUpdatedPeerNotificationBehaviorTimestamps) { - self.currentUpdatedPeerNotificationSettings[peerId] = notificationSettings + for (peerId, previous) in self.peerNotificationSettingsTable.resetAll(to: notificationSettings, updatedSettings: &self.currentUpdatedPendingPeerNotificationSettings, updatedTimestamps: &self.currentUpdatedPeerNotificationBehaviorTimestamps) { + self.currentUpdatedPeerNotificationSettings[peerId] = (previous, notificationSettings) } } @@ -2508,15 +2542,13 @@ public final class Postbox { |> switchToLatest } - public func tailChatListView(groupId: PeerGroupId, count: Int, summaryComponents: ChatListEntrySummaryComponents) -> Signal<(ChatListView, ViewUpdateType), NoError> { - return self.aroundChatListView(groupId: groupId, index: ChatListIndex.absoluteUpperBound, count: count, summaryComponents: summaryComponents, userInteractive: true) + public func tailChatListView(groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)? = nil, count: Int, summaryComponents: ChatListEntrySummaryComponents) -> Signal<(ChatListView, ViewUpdateType), NoError> { + return self.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: ChatListIndex.absoluteUpperBound, count: count, summaryComponents: summaryComponents, userInteractive: true) } - public func aroundChatListView(groupId: PeerGroupId, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)? = nil, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> { return self.transactionSignal(userInteractive: userInteractive, { subscriber, transaction in - let (entries, earlier, later) = self.fetchAroundChatEntries(groupId: groupId, index: index, count: count) - - let mutableView = MutableChatListView(postbox: self, groupId: groupId, earlier: earlier, entries: entries, later: later, count: count, summaryComponents: summaryComponents) + let mutableView = MutableChatListView(postbox: self, groupId: groupId, filterPredicate: filterPredicate, aroundIndex: index, count: count, summaryComponents: summaryComponents) mutableView.render(postbox: self, renderMessage: self.renderIntermediateMessage, getPeer: { id in return self.peerTable.get(id) }, getPeerNotificationSettings: { self.peerNotificationSettingsTable.getEffective($0) }, getPeerPresence: { self.peerPresenceTable.get($0) }) diff --git a/submodules/Postbox/Sources/PostboxTransaction.swift b/submodules/Postbox/Sources/PostboxTransaction.swift index ab12fb1cb1..207ed2a45a 100644 --- a/submodules/Postbox/Sources/PostboxTransaction.swift +++ b/submodules/Postbox/Sources/PostboxTransaction.swift @@ -7,7 +7,7 @@ final class PostboxTransaction { let chatListOperations: [PeerGroupId: [ChatListOperation]] let currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion] let currentUpdatedPeers: [PeerId: Peer] - let currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings] + let currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)] let currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp] let currentUpdatedCachedPeerData: [PeerId: CachedPeerData] let currentUpdatedPeerPresences: [PeerId: PeerPresence] @@ -172,7 +172,7 @@ final class PostboxTransaction { return true } - init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: PeerNotificationSettings], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?], currentUpdatedTotalUnreadState: ChatListTotalUnreadState?, currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, replacedAdditionalChatListItems: [PeerId]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set, updatedFailedMessageIds: Set) { + init(currentUpdatedState: PostboxCoding?, currentPeerHoleOperations: [MessageHistoryIndexHoleOperationKey: [MessageHistoryIndexHoleOperation]] = [:], currentOperationsByPeerId: [PeerId: [MessageHistoryOperation]], chatListOperations: [PeerGroupId: [ChatListOperation]], currentUpdatedChatListInclusions: [PeerId: PeerChatListInclusion], currentUpdatedPeers: [PeerId: Peer], currentUpdatedPeerNotificationSettings: [PeerId: (PeerNotificationSettings?, PeerNotificationSettings)], currentUpdatedPeerNotificationBehaviorTimestamps: [PeerId: PeerNotificationSettingsBehaviorTimestamp], currentUpdatedCachedPeerData: [PeerId: CachedPeerData], currentUpdatedPeerPresences: [PeerId: PeerPresence], currentUpdatedPeerChatListEmbeddedStates: [PeerId: PeerChatListEmbeddedInterfaceState?], currentUpdatedTotalUnreadState: ChatListTotalUnreadState?, currentUpdatedTotalUnreadSummaries: [PeerGroupId: PeerGroupUnreadCountersCombinedSummary], alteredInitialPeerCombinedReadStates: [PeerId: CombinedPeerReadState], currentPeerMergedOperationLogOperations: [PeerMergedOperationLogOperation], currentTimestampBasedMessageAttributesOperations: [TimestampBasedMessageAttributesOperation], unsentMessageOperations: [IntermediateMessageHistoryUnsentOperation], updatedSynchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation?], currentUpdatedGroupSummarySynchronizeOperations: [PeerGroupAndNamespace: Bool], currentPreferencesOperations: [PreferencesOperation], currentOrderedItemListOperations: [Int32: [OrderedItemListOperation]], currentItemCollectionItemsOperations: [ItemCollectionId: [ItemCollectionItemsOperation]], currentItemCollectionInfosOperations: [ItemCollectionInfosOperation], currentUpdatedPeerChatStates: Set, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set, replacedAdditionalChatListItems: [PeerId]?, updatedNoticeEntryKeys: Set, updatedCacheEntryKeys: Set, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set, updatedFailedMessageIds: Set) { self.currentUpdatedState = currentUpdatedState self.currentPeerHoleOperations = currentPeerHoleOperations self.currentOperationsByPeerId = currentOperationsByPeerId diff --git a/submodules/Postbox/Sources/SqliteValueBox.swift b/submodules/Postbox/Sources/SqliteValueBox.swift index dcaa340bb2..70b3e5661d 100644 --- a/submodules/Postbox/Sources/SqliteValueBox.swift +++ b/submodules/Postbox/Sources/SqliteValueBox.swift @@ -1498,6 +1498,38 @@ public final class SqliteValueBox: ValueBox { withExtendedLifetime(end, {}) } + public func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> ValueBoxFilterResult, limit: Int) { + var currentStart = start + var acceptedCount = 0 + while acceptedCount < limit { + var hadStop = false + var lastKey: ValueBoxKey? + self.range(table, start: currentStart, end: end, values: { key, value in + lastKey = key + let result = values(key, value) + switch result { + case .accept: + acceptedCount += 1 + return true + case .skip: + return true + case .stop: + hadStop = true + return false + } + return true + }, limit: limit) + if let lastKey = lastKey { + currentStart = lastKey + } else { + break + } + if hadStop { + break + } + } + } + public func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> Bool, limit: Int) { precondition(self.queue.isCurrent()) if let _ = self.tables[table.id] { diff --git a/submodules/Postbox/Sources/ValueBox.swift b/submodules/Postbox/Sources/ValueBox.swift index 00297c3708..08396740e6 100644 --- a/submodules/Postbox/Sources/ValueBox.swift +++ b/submodules/Postbox/Sources/ValueBox.swift @@ -57,6 +57,12 @@ public struct ValueBoxEncryptionParameters { } } +public enum ValueBoxFilterResult { + case accept + case skip + case stop +} + public protocol ValueBox { func begin() func commit() @@ -66,6 +72,7 @@ public protocol ValueBox { func endStats() func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> Bool, limit: Int) + func filteredRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, values: (ValueBoxKey, ReadBuffer) -> ValueBoxFilterResult, limit: Int) func range(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey, keys: (ValueBoxKey) -> Bool, limit: Int) func scan(_ table: ValueBoxTable, values: (ValueBoxKey, ReadBuffer) -> Bool) func scan(_ table: ValueBoxTable, keys: (ValueBoxKey) -> Bool) diff --git a/submodules/TelegramCore/Sources/AccountViewTracker.swift b/submodules/TelegramCore/Sources/AccountViewTracker.swift index 94913597d1..0d5ebe5678 100644 --- a/submodules/TelegramCore/Sources/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/AccountViewTracker.swift @@ -1330,17 +1330,17 @@ public final class AccountViewTracker { }) } - public func tailChatListView(groupId: PeerGroupId, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func tailChatListView(groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)? = nil, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { if let account = self.account { - return self.wrappedChatListView(signal: account.postbox.tailChatListView(groupId: groupId, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)))) + return self.wrappedChatListView(signal: account.postbox.tailChatListView(groupId: groupId, filterPredicate: filterPredicate, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)))) } else { return .never() } } - public func aroundChatListView(groupId: PeerGroupId, index: ChatListIndex, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { + public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ((Peer, PeerNotificationSettings?) -> Bool)? = nil, index: ChatListIndex, count: Int) -> Signal<(ChatListView, ViewUpdateType), NoError> { if let account = self.account { - return self.wrappedChatListView(signal: account.postbox.aroundChatListView(groupId: groupId, index: index, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)))) + return self.wrappedChatListView(signal: account.postbox.aroundChatListView(groupId: groupId, filterPredicate: filterPredicate, index: index, count: count, summaryComponents: ChatListEntrySummaryComponents(tagSummary: ChatListEntryMessageTagSummaryComponent(tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud), actionsSummary: ChatListEntryPendingMessageActionsSummaryComponent(type: PendingMessageActionType.consumeUnseenPersonalMessage, namespace: Namespaces.Message.Cloud)))) } else { return .never() } diff --git a/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift b/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift index d09c06ccc4..5e5ad3fb8e 100644 --- a/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift +++ b/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift @@ -177,15 +177,24 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa context.setStrokeColor(UIColor.black.cgColor) let borderWidth: CGFloat - if abs(UIScreenPixel - 0.5) < CGFloat.ulpOfOne { - borderWidth = UIScreenPixel + let borderOffset: CGFloat + + let innerExtension: CGFloat + if knockout && !mask { + innerExtension = 0.25 } else { - borderWidth = UIScreenPixel * 2.0 + innerExtension = 0.25 + } + + if abs(UIScreenPixel - 0.5) < CGFloat.ulpOfOne { + borderWidth = UIScreenPixel + innerExtension + borderOffset = -innerExtension / 2.0 + UIScreenPixel / 2.0 + } else { + borderWidth = UIScreenPixel * 2.0 + innerExtension + borderOffset = -innerExtension / 2.0 + UIScreenPixel * 2.0 / 2.0 } context.setLineWidth(borderWidth) - let borderOffset: CGFloat = borderWidth / 2.0 - context.move(to: CGPoint(x: -borderOffset, y: topLeftRadius + borderOffset)) context.addArc(tangent1End: CGPoint(x: -borderOffset, y: -borderOffset), tangent2End: CGPoint(x: topLeftRadius + borderOffset, y: -borderOffset), radius: topLeftRadius + borderOffset * 2.0) context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius - borderOffset, y: -borderOffset)) @@ -278,8 +287,6 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa context.setBlendMode(.copy) let image = outlineContext.generateImage()! context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) - context.setBlendMode(.normal) - context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) })! let drawingContext = DrawingContext(size: imageSize) diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBackground.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBackground.swift index 09ab5933d4..a24df24f77 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageBackground.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageBackground.swift @@ -82,8 +82,8 @@ class ChatMessageBackground: ASDisplayNode { super.init() self.isUserInteractionEnabled = false - self.addSubnode(self.imageNode) self.addSubnode(self.outlineImageNode) + self.addSubnode(self.imageNode) } func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {