mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-19 12:10:55 +00:00
Experimental chat list filtering
This commit is contained in:
parent
13b0847b1e
commit
d2e3c9d54a
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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<ChatListNodeFilter>(.all)
|
||||
private let chatListLocation = ValuePromise<ChatListNodeLocation>()
|
||||
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<ChatListNodeState>(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<ChatListNodeListViewTransition, NoError> in
|
||||
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, updateAndFilter, state) -> Signal<ChatListNodeListViewTransition, NoError> 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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,18 +31,90 @@ struct ChatListNodeViewUpdate {
|
||||
let scrollPosition: ChatListNodeViewScrollPosition?
|
||||
}
|
||||
|
||||
func chatListViewForLocation(groupId: PeerGroupId, location: ChatListNodeLocation, account: Account) -> Signal<ChatListNodeViewUpdate, NoError> {
|
||||
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<ChatListNodeViewUpdate, NoError> {
|
||||
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
|
||||
|
@ -11,6 +11,7 @@ struct ChatListNodeView {
|
||||
let originalView: ChatListView
|
||||
let filteredEntries: [ChatListNodeEntry]
|
||||
let isLoading: Bool
|
||||
let filter: ChatListNodeFilter
|
||||
}
|
||||
|
||||
enum ChatListNodeViewTransitionReason {
|
||||
|
@ -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<Bool>(true)
|
||||
override public var ready: Promise<Bool> {
|
||||
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
|
||||
}
|
@ -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] {
|
||||
|
@ -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<PeerId>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -225,7 +225,7 @@ final class PeerNotificationSettingsTable: Table {
|
||||
return (added, removed)
|
||||
}
|
||||
|
||||
func resetAll(to settings: PeerNotificationSettings, updatedSettings: inout Set<PeerId>, updatedTimestamps: inout [PeerId: PeerNotificationSettingsBehaviorTimestamp]) -> [PeerId] {
|
||||
func resetAll(to settings: PeerNotificationSettings, updatedSettings: inout Set<PeerId>, 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() {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) })
|
||||
|
@ -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<PeerId>, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set<PeerId>?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set<PeerId>, replacedAdditionalChatListItems: [PeerId]?, updatedNoticeEntryKeys: Set<NoticeEntryKey>, updatedCacheEntryKeys: Set<ItemCacheEntryId>, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set<PeerId>, updatedFailedMessageIds: Set<MessageId>) {
|
||||
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<PeerId>, currentGlobalTagsOperations: [GlobalMessageHistoryTagsOperation], currentLocalTagsOperations: [IntermediateMessageHistoryLocalTagsOperation], updatedMedia: [MediaId: Media?], replaceRemoteContactCount: Int32?, replaceContactPeerIds: Set<PeerId>?, currentPendingMessageActionsOperations: [PendingMessageActionsOperation], currentUpdatedMessageActionsSummaries: [PendingMessageActionsSummaryKey: Int32], currentUpdatedMessageTagSummaries: [MessageHistoryTagsSummaryKey: MessageHistoryTagNamespaceSummary], currentInvalidateMessageTagSummaries: [InvalidatedMessageHistoryTagsSummaryEntryOperation], currentUpdatedPendingPeerNotificationSettings: Set<PeerId>, replacedAdditionalChatListItems: [PeerId]?, updatedNoticeEntryKeys: Set<NoticeEntryKey>, updatedCacheEntryKeys: Set<ItemCacheEntryId>, currentUpdatedMasterClientId: Int64?, updatedFailedMessagePeerIds: Set<PeerId>, updatedFailedMessageIds: Set<MessageId>) {
|
||||
self.currentUpdatedState = currentUpdatedState
|
||||
self.currentPeerHoleOperations = currentPeerHoleOperations
|
||||
self.currentOperationsByPeerId = currentOperationsByPeerId
|
||||
|
@ -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] {
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user