[WIP] Topics

This commit is contained in:
Ali 2022-10-01 00:08:07 +02:00
parent 012aacf202
commit b5a59ba488
43 changed files with 1274 additions and 407 deletions

View File

@ -36,7 +36,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|> mapToSignal { view -> Signal<[INMessage], NoError> in
var signals: [Signal<[INMessage], NoError>] = []
for entry in view.0.entries {
if case let .MessageEntry(index, _, readState, isMuted, _, _, _, _, _, _) = entry {
if case let .MessageEntry(index, _, readState, isMuted, _, _, _, _, _, _, _) = entry {
if index.messageIndex.id.peerId.namespace != Namespaces.Peer.CloudUser {
continue
}

View File

@ -744,10 +744,8 @@ public protocol SharedAccountContext: AnyObject {
func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void)
#if ENABLE_WALLET
func openWallet(context: AccountContext, walletContext: OpenWalletContext, present: @escaping (ViewController) -> Void)
#endif
func openImagePicker(context: AccountContext, completion: @escaping (UIImage) -> Void, present: @escaping (ViewController) -> Void)
func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable)
func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController

View File

@ -23,6 +23,7 @@ public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
public enum AvatarNodeClipStyle {
case none
case round
case roundedRect
}
private class AvatarNodeParameters: NSObject {
@ -330,7 +331,7 @@ public final class AvatarNode: ASDisplayNode {
let account = account ?? genericContext.account
if let peer = peer, let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, round: clipStyle == .round, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
if let peer = peer, let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
self.contents = nil
self.displaySuspended = true
self.imageReady.set(self.imageNode.contentReady)
@ -432,6 +433,10 @@ public final class AvatarNode: ASDisplayNode {
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height:
bounds.size.height))
context.clip()
} else if case .roundedRect = parameters.clipStyle {
context.beginPath()
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height: bounds.size.height), cornerRadius: floor(bounds.size.width * 0.25)).cgPath)
context.clip()
}
if let explicitColorIndex = parameters.explicitColorIndex {

View File

@ -86,7 +86,7 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal<UIImage?, NoError> {
let iconSignal: Signal<UIImage?, NoError>
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, round: round, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, clipStyle: round ? .round : .none, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
@ -131,7 +131,7 @@ public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, size: CG
return iconSignal
}
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
return imageData
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
@ -145,8 +145,16 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.copy)
if round && displayDimensions.width != 60.0 {
context.addEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
switch clipStyle {
case .none:
break
case .round:
if displayDimensions.width != 60.0 {
context.addEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
context.clip()
}
case .roundedRect:
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: floor(displayDimensions.width * 0.25)).cgPath)
context.clip()
}
@ -186,30 +194,46 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
context.fill(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
}
if round {
switch clipStyle {
case .none:
break
case .round:
if displayDimensions.width == 60.0 {
context.setBlendMode(.destinationOut)
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
case .roundedRect:
break
}
} else {
if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
if round {
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else {
switch clipStyle {
case .none:
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .round:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .roundedRect:
context.beginPath()
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: floor(displayDimensions.width * 0.25)).cgPath)
context.fillPath()
}
}
}
} else if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
if round {
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else {
switch clipStyle {
case .none:
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .round:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .roundedRect:
context.beginPath()
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: floor(displayDimensions.width * 0.25)).cgPath)
context.fillPath()
}
}
})
@ -232,10 +256,15 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
} else if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
if round {
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else {
switch clipStyle {
case .none:
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .round:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .roundedRect:
context.beginPath()
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: floor(displayDimensions.width * 0.25)).cgPath)
context.fillPath()
}
}
})

View File

@ -78,6 +78,7 @@ swift_library(
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
"//submodules/TelegramUI/Components/EntityKeyboard",
"//submodules/AnimationUI:AnimationUI",
],
visibility = [
"//visibility:public",

View File

@ -36,6 +36,7 @@ import MultiAnimationRenderer
import EmojiStatusSelectionComponent
import EntityKeyboard
import TelegramStringFormatting
import AnimationUI
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
if listNode.scroller.isDragging {
@ -108,6 +109,161 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
}
}
private final class MoreHeaderButton: HighlightableButtonNode {
enum Content {
case image(UIImage?)
case more(UIImage?)
}
let referenceNode: ContextReferenceContentNode
let containerNode: ContextControllerSourceNode
private let iconNode: ASImageNode
private var animationNode: AnimationNode?
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
private var color: UIColor
init(color: UIColor) {
self.color = color
self.referenceNode = ContextReferenceContentNode()
self.containerNode = ContextControllerSourceNode()
self.containerNode.animateScale = false
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.iconNode.contentMode = .scaleToFill
super.init()
self.containerNode.addSubnode(self.referenceNode)
self.referenceNode.addSubnode(self.iconNode)
self.addSubnode(self.containerNode)
self.containerNode.shouldBegin = { [weak self] location in
guard let strongSelf = self, let _ = strongSelf.contextAction else {
return false
}
return true
}
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else {
return
}
strongSelf.contextAction?(strongSelf.containerNode, gesture)
}
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0))
self.referenceNode.frame = self.containerNode.bounds
self.iconNode.image = optionsCircleImage(color: color)
if let image = self.iconNode.image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
}
self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0)
}
private var content: Content?
func setContent(_ content: Content, animated: Bool = false) {
if case .more = content, self.animationNode == nil {
let iconColor = self.color
let animationNode = AnimationNode(animation: "anim_profilemore", colors: ["Point 2.Group 1.Fill 1": iconColor,
"Point 3.Group 1.Fill 1": iconColor,
"Point 1.Group 1.Fill 1": iconColor], scale: 1.0)
let animationSize = CGSize(width: 22.0, height: 22.0)
animationNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - animationSize.width) / 2.0), y: floor((self.containerNode.bounds.height - animationSize.height) / 2.0)), size: animationSize)
self.addSubnode(animationNode)
self.animationNode = animationNode
}
if animated {
if let snapshotView = self.referenceNode.view.snapshotContentTree() {
snapshotView.frame = self.referenceNode.frame
self.view.addSubview(snapshotView)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3)
self.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.animationNode?.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3)
}
switch content {
case let .image(image):
if let image = image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
}
self.iconNode.image = image
self.iconNode.isHidden = false
self.animationNode?.isHidden = true
case let .more(image):
if let image = image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
}
self.iconNode.image = image
self.iconNode.isHidden = false
self.animationNode?.isHidden = false
}
} else {
self.content = content
switch content {
case let .image(image):
if let image = image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
}
self.iconNode.image = image
self.iconNode.isHidden = false
self.animationNode?.isHidden = true
case let .more(image):
if let image = image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
}
self.iconNode.image = image
self.iconNode.isHidden = false
self.animationNode?.isHidden = false
}
}
}
override func didLoad() {
super.didLoad()
self.view.isOpaque = false
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 22.0, height: 44.0)
}
func onLayout() {
}
func play() {
self.animationNode?.playOnce()
}
}
private func optionsCircleImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(color.cgColor)
let lineWidth: CGFloat = 1.3
context.setLineWidth(lineWidth)
context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth, dy: lineWidth))
})
}
public class ChatListControllerImpl: TelegramBaseController, ChatListController {
private var validLayout: ContainerViewLayout?
@ -174,8 +330,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private weak var emojiStatusSelectionController: ViewController?
private let moreBarButton: MoreHeaderButton
private let moreBarButtonItem: UIBarButtonItem
private var forumChannelTracker: ForumChannelTopics?
private let selectAddMemberDisposable = MetaDisposable()
private let addMemberDisposable = MetaDisposable()
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
if self.isNodeLoaded {
self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition)
@ -204,6 +365,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
animationRenderer: self.animationRenderer
)
self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor)
self.moreBarButton.isUserInteractionEnabled = true
self.moreBarButton.setContent(.more(optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor)))
self.moreBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
self.tabContainerNode = ChatListFilterTabContainerNode()
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: .all)
@ -225,6 +392,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
title = "Forum"
self.forumChannelTracker = ForumChannelTopics(account: self.context.account, peerId: peerId)
self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in
self?.openMoreMenu(sourceNode: sourceNode, gesture: gesture)
}
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
}
self.titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil)
@ -265,9 +437,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
self.navigationItem.backBarButtonItem = backBarButtonItem
} else {
let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit
self.navigationItem.rightBarButtonItem = rightBarButtonItem
switch self.location {
case .chatList:
let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit
self.navigationItem.rightBarButtonItem = rightBarButtonItem
case .forum:
self.navigationItem.rightBarButtonItem = self.moreBarButtonItem
}
let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
self.navigationItem.backBarButtonItem = backBarButtonItem
@ -454,9 +632,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
} else {
let editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(strongSelf.editPressed))
editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Edit
strongSelf.navigationItem.setRightBarButton(editItem, animated: true)
switch strongSelf.location {
case .chatList:
let editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(strongSelf.editPressed))
editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Edit
strongSelf.navigationItem.setRightBarButton(editItem, animated: true)
case .forum:
strongSelf.navigationItem.setRightBarButton(strongSelf.moreBarButtonItem, animated: true)
}
}
let (hasProxy, connectsViaProxy) = proxy
@ -865,6 +1048,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
self.filterDisposable.dispose()
self.featuredFiltersDisposable.dispose()
self.activeDownloadsDisposable?.dispose()
self.selectAddMemberDisposable.dispose()
self.addMemberDisposable.dispose()
}
private func openStatusSetup(sourceView: UIView) {
@ -932,8 +1117,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
editItem.accessibilityLabel = self.presentationData.strings.Common_Done
} else {
editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
editItem.accessibilityLabel = self.presentationData.strings.Common_Edit
switch self.location {
case .chatList:
editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
editItem.accessibilityLabel = self.presentationData.strings.Common_Edit
case .forum:
editItem = self.moreBarButtonItem
}
}
if case .chatList(.root) = self.location {
self.navigationItem.leftBarButtonItem = editItem
@ -1258,7 +1448,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
var joined = false
if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first {
if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first {
for media in message.media {
if let action = media as? TelegramMediaAction, action.action == .peerJoined {
joined = true
@ -1272,7 +1462,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
chatListController.navigationPresentation = .master
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: archiveContextMenuItems(context: strongSelf.context, groupId: groupId._asGroup(), chatListController: strongSelf) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _):
let source: ContextContentSource
if let location = location {
source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location))
@ -2020,11 +2210,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
@objc private func editPressed() {
if case .forum = self.location {
self.forumChannelTracker?.createTopic(title: "Topic#\(Int.random(in: 0 ..< 100000))")
return
}
let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
editItem.accessibilityLabel = self.presentationData.strings.Common_Done
if case .chatList(.root) = self.location {
@ -2125,6 +2310,83 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
@objc private func moreButtonPressed() {
self.moreBarButton.play()
self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
}
private func openMoreMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
guard case let .forum(peerId) = self.location else {
return
}
var items: [ContextMenuItem] = []
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Group Info", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateGroupActionIcon"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
)
|> deliverOnMainQueue).start(next: { peer in
guard let strongSelf = self, let peer = peer, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else {
return
}
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
})
})))
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Add Member", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddUser"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in
f(.default)
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
guard let strongSelf = self, let peer = peer else {
return
}
strongSelf.context.sharedContext.openAddPeerMembers(context: strongSelf.context, updatedPresentationData: nil, parentController: strongSelf, groupPeer: peer._asPeer(), selectAddMemberDisposable: strongSelf.selectAddMemberDisposable, addMemberDisposable: strongSelf.addMemberDisposable)
})
})))
items.append(.separator)
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "New Topic", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] action in
action.dismissWithResult(.default)
guard let strongSelf = self, let forumChannelTracker = strongSelf.forumChannelTracker else {
return
}
let _ = (forumChannelTracker.createTopic(title: "Topic#\(Int.random(in: 0 ..< 100000))")
|> deliverOnMainQueue).start(next: { [weak self] topicId in
guard let strongSelf = self else {
return
}
let _ = enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: [.message(text: "First Message", attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(topicId)), localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start()
})
})))
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(HeaderContextReferenceContentSource(controller: self, sourceNode: self.moreBarButton.referenceNode)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.presentInGlobalOverlay(contextController)
}
private var initializedFilters = false
private func reloadFilters(firstUpdate: (() -> Void)? = nil) {
let filterItems = chatListFilterItems(context: self.context)
@ -3606,3 +3868,17 @@ private final class ChatListContextLocationContentSource: ContextLocationContent
return ContextControllerLocationViewInfo(location: self.location, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceNode: ContextReferenceContentNode
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -213,7 +213,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
)
let readState = EnginePeerReadCounters()
return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumThreadTitle: nil), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
}
var itemNodes: [ChatListItemNode] = []

View File

@ -694,7 +694,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
if isMedia {
return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(id: peer.peerId), interaction: listInteraction, message: message._asMessage(), selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: key == .downloads ? header : nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: key != .downloads, isDownloadList: key == .downloads)
} else {
return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)), content: .peer(messages: [message], peer: peer, threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)), content: .peer(messages: [message], peer: peer, threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumThreadTitle: nil), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
}
case let .addContact(phoneNumber, theme, strings):
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
@ -1752,7 +1752,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return
}
switch item.content {
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _):
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
if let peer = peer.peer, let message = messages.first {
peerContextAction(peer, .search(message.id), node, gesture, location)
}
@ -2794,7 +2794,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
bounds = selectedItemNode.bounds
}
switch item.content {
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _):
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId)
case let .groupReference(groupId, _, _, _, _):
return (selectedItemNode.view, bounds, groupId)
@ -2982,7 +2982,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
associatedMedia: [:]
)
let readState = EnginePeerReadCounters()
return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
return ChatListItem(presentationData: chatListPresentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: 0, messageIndex: EngineMessage.Index(id: EngineMessage.Id(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1))), content: .peer(messages: [message], peer: EngineRenderedPeer(peer: peer1), threadInfo: nil, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false, forumThreadTitle: nil), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
case .media:
return nil
case .links:

View File

@ -49,12 +49,12 @@ public enum ChatListItemContent {
}
}
case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThreads.Info?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool)
case peer(messages: [EngineMessage], peer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThread.Info?, combinedReadState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, draftState: DraftState?, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, ignoreUnreadBadge: Bool, displayAsMessage: Bool, hasFailedMessages: Bool, forumThreadTitle: String?)
case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool)
public var chatLocation: ChatLocation? {
switch self {
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _):
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
return .peer(id: peer.peerId)
case .groupReference:
return nil
@ -158,7 +158,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
public func selected(listView: ListView) {
switch self.content {
case let .peer(messages, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
case let .peer(messages, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _):
if let message = messages.last, let peer = peer.peer {
var threadId: Int64?
if case let .forum(_, threadIdValue, _, _) = self.index {
@ -539,7 +539,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
result += "\n\(item.presentationData.strings.VoiceOver_Chat_UnreadMessages(Int32(allCount)))"
}
return result
case let .peer(_, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _):
case let .peer(_, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _):
guard let chatMainPeer = peer.chatMainPeer else {
return nil
}
@ -599,7 +599,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else {
return item.presentationData.strings.VoiceOver_ChatList_MessageEmpty
}
case let .peer(messages, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _):
case let .peer(messages, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _):
if let message = messages.last {
var result = ""
if message.flags.contains(.Incoming) {
@ -792,7 +792,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
var displayAsMessage = false
var enablePreview = true
switch item.content {
case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _):
case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _, _):
displayAsMessage = displayAsMessageValue
if displayAsMessage, case let .user(author) = messages.last?.author {
peer = .user(author)
@ -819,7 +819,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
} else if peer.isDeleted {
overrideImage = .deletedIcon
}
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
var isForum = false
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
isForum = true
}
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: isForum ? .roundedRect : .round, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
if peer.isPremium && peer.id != item.context.account.peerId {
let context = item.context
@ -958,7 +962,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
guard let item = self.item, item.editing else {
return
}
if case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _) = item.content {
if case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _) = item.content {
if promoInfo == nil, let mainPeer = peer.peer {
item.interaction.togglePeerSelected(mainPeer)
}
@ -1007,12 +1011,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let promoInfo: ChatListNodeEntryPromoInfo?
let displayAsMessage: Bool
let hasFailedMessages: Bool
var threadInfo: EngineMessageHistoryThreads.Info?
var threadInfo: EngineMessageHistoryThread.Info?
var forumThreadTitle: String?
var groupHiddenByDefault = false
switch item.content {
case let .peer(messagesValue, peerValue, threadInfoValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _):
case let .peer(messagesValue, peerValue, threadInfoValue, combinedReadStateValue, isRemovedFromTotalUnreadCountValue, peerPresenceValue, hasUnseenMentionsValue, hasUnseenReactionsValue, draftStateValue, inputActivitiesValue, promoInfoValue, ignoreUnreadBadge, displayAsMessageValue, _, forumThreadTitleValue):
messages = messagesValue
contentPeer = .chat(peerValue)
combinedReadState = combinedReadStateValue
@ -1033,6 +1038,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
threadInfo = threadInfoValue
hasUnseenMentions = hasUnseenMentionsValue
hasUnseenReactions = hasUnseenReactionsValue
forumThreadTitle = forumThreadTitleValue
switch peerValue.peer {
case .user, .secretChat:
@ -1144,7 +1150,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let leftInset: CGFloat = params.leftInset + avatarLeftInset
enum ContentData {
case chat(itemPeer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThreads.Info?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?)
case chat(itemPeer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThread.Info?, peer: EnginePeer?, hideAuthor: Bool, messageText: String, spoilers: [NSRange]?, customEmojiRanges: [(NSRange, ChatTextInputTextCustomEmojiAttribute)]?)
case group(peers: [EngineChatList.GroupItem.Item])
}
@ -1231,6 +1237,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
}
}
if let forumThreadTitle = forumThreadTitle, let peerTextValue = peerText {
peerText = "\(peerTextValue)\(forumThreadTitle)"
}
let messageText: String
if let currentChatListText = currentChatListText, currentChatListText.0 == text {
@ -1451,7 +1461,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
switch item.content {
case let .groupReference(_, _, message, _, _):
topIndex = message?.index
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
topIndex = messages.first?.index
}
if let topIndex {
@ -1588,7 +1598,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if !isPeerGroup && !isAccountPeer {
if displayAsMessage {
switch item.content {
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if let peer = messages.last?.author {
if case let .user(user) = peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
currentCredibilityIconContent = .animation(content: .customEmoji(fileId: emojiStatus.fileId), size: CGSize(width: 32.0, height: 32.0), placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, themeColor: item.presentationData.theme.list.itemAccentColor, loopMode: .count(2))
@ -1705,7 +1715,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let peerRevealOptions: [ItemListRevealOption]
let peerLeftRevealOptions: [ItemListRevealOption]
switch item.content {
case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _):
case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _, _):
if !displayAsMessage {
if case let .user(peer) = renderedPeer.chatMainPeer, let presence = presence, !isServicePeer(peer) && !peer.flags.contains(.isSupport) && peer.id != item.context.account.peerId {
let updatedPresence = EnginePeer.Presence(status: presence.status, lastActivity: 0)
@ -2547,7 +2557,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
close = false
case RevealOptionKey.delete.rawValue:
var joined = false
if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first {
if case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content, let message = messages.first {
for media in message.media {
if let action = media as? TelegramMediaAction, action.action == .peerJoined {
joined = true
@ -2583,7 +2593,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
item.interaction.toggleArchivedFolderHiddenByDefault()
close = false
case RevealOptionKey.hidePsa.rawValue:
if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _) = item.content {
if let item = self.item, case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _) = item.content {
item.interaction.hidePsa(peer.peerId)
}
close = false

View File

@ -242,7 +242,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction.additionalCategorySelected(id)
}
), directionHint: entry.directionHint)
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact):
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumThreadTitle):
switch mode {
case .chatList:
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
@ -265,7 +265,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
promoInfo: promoInfo,
ignoreUnreadBadge: false,
displayAsMessage: false,
hasFailedMessages: hasFailedMessages
hasFailedMessages: hasFailedMessages,
forumThreadTitle: forumThreadTitle
),
editing: editing,
hasActiveRevealControls: hasActiveRevealControls,
@ -432,7 +433,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
return entries.map { entry -> ListViewUpdateItem in
switch entry.entry {
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact):
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumThreadTitle):
switch mode {
case .chatList:
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
@ -455,7 +456,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
promoInfo: promoInfo,
ignoreUnreadBadge: false,
displayAsMessage: false,
hasFailedMessages: hasFailedMessages
hasFailedMessages: hasFailedMessages,
forumThreadTitle: forumThreadTitle
),
editing: editing,
hasActiveRevealControls: hasActiveRevealControls,
@ -1053,7 +1055,7 @@ public final class ChatListNode: ListView {
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode)
let entries = rawEntries.filter { entry in
switch entry {
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _):
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _):
switch mode {
case .chatList:
return true
@ -1225,7 +1227,7 @@ public final class ChatListNode: ListView {
var didIncludeHiddenByDefaultArchive = false
if let previous = previousView {
for entry in previous.filteredEntries {
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
if case let .chatList(chatListIndex) = index {
if chatListIndex.pinningIndex != nil {
previousPinnedChats.append(chatListIndex.messageIndex.id.peerId)
@ -1243,7 +1245,7 @@ public final class ChatListNode: ListView {
var doesIncludeArchive = false
var doesIncludeHiddenByDefaultArchive = false
for entry in processedView.filteredEntries {
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
if case let .chatList(index) = index, index.pinningIndex != nil {
updatedPinnedChats.append(index.messageIndex.id.peerId)
}
@ -1507,7 +1509,7 @@ public final class ChatListNode: ListView {
var referenceId: EngineChatList.PinnedItem.Id?
var beforeAll = false
switch toEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _):
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
if promoInfo != nil {
beforeAll = true
} else {
@ -1534,7 +1536,7 @@ public final class ChatListNode: ListView {
var itemId: EngineChatList.PinnedItem.Id?
switch fromEntry {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if case let .chatList(index) = index {
itemId = .peer(index.messageIndex.id.peerId)
}
@ -1785,7 +1787,7 @@ public final class ChatListNode: ListView {
if !transition.chatListView.originalList.hasLater {
for entry in filteredEntries.reversed() {
switch entry {
case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _):
case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
if promoInfo == nil {
var hasUnread = false
if let combinedReadState = combinedReadState {
@ -1908,7 +1910,7 @@ public final class ChatListNode: ListView {
for item in transition.insertItems {
if let item = item.item as? ChatListItem {
switch item.content {
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _):
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
insertedPeerIds.append(peer.peerId)
case .groupReference:
break
@ -2084,7 +2086,7 @@ public final class ChatListNode: ListView {
continue
}
switch chatListView.filteredEntries[entryCount - i - 1] {
case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _):
case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _):
if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peer.peerId) {
current = (index, peer.peer!, entryCount - i - 1)
break outer
@ -2131,10 +2133,10 @@ public final class ChatListNode: ListView {
case .previous(unread: false), .next(unread: false):
var target: (EngineChatList.Item.Index, EnginePeer)? = nil
if let current = current, entryCount > 1 {
if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] {
if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] {
next = (index, peer.peer!)
}
if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] {
if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] {
previous = (index, peer.peer!)
}
if case .previous = option {
@ -2143,7 +2145,7 @@ public final class ChatListNode: ListView {
target = next
}
} else if entryCount > 0 {
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] {
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] {
target = (index, peer.peer!)
}
}
@ -2221,7 +2223,7 @@ public final class ChatListNode: ListView {
continue
}
switch chatListView.filteredEntries[entryCount - i - 1] {
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return index
default:
break
@ -2237,7 +2239,7 @@ public final class ChatListNode: ListView {
if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) {
if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item {
switch item.content {
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _):
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
resultPeer = peer.peer
default:
break

View File

@ -47,7 +47,27 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
enum ChatListNodeEntry: Comparable, Identifiable {
case HeaderEntry
case PeerEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, messages: [EngineMessage], readState: EnginePeerReadCounters?, isRemovedFromTotalUnreadCount: Bool, draftState: ChatListItemContent.DraftState?, peer: EngineRenderedPeer, threadInfo: EngineMessageHistoryThreads.Info?, presence: EnginePeer.Presence?, hasUnseenMentions: Bool, hasUnseenReactions: Bool, editing: Bool, hasActiveRevealControls: Bool, selected: Bool, inputActivities: [(EnginePeer, PeerInputActivity)]?, promoInfo: ChatListNodeEntryPromoInfo?, hasFailedMessages: Bool, isContact: Bool)
case PeerEntry(
index: EngineChatList.Item.Index,
presentationData: ChatListPresentationData,
messages: [EngineMessage],
readState: EnginePeerReadCounters?,
isRemovedFromTotalUnreadCount: Bool,
draftState: ChatListItemContent.DraftState?,
peer: EngineRenderedPeer,
threadInfo: EngineMessageHistoryThread.Info?,
presence: EnginePeer.Presence?,
hasUnseenMentions: Bool,
hasUnseenReactions: Bool,
editing: Bool,
hasActiveRevealControls: Bool,
selected: Bool,
inputActivities: [(EnginePeer, PeerInputActivity)]?,
promoInfo: ChatListNodeEntryPromoInfo?,
hasFailedMessages: Bool,
isContact: Bool,
forumThreadTitle: String?
)
case HoleEntry(EngineMessage.Index, theme: PresentationTheme)
case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool)
case ArchiveIntro(presentationData: ChatListPresentationData)
@ -57,7 +77,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
switch self {
case .HeaderEntry:
return .index(.chatList(.absoluteUpperBound))
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return .index(index)
case let .HoleEntry(holeIndex, _):
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
@ -74,7 +94,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
switch self {
case .HeaderEntry:
return .Header
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
switch index {
case let .chatList(index):
return .PeerId(index.messageIndex.id.peerId.toInt64())
@ -104,9 +124,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
return false
}
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact):
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact, lhsForumThreadTitle):
switch rhs {
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact):
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact, rhsForumThreadTitle):
if lhsIndex != rhsIndex {
return false
}
@ -201,6 +221,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
if lhsIsContact != rhsIsContact {
return false
}
if lhsForumThreadTitle != rhsForumThreadTitle {
return false
}
return true
default:
return false
@ -355,7 +378,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
inputActivities = state.peerInputActivities?.activities[peerId]
}
result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact))
result.append(.PeerEntry(index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset), presentationData: state.presentationData, messages: updatedMessages, readState: updatedCombinedReadState, isRemovedFromTotalUnreadCount: entry.isMuted, draftState: draftState, peer: entry.renderedPeer, threadInfo: entry.threadInfo, presence: entry.presence, hasUnseenMentions: entry.hasUnseenMentions, hasUnseenReactions: entry.hasUnseenReactions, editing: state.editing, hasActiveRevealControls: hasActiveRevealControls, selected: isSelected, inputActivities: inputActivities, promoInfo: nil, hasFailedMessages: entry.hasFailed, isContact: entry.isContact, forumThreadTitle: entry.forumTopicTitle))
}
if !view.hasLater {
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
@ -388,7 +411,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
inputActivities: nil,
promoInfo: nil,
hasFailedMessages: false,
isContact: false
isContact: false,
forumThreadTitle: nil
))
if foundPinningIndex != 0 {
foundPinningIndex -= 1
@ -396,7 +420,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
}
}
result.append(.PeerEntry(index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false))
result.append(.PeerEntry(index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor), presentationData: state.presentationData, messages: [], readState: nil, isRemovedFromTotalUnreadCount: false, draftState: nil, peer: EngineRenderedPeer(peerId: savedMessagesPeer.id, peers: [savedMessagesPeer.id: savedMessagesPeer], associatedMedia: [:]), threadInfo: nil, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, editing: state.editing, hasActiveRevealControls: false, selected: state.selectedPeerIds.contains(savedMessagesPeer.id), inputActivities: nil, promoInfo: nil, hasFailedMessages: false, isContact: false, forumThreadTitle: nil))
} else {
if !filteredAdditionalItemEntries.isEmpty {
for item in filteredAdditionalItemEntries.reversed() {
@ -434,7 +458,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
inputActivities: state.peerInputActivities?.activities[peerId],
promoInfo: promoInfo,
hasFailedMessages: item.item.hasFailed,
isContact: item.item.isContact
isContact: item.item.isContact,
forumThreadTitle: item.item.forumTopicTitle
))
if pinningIndex != 0 {
pinningIndex -= 1

View File

@ -179,21 +179,22 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
guard let peer = view.peer else {
continue
}
guard let info = item.info.get(EngineMessageHistoryThreads.Info.self) else {
guard let data = item.info.get(MessageHistoryThreadData.self) else {
continue
}
items.append(EngineChatList.Item(
id: .forum(item.id),
index: .forum(timestamp: item.index.timestamp, threadId: item.id, namespace: item.index.id.namespace, id: item.index.id.id),
messages: item.topMessage.flatMap { [EngineMessage($0)] } ?? [],
readCounters: nil,
readCounters: EnginePeerReadCounters(state: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, .idBased(maxIncomingReadId: 1, maxOutgoingReadId: 1, maxKnownId: 1, count: data.incomingUnreadCount, markedUnread: false))])),
isMuted: false,
draft: nil,
threadInfo: info,
threadInfo: data.info,
renderedPeer: EngineRenderedPeer(peer: EnginePeer(peer)),
presence: nil,
hasUnseenMentions: false,
hasUnseenReactions: false,
forumTopicTitle: nil,
hasFailed: false,
isContact: false
))

View File

@ -1265,7 +1265,7 @@ public final class ContactListNode: ASDisplayNode {
return context.engine.data.get(EngineDataMap(
view.entries.compactMap { entry -> EnginePeer.Id? in
switch entry {
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _):
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _):
if let peer = renderedPeer.peer {
if let channel = peer as? TelegramChannel, case .group = channel.info {
return peer.id
@ -1281,7 +1281,7 @@ public final class ContactListNode: ASDisplayNode {
var peers: [(EnginePeer, Int32)] = []
for entry in view.entries {
switch entry {
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _):
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _):
if let peer = renderedPeer.peer {
if peer is TelegramGroup {
peers.append((EnginePeer(peer), 0))

View File

@ -101,12 +101,12 @@ public struct ChatListGroupReferenceEntry: Equatable {
}
public enum ChatListEntry: Comparable {
case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], hasFailed: Bool, isContact: Bool)
case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: CodableEntry?, hasFailed: Bool, isContact: Bool)
case HoleEntry(ChatListHole)
public var index: ChatListIndex {
switch self {
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _):
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
return index
case let .HoleEntry(hole):
return ChatListIndex(pinningIndex: nil, messageIndex: hole.index)
@ -115,9 +115,9 @@ public enum ChatListEntry: Comparable {
public static func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool {
switch lhs {
case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsHasFailed, lhsIsContact):
case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsForumTopicData, lhsHasFailed, lhsIsContact):
switch rhs {
case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsHasFailed, rhsIsContact):
case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsForumTopicData, rhsHasFailed, rhsIsContact):
if lhsIndex != rhsIndex {
return false
}
@ -155,6 +155,9 @@ public enum ChatListEntry: Comparable {
if lhsInfo != rhsInfo {
return false
}
if lhsForumTopicData != rhsForumTopicData {
return false
}
if lhsHasFailed != rhsHasFailed {
return false
}
@ -179,26 +182,9 @@ public enum ChatListEntry: Comparable {
}
}
/*private func processedChatListEntry(_ entry: MutableChatListEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) -> MutableChatListEntry {
switch entry {
case let .IntermediateMessageEntry(index, messageIndex):
var updatedMessage = message
if let message = message, let cachedData = cachedDataTable.get(message.id.peerId), let associatedHistoryMessageId = cachedData.associatedHistoryMessageId, message.id.id == 1 {
if let messageIndex = messageHistoryTable.messageHistoryIndexTable.earlierEntries(id: associatedHistoryMessageId, count: 1).first {
if let associatedMessage = messageHistoryTable.getMessage(messageIndex) {
updatedMessage = associatedMessage
}
}
}
return .IntermediateMessageEntry(index, updatedMessage, readState, embeddedState)
default:
return entry
}
}*/
enum MutableChatListEntry: Equatable {
case IntermediateMessageEntry(index: ChatListIndex, messageIndex: MessageIndex?)
case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], hasFailedMessages: Bool, isContact: Bool)
case MessageEntry(index: ChatListIndex, messages: [Message], readState: CombinedPeerReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: CodableEntry?, hasFailedMessages: Bool, isContact: Bool)
case HoleEntry(ChatListHole)
init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) {
@ -214,7 +200,7 @@ enum MutableChatListEntry: Equatable {
switch self {
case let .IntermediateMessageEntry(index, _):
return index
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
return index
case let .HoleEntry(hole):
return ChatListIndex(pinningIndex: nil, messageIndex: hole.index)
@ -655,7 +641,12 @@ final class MutableChatListView {
}
}
return .MessageEntry(index: index, messages: renderedMessages, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers)), presence: presence, tagSummaryInfo: [:], hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact)
var forumTopicData: CodableEntry?
if let message = renderedMessages.first, let threadId = message.threadId {
forumTopicData = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId)
}
return .MessageEntry(index: index, messages: renderedMessages, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: RenderedPeer(peerId: index.messageIndex.id.peerId, peers: peers, associatedMedia: renderAssociatedMediaForPeers(postbox: postbox, peers: peers)), presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact)
default:
return nil
}
@ -684,8 +675,8 @@ public final class ChatListView {
var entries: [ChatListEntry] = []
for entry in mutableView.sampledState.entries {
switch entry {
case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, hasFailed: hasFailed, isContact: isContact))
case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, hasFailed, isContact):
entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, hasFailed: hasFailed, isContact: isContact))
case let .HoleEntry(hole):
entries.append(.HoleEntry(hole))
case .IntermediateMessageEntry:
@ -702,9 +693,9 @@ public final class ChatListView {
var additionalItemEntries: [ChatListAdditionalItemEntry] = []
for entry in mutableView.additionalItemEntries {
switch entry.entry {
case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, isContact):
case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, hasFailed, isContact):
additionalItemEntries.append(ChatListAdditionalItemEntry(
entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, hasFailed: hasFailed, isContact: isContact),
entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, hasFailed: hasFailed, isContact: isContact),
info: entry.info
))
case .HoleEntry:

View File

@ -502,7 +502,7 @@ private final class ChatListViewSpaceState {
let entryPeer: Peer
let entryNotificationsPeerId: PeerId
switch entry {
case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _):
case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _, _):
if let peer = renderedPeer.peer {
entryPeer = peer
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
@ -604,13 +604,13 @@ private final class ChatListViewSpaceState {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, hasFailedMessages, isContact):
case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact):
if let peer = renderedPeer.peer {
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
if let (_, updated) = transaction.currentUpdatedPeerNotificationSettings[notificationsPeerId] {
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: updated)
return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, hasFailedMessages: hasFailedMessages, isContact: isContact)
return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, hasFailedMessages: hasFailedMessages, isContact: isContact)
} else {
return nil
}
@ -628,7 +628,7 @@ private final class ChatListViewSpaceState {
if !transaction.updatedFailedMessagePeerIds.isEmpty {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, _, isContact):
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, _, isContact):
if transaction.updatedFailedMessagePeerIds.contains(index.messageIndex.id.peerId) {
return .MessageEntry(
index: index,
@ -640,6 +640,7 @@ private final class ChatListViewSpaceState {
renderedPeer: renderedPeer,
presence: presence,
tagSummaryInfo: tagSummaryInfo,
forumTopicData: forumTopicData,
hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId),
isContact: isContact
)
@ -657,7 +658,7 @@ private final class ChatListViewSpaceState {
if !transaction.currentUpdatedPeers.isEmpty {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, hasFailedMessages, isContact):
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact):
var updatedMessages: [Message] = messages
var hasUpdatedMessages = false
for i in 0 ..< updatedMessages.count {
@ -679,6 +680,7 @@ private final class ChatListViewSpaceState {
renderedPeer: renderedPeer ?? entryRenderedPeer,
presence: presence,
tagSummaryInfo: tagSummaryInfo,
forumTopicData: forumTopicData,
hasFailedMessages: hasFailedMessages,
isContact: isContact)
} else {
@ -695,7 +697,7 @@ private final class ChatListViewSpaceState {
if !transaction.currentUpdatedPeerPresences.isEmpty {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, hasFailedMessages, isContact):
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact):
var presencePeerId = entryRenderedPeer.peerId
if let peer = entryRenderedPeer.peers[entryRenderedPeer.peerId], let associatedPeerId = peer.associatedPeerId {
presencePeerId = associatedPeerId
@ -711,6 +713,7 @@ private final class ChatListViewSpaceState {
renderedPeer: entryRenderedPeer,
presence: presence,
tagSummaryInfo: tagSummaryInfo,
forumTopicData: forumTopicData,
hasFailedMessages: hasFailedMessages,
isContact: isContact
)
@ -731,7 +734,7 @@ private final class ChatListViewSpaceState {
let entryPeer: Peer
let entryNotificationsPeerId: PeerId
switch entry {
case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _):
case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _, _):
if let peer = entryRenderedPeer.peer {
entryPeer = peer
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
@ -842,7 +845,7 @@ private final class ChatListViewSpaceState {
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty {
if self.orderedEntries.mutableScan({ entry in
switch entry {
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, hasFailedMessages, isContact):
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailedMessages, isContact):
var updatedChatListMessageTagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = tagSummaryInfo
var didUpdateSummaryInfo = false
@ -883,6 +886,7 @@ private final class ChatListViewSpaceState {
renderedPeer: entryRenderedPeer,
presence: presence,
tagSummaryInfo: updatedChatListMessageTagSummaryInfo,
forumTopicData: forumTopicData,
hasFailedMessages: hasFailedMessages,
isContact: isContact
)
@ -1018,7 +1022,7 @@ private extension MutableChatListEntry {
switch self {
case let .IntermediateMessageEntry(index, _):
return index.messageIndex.id.peerId
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
return index.messageIndex.id.peerId
case .HoleEntry:
return nil
@ -1029,7 +1033,7 @@ private extension MutableChatListEntry {
switch self {
case let .IntermediateMessageEntry(index, _):
return MutableChatListEntryIndex(index: index, isMessage: true)
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
return MutableChatListEntryIndex(index: index, isMessage: true)
case let .HoleEntry(hole):
return MutableChatListEntryIndex(index: ChatListIndex(pinningIndex: nil, messageIndex: hole.index), isMessage: false)
@ -1040,7 +1044,7 @@ private extension MutableChatListEntry {
switch self {
case let .IntermediateMessageEntry(index, _):
return .peer(index.messageIndex.id.peerId)
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
return .peer(index.messageIndex.id.peerId)
case let .HoleEntry(hole):
return .hole(hole.index)
@ -1457,7 +1461,12 @@ struct ChatListViewState {
}
}
let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId))
var forumTopicData: CodableEntry?
if let message = renderedMessages.first, let threadId = message.threadId {
forumTopicData = postbox.messageHistoryThreadIndexTable.get(peerId: message.id.peerId, threadId: threadId)
}
let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: postbox.readStateTable.getCombinedState(index.messageIndex.id.peerId), notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId))
if directionIndex == 0 {
self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry)
} else {

View File

@ -590,7 +590,8 @@ final class MediaBoxPartialFile {
} else {
assertionFailure()
}
} catch {
} catch let e {
postboxLog("moveLocalFile error: \(e)")
assertionFailure()
}
}

View File

@ -129,3 +129,51 @@ public final class MessageHistoryThreadIndexView: PostboxView {
self.items = items
}
}
final class MutableMessageHistoryThreadInfoView: MutablePostboxView {
private let peerId: PeerId
private let threadId: Int64
fileprivate var info: CodableEntry?
init(postbox: PostboxImpl, peerId: PeerId, threadId: Int64) {
self.peerId = peerId
self.threadId = threadId
self.reload(postbox: postbox)
}
private func reload(postbox: PostboxImpl) {
self.info = postbox.messageHistoryThreadIndexTable.get(peerId: self.peerId, threadId: self.threadId)
}
func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool {
var updated = false
if transaction.updatedMessageThreadPeerIds.contains(self.peerId) {
let info = postbox.messageHistoryThreadIndexTable.get(peerId: self.peerId, threadId: self.threadId)
if self.info != info {
self.info = info
updated = true
}
}
return updated
}
func refreshDueToExternalTransaction(postbox: PostboxImpl) -> Bool {
return false
}
func immutableView() -> PostboxView {
return MessageHistoryThreadInfoView(self)
}
}
public final class MessageHistoryThreadInfoView: PostboxView {
public let info: CodableEntry?
init(_ view: MutableMessageHistoryThreadInfoView) {
self.info = view.info
}
}

View File

@ -110,6 +110,26 @@ class MessageHistoryThreadIndexTable: Table {
return self.lowerBound(peerId: peerId).successor
}
func get(peerId: PeerId, threadId: Int64) -> CodableEntry? {
if let updated = self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] {
return updated
} else {
if let itemIndex = self.reverseIndexTable.get(peerId: peerId, threadId: threadId) {
if let value = self.valueBox.get(self.table, key: self.key(peerId: itemIndex.id.peerId, timestamp: itemIndex.timestamp, threadId: threadId, namespace: itemIndex.id.namespace, id: itemIndex.id.id, key: self.sharedKey)) {
if value.length != 0 {
return CodableEntry(data: value.makeData())
} else {
return nil
}
} else {
return nil
}
} else {
return nil
}
}
}
func set(peerId: PeerId, threadId: Int64, info: CodableEntry) {
self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] = info
}

View File

@ -1164,6 +1164,11 @@ public final class Transaction {
return self.postbox!.messageHistoryThreadIndexTable.getAll(peerId: peerId)
}
public func getMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64) -> CodableEntry? {
assert(!self.disposed)
return self.postbox!.messageHistoryThreadIndexTable.get(peerId: peerId, threadId: threadId)
}
public func setMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64, info: CodableEntry) {
assert(!self.disposed)
self.postbox!.messageHistoryThreadIndexTable.set(peerId: peerId, threadId: threadId, info: info)

View File

@ -39,6 +39,7 @@ public enum PostboxViewKey: Hashable {
case chatListIndex(id: PeerId)
case peerTimeoutAttributes
case messageHistoryThreadIndex(id: PeerId)
case messageHistoryThreadInfo(peerId: PeerId, threadId: Int64)
public func hash(into hasher: inout Hasher) {
switch self {
@ -127,6 +128,9 @@ public enum PostboxViewKey: Hashable {
hasher.combine(17)
case let .messageHistoryThreadIndex(id):
hasher.combine(id)
case let .messageHistoryThreadInfo(peerId, threadId):
hasher.combine(peerId)
hasher.combine(threadId)
}
}
@ -360,6 +364,12 @@ public enum PostboxViewKey: Hashable {
} else {
return false
}
case let .messageHistoryThreadInfo(peerId, threadId):
if case .messageHistoryThreadInfo(peerId, threadId) = rhs {
return true
} else {
return false
}
}
}
}
@ -442,5 +452,7 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost
return MutablePeerTimeoutAttributesView(postbox: postbox)
case let .messageHistoryThreadIndex(id):
return MutableMessageHistoryThreadIndexView(postbox: postbox, peerId: id)
case let .messageHistoryThreadInfo(peerId, threadId):
return MutableMessageHistoryThreadInfoView(postbox: postbox, peerId: peerId, threadId: threadId)
}
}

View File

@ -282,7 +282,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
promoInfo: nil,
ignoreUnreadBadge: false,
displayAsMessage: false,
hasFailedMessages: false
hasFailedMessages: false,
forumThreadTitle: nil
),
editing: false,
hasActiveRevealControls: false,

View File

@ -902,7 +902,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
promoInfo: nil,
ignoreUnreadBadge: false,
displayAsMessage: false,
hasFailedMessages: false
hasFailedMessages: false,
forumThreadTitle: nil
),
editing: false,
hasActiveRevealControls: false,

View File

@ -425,7 +425,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
promoInfo: nil,
ignoreUnreadBadge: false,
displayAsMessage: false,
hasFailedMessages: false
hasFailedMessages: false,
forumThreadTitle: nil
),
editing: false,
hasActiveRevealControls: false,

View File

@ -991,7 +991,7 @@ public final class ShareController: ViewController {
var peers: [EngineRenderedPeer] = []
for entry in view.0.entries.reversed() {
switch entry {
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _):
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _):
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) {
peers.append(EngineRenderedPeer(renderedPeer))
}

View File

@ -117,6 +117,7 @@ enum AccountStateMutationOperation {
case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String)
case UpdateConfig
case UpdateExtendedMedia(MessageId, Api.MessageExtendedMedia)
case ResetForumTopic(topicId: MessageId, data: MessageHistoryThreadData, pts: Int32)
}
struct HoleFromPreviousState {
@ -151,6 +152,8 @@ struct AccountMutableState {
var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]]
var updatedOutgoingUniqueMessageIds: [Int64: Int32]
var resetForumTopicLists: [PeerId: [MessageHistoryThreadData]] = [:]
var storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>]
var displayAlerts: [(text: String, isDropAuth: Bool)] = []
var dismissBotWebViews: [Int64] = []
@ -228,6 +231,8 @@ struct AccountMutableState {
self.updatedOutgoingUniqueMessageIds.merge(other.updatedOutgoingUniqueMessageIds, uniquingKeysWith: { lhs, _ in lhs })
self.displayAlerts.append(contentsOf: other.displayAlerts)
self.dismissBotWebViews.append(contentsOf: other.dismissBotWebViews)
self.resetForumTopicLists.merge(other.resetForumTopicLists, uniquingKeysWith: { lhs, _ in lhs })
}
mutating func addPreCachedResource(_ resource: MediaResource, data: Data) {
@ -530,7 +535,7 @@ struct AccountMutableState {
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic:
break
case let .AddMessages(messages, location):
for message in messages {

View File

@ -3,8 +3,8 @@ import SwiftSignalKit
import Postbox
import TelegramApi
public final class EngineMessageHistoryThreads {
public final class Info: Equatable, Codable {
public extension EngineMessageHistoryThread {
final class Info: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case title
case icon
@ -45,6 +45,18 @@ public final class EngineMessageHistoryThreads {
}
}
public struct MessageHistoryThreadData: Codable {
public var info: EngineMessageHistoryThread.Info
public var incomingUnreadCount: Int32
public var maxIncomingReadId: Int32
public var maxKnownMessageId: Int32
public var maxOutgoingReadId: Int32
}
public enum CreateForumChannelTopicError {
case generic
}
func _internal_setChannelForumMode(account: Account, peerId: PeerId, isForum: Bool) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> Api.InputChannel? in
return transaction.getPeer(peerId).flatMap(apiInputChannel)
@ -130,8 +142,18 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si
for topic in topics {
switch topic {
case let .forumTopic(_, id, _, title, iconEmojiId, _, _, _, _):
guard let info = CodableEntry(EngineMessageHistoryThreads.Info(title: title, icon: iconEmojiId)) else {
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount):
let data = MessageHistoryThreadData(
info: EngineMessageHistoryThread.Info(
title: title,
icon: iconEmojiId
),
incomingUnreadCount: unreadCount,
maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId
)
guard let info = CodableEntry(data) else {
continue
}
transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: Int64(id), info: info)
@ -161,7 +183,7 @@ public final class ForumChannelTopics {
}
private let loadMoreDisposable = MetaDisposable()
private let createTopicDisposable = MetaDisposable()
private let updateDisposable = MetaDisposable()
init(queue: Queue, account: Account, peerId: PeerId) {
self.queue = queue
@ -177,30 +199,32 @@ public final class ForumChannelTopics {
preconditionFailure()
}
return State(items: view.items.compactMap { item -> ForumChannelTopics.Item? in
guard let info = item.info.get(EngineMessageHistoryThreads.Info.self) else {
guard let data = item.info.get(MessageHistoryThreadData.self) else {
return nil
}
return ForumChannelTopics.Item(
id: item.id,
info: info,
info: data.info,
index: item.index,
topMessage: item.topMessage.flatMap(EngineMessage.init)
)
})
})
self.updateDisposable.set(account.viewTracker.polledChannel(peerId: peerId).start())
}
deinit {
assert(self.queue.isCurrent())
self.loadMoreDisposable.dispose()
self.createTopicDisposable.dispose()
self.updateDisposable.dispose()
}
func createTopic(title: String) {
func createTopic(title: String) -> Signal<Int64, CreateForumChannelTopicError> {
let peerId = self.peerId
let account = self.account
let signal: Signal<Int32?, NoError> = self.account.postbox.transaction { transaction -> (Api.InputChannel?, Int64?) in
return self.account.postbox.transaction { transaction -> (Api.InputChannel?, Int64?) in
var fileId: Int64? = nil
var filteredFiles: [TelegramMediaFile] = []
@ -223,9 +247,10 @@ public final class ForumChannelTopics {
return (transaction.getPeer(peerId).flatMap(apiInputChannel), fileId)
}
|> mapToSignal { inputChannel, fileId -> Signal<Int32?, NoError> in
|> castError(CreateForumChannelTopicError.self)
|> mapToSignal { inputChannel, fileId -> Signal<Int64, CreateForumChannelTopicError> in
guard let inputChannel = inputChannel else {
return .single(nil)
return .fail(.generic)
}
var flags: Int32 = 0
if fileId != nil {
@ -239,37 +264,46 @@ public final class ForumChannelTopics {
randomId: Int64.random(in: Int64.min ..< Int64.max),
sendAs: nil
))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)
|> mapError { _ -> CreateForumChannelTopicError in
return .generic
}
|> mapToSignal { result -> Signal<Int32?, NoError> in
guard let result = result else {
return .single(nil)
}
|> mapToSignal { result -> Signal<Int64, CreateForumChannelTopicError> in
account.stateManager.addUpdates(result)
return .single(nil)
var topicId: Int64?
topicId = nil
for update in result.allUpdates {
switch update {
case let .updateNewChannelMessage(message, _, _):
if let message = StoreMessage(apiMessage: message) {
if case let .Id(id) = message.id {
topicId = Int64(id.id)
}
}
default:
break
}
}
if let topicId = topicId {
return .single(topicId)
} else {
return .fail(.generic)
}
}
}
self.createTopicDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let strongSelf = self else {
return
}
let _ = _internal_loadMessageHistoryThreads(account: strongSelf.account, peerId: strongSelf.peerId).start()
}))
}
}
public struct Item: Equatable {
public var id: Int64
public var info: EngineMessageHistoryThreads.Info
public var info: EngineMessageHistoryThread.Info
public var index: MessageIndex
public var topMessage: EngineMessage?
init(
id: Int64,
info: EngineMessageHistoryThreads.Info,
info: EngineMessageHistoryThread.Info,
index: MessageIndex,
topMessage: EngineMessage?
) {
@ -311,9 +345,19 @@ public final class ForumChannelTopics {
})
}
public func createTopic(title: String) {
self.impl.with { impl in
impl.createTopic(title: title)
public func createTopic(title: String) -> Signal<Int64, CreateForumChannelTopicError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.createTopic(title: title).start(next: { value in
subscriber.putNext(value)
}, error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
}))
}
return disposable
}
}
}

View File

@ -1586,14 +1586,15 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
} else {
for peerId in channelsToPoll.union(missingUpdatesFromChannels) {
if let peer = updatedState.peers[peerId] {
pollChannelSignals.append(pollChannel(network: network, peer: peer, state: updatedState.branch()))
pollChannelSignals.append(pollChannel(postbox: postbox, network: network, peer: peer, state: updatedState.branch()))
} else {
Logger.shared.log("State", "can't poll channel \(peerId): no peer found")
}
}
}
return combineLatest(pollChannelSignals) |> mapToSignal { states -> Signal<AccountFinalState, NoError> in
return combineLatest(pollChannelSignals)
|> mapToSignal { states -> Signal<AccountFinalState, NoError> in
var finalState: AccountMutableState = updatedState
var hadError = false
@ -1608,16 +1609,207 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
}
}
}
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|> map { resultingState, resolveError -> AccountFinalState in
return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError || resolveError, incomplete: missingUpdates, missingUpdatesFromChannels: Set(), discard: resolveError)
return resolveForumThreads(postbox: postbox, network: network, state: finalState)
|> mapToSignal { finalState in
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|> map { resultingState, resolveError -> AccountFinalState in
return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError || resolveError, incomplete: missingUpdates, missingUpdatesFromChannels: Set(), discard: resolveError)
}
}
}
}
}
func resolveForumThreads(postbox: Postbox, network: Network, state: AccountMutableState) -> Signal<AccountMutableState, NoError> {
var forumThreadIds = Set<MessageId>()
for operation in state.operations {
switch operation {
case let .AddMessages(messages, _):
for message in messages {
if let threadId = message.threadId {
if let channel = state.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) {
forumThreadIds.insert(MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: Int32(clamping: threadId)))
}
}
}
default:
break
}
}
if forumThreadIds.isEmpty {
return .single(state)
} else {
return postbox.transaction { transaction -> Signal<AccountMutableState, NoError> in
var missingForumThreadIds: [PeerId: [Int32]] = [:]
for threadId in forumThreadIds {
if let _ = transaction.getMessageHistoryThreadInfo(peerId: threadId.peerId, threadId: Int64(threadId.id)) {
} else {
missingForumThreadIds[threadId.peerId, default: []].append(threadId.id)
}
}
if missingForumThreadIds.isEmpty {
return .single(state)
} else {
var signals: [Signal<(PeerId, Api.messages.ForumTopics)?, NoError>] = []
for (peerId, threadIds) in missingForumThreadIds {
guard let inputChannel = transaction.getPeer(peerId).flatMap(apiInputChannel) else {
Logger.shared.log("State", "can't fetch thread infos \(threadIds) for peer \(peerId): can't create inputChannel")
continue
}
let signal = network.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: threadIds))
|> map { result -> (PeerId, Api.messages.ForumTopics)? in
return (peerId, result)
}
|> `catch` { _ -> Signal<(PeerId, Api.messages.ForumTopics)?, NoError> in
return .single(nil)
}
signals.append(signal)
}
return combineLatest(signals)
|> map { results -> AccountMutableState in
var state = state
var storeMessages: [StoreMessage] = []
for maybeResult in results {
if let (peerId, result) = maybeResult {
switch result {
case let .forumTopics(_, _, topics, messages, chats, users, pts):
state.mergeChats(chats)
state.mergeUsers(users)
for message in messages {
if let message = StoreMessage(apiMessage: message) {
storeMessages.append(message)
}
}
for topic in topics {
switch topic {
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount):
state.operations.append(.ResetForumTopic(topicId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id), data: MessageHistoryThreadData(
info: EngineMessageHistoryThread.Info(
title: title,
icon: iconEmojiId
),
incomingUnreadCount: unreadCount,
maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId
), pts: pts))
}
}
}
}
}
state.addMessages(storeMessages, location: .Random)
return state
}
}
}
|> switchToLatest
}
}
func resolveForumThreads(postbox: Postbox, network: Network, fetchedChatList: FetchedChatList) -> Signal<FetchedChatList, NoError> {
var forumThreadIds = Set<MessageId>()
for message in fetchedChatList.storeMessages {
if let threadId = message.threadId {
if let channel = fetchedChatList.peers.first(where: { $0.id == message.id.peerId }) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) {
forumThreadIds.insert(MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: Int32(clamping: threadId)))
}
}
}
if forumThreadIds.isEmpty {
return .single(fetchedChatList)
} else {
return postbox.transaction { transaction -> Signal<FetchedChatList, NoError> in
var missingForumThreadIds: [PeerId: [Int32]] = [:]
for threadId in forumThreadIds {
if let _ = transaction.getMessageHistoryThreadInfo(peerId: threadId.peerId, threadId: Int64(threadId.id)) {
} else {
missingForumThreadIds[threadId.peerId, default: []].append(threadId.id)
}
}
if missingForumThreadIds.isEmpty {
return .single(fetchedChatList)
} else {
var signals: [Signal<(PeerId, Api.messages.ForumTopics)?, NoError>] = []
for (peerId, threadIds) in missingForumThreadIds {
guard let inputChannel = fetchedChatList.peers.first(where: { $0.id == peerId }).flatMap(apiInputChannel) else {
Logger.shared.log("resolveForumThreads", "can't fetch thread infos \(threadIds) for peer \(peerId): can't create inputChannel")
continue
}
let signal = network.request(Api.functions.channels.getForumTopicsByID(channel: inputChannel, topics: threadIds))
|> map { result -> (PeerId, Api.messages.ForumTopics)? in
return (peerId, result)
}
|> `catch` { _ -> Signal<(PeerId, Api.messages.ForumTopics)?, NoError> in
return .single(nil)
}
signals.append(signal)
}
return combineLatest(signals)
|> map { results -> FetchedChatList in
var fetchedChatList = fetchedChatList
for maybeResult in results {
if let (peerId, result) = maybeResult {
switch result {
case let .forumTopics(_, _, topics, messages, chats, users, _):
fetchedChatList.peers.append(contentsOf: chats.compactMap { chat in
return parseTelegramGroupOrChannel(chat: chat)
})
fetchedChatList.peers.append(contentsOf: users.compactMap { user in
return TelegramUser(user: user)
})
for message in messages {
if let message = StoreMessage(apiMessage: message) {
fetchedChatList.storeMessages.append(message)
}
}
for topic in topics {
switch topic {
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount):
fetchedChatList.threadInfos[MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)] = MessageHistoryThreadData(
info: EngineMessageHistoryThread.Info(
title: title,
icon: iconEmojiId
),
incomingUnreadCount: unreadCount,
maxIncomingReadId: readInboxMaxId,
maxKnownMessageId: topMessage,
maxOutgoingReadId: readOutboxMaxId
)
}
}
}
}
}
return fetchedChatList
}
}
}
|> switchToLatest
}
}
func extractEmojiFileIds(message: StoreMessage, fileIds: inout Set<Int64>) {
for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
@ -1673,6 +1865,9 @@ private func resolveAssociatedMessages(postbox: Postbox, network: Network, state
let missingMessageIds = state.referencedMessageIds.subtracting(state.storedMessages)
if missingMessageIds.isEmpty {
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), reactions: reactionsFromState(state), result: state)
|> mapToSignal { state in
return resolveForumThreads(postbox: postbox, network: network, state: state)
}
} else {
var missingPeers = false
let _ = missingPeers
@ -1735,6 +1930,9 @@ private func resolveAssociatedMessages(postbox: Postbox, network: Network, state
}
|> mapToSignal { updatedState -> Signal<AccountMutableState, NoError> in
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: updatedState), reactions: reactionsFromState(updatedState), result: updatedState)
|> mapToSignal { state in
return resolveForumThreads(postbox: postbox, network: network, state: state)
}
}
}
}
@ -1896,7 +2094,7 @@ func pollChannelOnce(postbox: Postbox, network: Network, peerId: PeerId, stateMa
}
}
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
return pollChannel(network: network, peer: peer, state: initialState)
return pollChannel(postbox: postbox, network: network, peer: peer, state: initialState)
|> mapToSignal { (finalState, _, timeout) -> Signal<Int32, NoError> in
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
@ -1950,7 +2148,7 @@ public func standalonePollChannelOnce(postbox: Postbox, network: Network, peerId
}
}
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
return pollChannel(network: network, peer: peer, state: initialState)
return pollChannel(postbox: postbox, network: network, peer: peer, state: initialState)
|> mapToSignal { (finalState, _, timeout) -> Signal<Never, NoError> in
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
@ -2132,7 +2330,7 @@ private func resetChannels(postbox: Postbox, network: Network, peers: [Peer], st
}
}
private func pollChannel(network: Network, peer: Peer, state: AccountMutableState) -> Signal<(AccountMutableState, Bool, Int32?), NoError> {
private func pollChannel(postbox: Postbox, network: Network, peer: Peer, state: AccountMutableState) -> Signal<(AccountMutableState, Bool, Int32?), NoError> {
if let inputChannel = apiInputChannel(peer) {
let limit: Int32
limit = 100
@ -2154,161 +2352,191 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
}
})
|> retryRequest
|> map { difference -> (AccountMutableState, Bool, Int32?) in
var updatedState = state
var apiTimeout: Int32?
if let difference = difference {
switch difference {
case let .channelDifference(_, pts, timeout, newMessages, otherUpdates, chats, users):
apiTimeout = timeout
let channelPts: Int32
if let _ = updatedState.channelStates[peer.id] {
channelPts = pts
} else {
channelPts = pts
}
updatedState.updateChannelState(peer.id, pts: channelPts)
updatedState.mergeChats(chats)
updatedState.mergeUsers(users)
for apiMessage in newMessages {
if var message = StoreMessage(apiMessage: apiMessage) {
var attributes = message.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
message = message.withUpdatedAttributes(attributes)
if let preCachedResources = apiMessage.preCachedResources {
for (resource, data) in preCachedResources {
updatedState.addPreCachedResource(resource, data: data)
}
}
updatedState.addMessages([message], location: .UpperHistoryBlock)
if case let .Id(id) = message.id {
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
}
}
}
for update in otherUpdates {
switch update {
case let .updateDeleteChannelMessages(_, messages, _, _):
let peerId = peer.id
updatedState.deleteMessages(messages.map({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }))
case let .updateEditChannelMessage(apiMessage, _, _):
if let message = StoreMessage(apiMessage: apiMessage), case let .Id(messageId) = message.id, messageId.peerId == peer.id {
if let preCachedResources = apiMessage.preCachedResources {
for (resource, data) in preCachedResources {
updatedState.addPreCachedResource(resource, data: data)
}
}
var attributes = message.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes))
} else {
Logger.shared.log("State", "Invalid updateEditChannelMessage")
}
case let .updatePinnedChannelMessages(flags, channelId, messages, _, _):
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
updatedState.updateMessagesPinned(ids: messages.map { id in
MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: id)
}, pinned: (flags & (1 << 0)) != 0)
case let .updateChannelReadMessagesContents(_, messages):
updatedState.addReadMessagesContents((peer.id, messages))
case let .updateChannelMessageViews(_, id, views):
updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)
/*case let .updateChannelMessageForwards(_, id, views):
updatedState.addUpdateMessageForwardsCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)*/
case let .updateChannelWebPage(_, apiWebpage, _, _):
switch apiWebpage {
case let .webPageEmpty(id):
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
default:
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
case let .updateChannelAvailableMessages(_, minId):
let messageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: minId)
updatedState.updateMinAvailableMessage(messageId)
updatedState.updateCachedPeerData(peer.id, { current in
let previous: CachedChannelData
if let current = current as? CachedChannelData {
previous = current
} else {
previous = CachedChannelData()
}
return previous.withUpdatedMinAvailableMessageId(messageId)
})
default:
break
}
}
case let .channelDifferenceEmpty(_, pts, timeout):
apiTimeout = timeout
let channelPts: Int32
if let _ = updatedState.channelStates[peer.id] {
channelPts = pts
} else {
channelPts = pts
}
updatedState.updateChannelState(peer.id, pts: channelPts)
case let .channelDifferenceTooLong(_, timeout, dialog, messages, chats, users):
apiTimeout = timeout
var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32)?
switch dialog {
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, _, pts, _, _):
if let pts = pts {
parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount)
}
case .dialogFolder:
break
}
if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) = parameters {
updatedState.updateChannelState(peer.peerId, pts: pts)
updatedState.updateChannelInvalidationPts(peer.peerId, invalidationPts: pts)
updatedState.mergeChats(chats)
updatedState.mergeUsers(users)
updatedState.setNeedsHoleFromPreviousState(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, validateChannelPts: pts)
for apiMessage in messages {
if var message = StoreMessage(apiMessage: apiMessage) {
var attributes = message.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
message = message.withUpdatedAttributes(attributes)
if let preCachedResources = apiMessage.preCachedResources {
for (resource, data) in preCachedResources {
updatedState.addPreCachedResource(resource, data: data)
}
}
let location: AddMessagesLocation
if case let .Id(id) = message.id, id.id == topMessage {
location = .UpperHistoryBlock
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
} else {
location = .Random
}
updatedState.addMessages([message], location: location)
}
}
updatedState.resetReadState(peer.peerId, namespace: Namespaces.Message.Cloud, maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: nil)
updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage))
updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenReaction, namespace: Namespaces.Message.Cloud, count: unreadReactionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage))
} else {
assertionFailure()
}
}
|> mapToSignal { difference -> Signal<(AccountMutableState, Bool, Int32?), NoError> in
guard let difference = difference else {
return .single((state, false, nil))
}
switch difference {
case let .channelDifference(_, pts, timeout, newMessages, otherUpdates, chats, users):
var updatedState = state
var apiTimeout: Int32?
apiTimeout = timeout
let channelPts: Int32
if let _ = updatedState.channelStates[peer.id] {
channelPts = pts
} else {
channelPts = pts
}
updatedState.updateChannelState(peer.id, pts: channelPts)
updatedState.mergeChats(chats)
updatedState.mergeUsers(users)
var forumThreadIds = Set<MessageId>()
for apiMessage in newMessages {
if var message = StoreMessage(apiMessage: apiMessage) {
var attributes = message.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
message = message.withUpdatedAttributes(attributes)
if let preCachedResources = apiMessage.preCachedResources {
for (resource, data) in preCachedResources {
updatedState.addPreCachedResource(resource, data: data)
}
}
updatedState.addMessages([message], location: .UpperHistoryBlock)
if case let .Id(id) = message.id {
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
if let threadId = message.threadId {
if let channel = updatedState.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) {
forumThreadIds.insert(MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: Int32(clamping: threadId)))
}
}
}
}
}
for update in otherUpdates {
switch update {
case let .updateDeleteChannelMessages(_, messages, _, _):
let peerId = peer.id
updatedState.deleteMessages(messages.map({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }))
case let .updateEditChannelMessage(apiMessage, _, _):
if let message = StoreMessage(apiMessage: apiMessage), case let .Id(messageId) = message.id, messageId.peerId == peer.id {
if let preCachedResources = apiMessage.preCachedResources {
for (resource, data) in preCachedResources {
updatedState.addPreCachedResource(resource, data: data)
}
}
var attributes = message.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes))
if let threadId = message.threadId {
if let channel = updatedState.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) {
forumThreadIds.insert(MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: Int32(clamping: threadId)))
}
}
} else {
Logger.shared.log("State", "Invalid updateEditChannelMessage")
}
case let .updatePinnedChannelMessages(flags, channelId, messages, _, _):
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
updatedState.updateMessagesPinned(ids: messages.map { id in
MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: id)
}, pinned: (flags & (1 << 0)) != 0)
case let .updateChannelReadMessagesContents(_, messages):
updatedState.addReadMessagesContents((peer.id, messages))
case let .updateChannelMessageViews(_, id, views):
updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)
case let .updateChannelWebPage(_, apiWebpage, _, _):
switch apiWebpage {
case let .webPageEmpty(id):
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
default:
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
updatedState.updateMedia(webpage.webpageId, media: webpage)
}
}
case let .updateChannelAvailableMessages(_, minId):
let messageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: minId)
updatedState.updateMinAvailableMessage(messageId)
updatedState.updateCachedPeerData(peer.id, { current in
let previous: CachedChannelData
if let current = current as? CachedChannelData {
previous = current
} else {
previous = CachedChannelData()
}
return previous.withUpdatedMinAvailableMessageId(messageId)
})
default:
break
}
}
return resolveForumThreads(postbox: postbox, network: network, state: updatedState)
|> map { updatedState -> (AccountMutableState, Bool, Int32?) in
return (updatedState, true, apiTimeout)
}
case let .channelDifferenceEmpty(_, pts, timeout):
var updatedState = state
var apiTimeout: Int32?
apiTimeout = timeout
let channelPts: Int32
if let _ = updatedState.channelStates[peer.id] {
channelPts = pts
} else {
channelPts = pts
}
updatedState.updateChannelState(peer.id, pts: channelPts)
return .single((updatedState, true, apiTimeout))
case let .channelDifferenceTooLong(_, timeout, dialog, messages, chats, users):
var updatedState = state
var apiTimeout: Int32?
apiTimeout = timeout
var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32)?
switch dialog {
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, _, pts, _, _):
if let pts = pts {
parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount)
}
case .dialogFolder:
break
}
if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) = parameters {
updatedState.updateChannelState(peer.peerId, pts: pts)
updatedState.updateChannelInvalidationPts(peer.peerId, invalidationPts: pts)
updatedState.mergeChats(chats)
updatedState.mergeUsers(users)
updatedState.setNeedsHoleFromPreviousState(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, validateChannelPts: pts)
for apiMessage in messages {
if var message = StoreMessage(apiMessage: apiMessage) {
var attributes = message.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
message = message.withUpdatedAttributes(attributes)
if let preCachedResources = apiMessage.preCachedResources {
for (resource, data) in preCachedResources {
updatedState.addPreCachedResource(resource, data: data)
}
}
let location: AddMessagesLocation
if case let .Id(id) = message.id, id.id == topMessage {
location = .UpperHistoryBlock
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
} else {
location = .Random
}
updatedState.addMessages([message], location: location)
}
}
updatedState.resetReadState(peer.peerId, namespace: Namespaces.Message.Cloud, maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: nil)
updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage))
updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenReaction, namespace: Namespaces.Message.Cloud, count: unreadReactionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage))
} else {
assertionFailure()
}
return .single((updatedState, true, apiTimeout))
}
return (updatedState, difference != nil, apiTimeout)
}
} else {
Logger.shared.log("State", "can't poll channel \(peer.id): can't create inputChannel")
@ -2403,7 +2631,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddScheduledMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -2631,12 +2859,48 @@ func replayFinalState(
for message in messages {
if case let .Id(id) = message.id {
if let threadId = message.threadId {
for media in message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case let .topicEditTitle(title):
if var data = transaction.getMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) {
data.info = EngineMessageHistoryThread.Info(title: title, icon: data.info.icon)
if let entry = CodableEntry(data) {
transaction.setMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId, info: entry)
}
}
case let .topicEditIcon(fileId):
if var data = transaction.getMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) {
data.info = EngineMessageHistoryThread.Info(title: data.info.title, icon: fileId == 0 ? nil : fileId)
if let entry = CodableEntry(data) {
transaction.setMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId, info: entry)
}
}
default:
break
}
}
}
let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId)
if id.peerId.namespace == Namespaces.Peer.CloudChannel {
if !transaction.messageExists(id: id) {
addMessageThreadStatsDifference(threadMessageId: messageThreadId, remove: 0, addedMessagePeer: message.authorId, addedMessageId: id, isOutgoing: !message.flags.contains(.Incoming))
}
}
if message.flags.contains(.Incoming) {
if var data = transaction.getMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId)?.get(MessageHistoryThreadData.self) {
if id.id >= data.maxKnownMessageId {
data.maxKnownMessageId = id.id
data.incomingUnreadCount += 1
if let entry = CodableEntry(data) {
transaction.setMessageHistoryThreadInfo(peerId: id.peerId, threadId: threadId, info: entry)
}
}
}
}
}
}
}
@ -2885,7 +3149,6 @@ func replayFinalState(
}
case .ReadGroupFeedInbox:
break
//transaction.applyGroupFeedReadMaxIndex(groupId: groupId, index: index)
case let .UpdateReadThread(threadMessageId, readMaxId, isIncoming, mainChannelMessage):
if isIncoming {
if let currentId = updatedIncomingThreadReadStates[threadMessageId] {
@ -2895,6 +3158,23 @@ func replayFinalState(
} else {
updatedIncomingThreadReadStates[threadMessageId] = readMaxId
}
if let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) {
if var data = transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id))?.get(MessageHistoryThreadData.self) {
if readMaxId > data.maxIncomingReadId {
if let toIndex = transaction.getMessage(MessageId(peerId: threadMessageId.peerId, namespace: threadMessageId.namespace, id: readMaxId))?.index {
if let count = transaction.getThreadMessageCount(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), namespace: threadMessageId.namespace, fromIdExclusive: data.maxIncomingReadId, toIndex: toIndex) {
data.incomingUnreadCount = max(0, data.incomingUnreadCount - Int32(count))
}
}
data.maxKnownMessageId = max(data.maxKnownMessageId, readMaxId)
if let entry = CodableEntry(data) {
transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry)
}
}
}
}
if let mainChannelMessage = mainChannelMessage {
transaction.updateMessage(mainChannelMessage, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
@ -2919,6 +3199,17 @@ func replayFinalState(
} else {
updatedOutgoingThreadReadStates[threadMessageId] = readMaxId
}
if let channel = transaction.getPeer(threadMessageId.peerId) as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) {
if var data = transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id))?.get(MessageHistoryThreadData.self) {
if readMaxId >= data.maxOutgoingReadId {
data.maxOutgoingReadId = readMaxId
if let entry = CodableEntry(data) {
transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry)
}
}
}
}
}
case let .ResetReadState(peerId, namespace, maxIncomingReadId, maxOutgoingReadId, maxKnownId, count, markedUnread):
var markedUnreadValue: Bool = false
@ -3522,6 +3813,15 @@ func replayFinalState(
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: media))
})
case let .ResetForumTopic(topicId, data, pts):
if finalState.state.resetForumTopicLists[topicId.peerId] == nil {
let _ = pts
if let entry = CodableEntry(data) {
transaction.setMessageHistoryThreadInfo(peerId: topicId.peerId, threadId: Int64(topicId.id), info: entry)
} else {
assertionFailure()
}
}
}
}

View File

@ -287,10 +287,10 @@ final class ChatHistoryPreloadManager {
self.canPreloadHistoryDisposable = (networkState
|> map { state -> Bool in
switch state {
case .online:
return true
default:
return false
case .online:
return true
default:
return false
}
}
|> distinctUntilChanged
@ -298,6 +298,13 @@ final class ChatHistoryPreloadManager {
guard let strongSelf = self, strongSelf.canPreloadHistoryValue != value else {
return
}
#if DEBUG
if "".isEmpty {
return
}
#endif
strongSelf.canPreloadHistoryValue = value
if value {
for i in 0 ..< min(3, strongSelf.entries.count) {

View File

@ -187,22 +187,23 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
}
struct FetchedChatList {
let chatPeerIds: [PeerId]
let peers: [Peer]
let peerPresences: [PeerId: Api.User]
let notificationSettings: [PeerId: PeerNotificationSettings]
let readStates: [PeerId: [MessageId.Namespace: PeerReadState]]
let mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
let reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
let channelStates: [PeerId: Int32]
let storeMessages: [StoreMessage]
let topMessageIds: [PeerId: MessageId]
var chatPeerIds: [PeerId]
var peers: [Peer]
var peerPresences: [PeerId: Api.User]
var notificationSettings: [PeerId: PeerNotificationSettings]
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]]
var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
var channelStates: [PeerId: Int32]
var storeMessages: [StoreMessage]
var topMessageIds: [PeerId: MessageId]
let lowerNonPinnedIndex: MessageIndex?
var lowerNonPinnedIndex: MessageIndex?
let pinnedItemIds: [PeerId]?
let folderSummaries: [PeerGroupId: PeerGroupUnreadCountersSummary]
let peerGroupIds: [PeerId: PeerGroupId]
var pinnedItemIds: [PeerId]?
var folderSummaries: [PeerGroupId: PeerGroupUnreadCountersSummary]
var peerGroupIds: [PeerId: PeerGroupId]
var threadInfos: [MessageId: MessageHistoryThreadData]
}
func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLocation, upperBound: MessageIndex, hash: Int64, limit: Int32) -> Signal<FetchedChatList?, NoError> {
@ -386,9 +387,18 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
pinnedItemIds: pinnedItemIds,
folderSummaries: folderSummaries,
peerGroupIds: peerGroupIds
peerGroupIds: peerGroupIds,
threadInfos: [:]
)
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: storeMessages, reactions: [], result: result)
|> mapToSignal { result in
if let result = result {
return resolveForumThreads(postbox: postbox, network: network, fetchedChatList: result)
|> map(Optional.init)
} else {
return .single(result)
}
}
}
}
}

View File

@ -0,0 +1,3 @@
import Foundation

View File

@ -735,6 +735,12 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId
return updated
})
for (threadMessageId, data) in fetchedChats.threadInfos {
if let entry = CodableEntry(data) {
transaction.setMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id), info: entry)
}
}
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: fetchedChats.peerPresences)
transaction.updateCurrentPeerNotificationSettings(fetchedChats.notificationSettings)
let _ = transaction.addMessages(fetchedChats.storeMessages, location: .UpperHistoryBlock)

View File

@ -72,11 +72,12 @@ public final class EngineChatList: Equatable {
public let readCounters: EnginePeerReadCounters?
public let isMuted: Bool
public let draft: Draft?
public let threadInfo: EngineMessageHistoryThreads.Info?
public let threadInfo: EngineMessageHistoryThread.Info?
public let renderedPeer: EngineRenderedPeer
public let presence: EnginePeer.Presence?
public let hasUnseenMentions: Bool
public let hasUnseenReactions: Bool
public let forumTopicTitle: String?
public let hasFailed: Bool
public let isContact: Bool
@ -87,11 +88,12 @@ public final class EngineChatList: Equatable {
readCounters: EnginePeerReadCounters?,
isMuted: Bool,
draft: Draft?,
threadInfo: EngineMessageHistoryThreads.Info?,
threadInfo: EngineMessageHistoryThread.Info?,
renderedPeer: EngineRenderedPeer,
presence: EnginePeer.Presence?,
hasUnseenMentions: Bool,
hasUnseenReactions: Bool,
forumTopicTitle: String?,
hasFailed: Bool,
isContact: Bool
) {
@ -106,6 +108,7 @@ public final class EngineChatList: Equatable {
self.presence = presence
self.hasUnseenMentions = hasUnseenMentions
self.hasUnseenReactions = hasUnseenReactions
self.forumTopicTitle = forumTopicTitle
self.hasFailed = hasFailed
self.isContact = isContact
}
@ -144,6 +147,9 @@ public final class EngineChatList: Equatable {
if lhs.hasUnseenReactions != rhs.hasUnseenReactions {
return false
}
if lhs.forumTopicTitle != rhs.forumTopicTitle {
return false
}
if lhs.hasFailed != rhs.hasFailed {
return false
}
@ -351,7 +357,7 @@ public extension EngineChatList.RelativePosition {
extension EngineChatList.Item {
convenience init?(_ entry: ChatListEntry) {
switch entry {
case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, hasFailed, isContact):
case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, forumTopicData, hasFailed, isContact):
var draft: EngineChatList.Draft?
if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp {
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
@ -377,6 +383,11 @@ extension EngineChatList.Item {
hasUnseenReactions = (info.tagSummaryCount ?? 0) != 0// > (info.actionsSummaryCount ?? 0)
}
var forumTopicTitle: String?
if let forumTopicData = forumTopicData?.get(MessageHistoryThreadData.self) {
forumTopicTitle = forumTopicData.info.title
}
self.init(
id: .chatList(index.messageIndex.id.peerId),
index: .chatList(index),
@ -389,6 +400,7 @@ extension EngineChatList.Item {
presence: presence.flatMap(EnginePeer.Presence.init),
hasUnseenMentions: hasUnseenMentions,
hasUnseenReactions: hasUnseenReactions,
forumTopicTitle: forumTopicTitle,
hasFailed: hasFailed,
isContact: isContact
)

View File

@ -332,6 +332,20 @@ private class ReplyThreadHistoryContextImpl {
let account = self.account
let _ = (self.account.postbox.transaction { transaction -> (Api.InputPeer?, MessageId?, Int?) in
if var data = transaction.getMessageHistoryThreadInfo(peerId: messageId.peerId, threadId: Int64(messageId.id))?.get(MessageHistoryThreadData.self) {
if messageIndex.id.id >= data.maxIncomingReadId {
if let count = transaction.getThreadMessageCount(peerId: messageId.peerId, threadId: Int64(messageId.id), namespace: messageId.namespace, fromIdExclusive: data.maxIncomingReadId, toIndex: messageIndex) {
data.incomingUnreadCount = max(0, data.incomingUnreadCount - Int32(count))
}
data.maxKnownMessageId = max(data.maxKnownMessageId, messageIndex.id.id)
if let entry = CodableEntry(data) {
transaction.setMessageHistoryThreadInfo(peerId: messageId.peerId, threadId: Int64(messageId.id), info: entry)
}
}
}
if let message = transaction.getMessage(messageId) {
for attribute in message.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
@ -574,8 +588,10 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
return .fail(.generic)
}
let replyInfo = Promise<AccountViewTracker.UpdatedMessageReplyInfo?>()
replyInfo.set(.single(nil))
let replyInfo = Promise<MessageHistoryThreadData?>()
replyInfo.set(account.postbox.transaction { transaction -> MessageHistoryThreadData? in
return transaction.getMessageHistoryThreadInfo(peerId: messageId.peerId, threadId: Int64(messageId.id))?.get(MessageHistoryThreadData.self)
})
let remoteDiscussionMessageSignal: Signal<DiscussionMessage?, NoError> = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id))
|> map(Optional.init)
@ -664,12 +680,22 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
}
let discussionMessageSignal = (replyInfo.get()
|> take(1)
|> mapToSignal { replyInfo -> Signal<DiscussionMessage?, NoError> in
guard let replyInfo = replyInfo else {
|> mapToSignal { threadData -> Signal<DiscussionMessage?, NoError> in
guard let threadData = threadData else {
return .single(nil)
}
return account.postbox.transaction { transaction -> DiscussionMessage? in
var foundDiscussionMessageId: MessageId?
return DiscussionMessage(
messageId: messageId,
channelMessageId: nil,
isChannelPost: false,
maxMessage: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxKnownMessageId),
maxReadIncomingMessageId: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxIncomingReadId),
maxReadOutgoingMessageId: MessageId(peerId: messageId.peerId, namespace: messageId.namespace, id: threadData.maxOutgoingReadId),
unreadCount: Int(threadData.incomingUnreadCount)
)
/*var foundDiscussionMessageId: MessageId?
transaction.scanMessageAttributes(peerId: replyInfo.commentsPeerId, namespace: Namespaces.Message.Cloud, limit: 1000, { id, attributes in
for attribute in attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
@ -696,7 +722,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
maxReadIncomingMessageId: replyInfo.maxReadIncomingMessageId,
maxReadOutgoingMessageId: nil,
unreadCount: 0
)
)*/
}
})
|> mapToSignal { result -> Signal<DiscussionMessage?, NoError> in
@ -718,12 +744,13 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
let preloadedHistoryPosition: Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?), FetchChannelReplyThreadMessageError> = replyInfo.get()
|> take(1)
|> castError(FetchChannelReplyThreadMessageError.self)
|> mapToSignal { replyInfo -> Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?), FetchChannelReplyThreadMessageError> in
if let replyInfo = replyInfo {
return account.postbox.transaction { transaction -> (FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?) in
|> mapToSignal { threadData -> Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?), FetchChannelReplyThreadMessageError> in
if let _ = threadData, !"".isEmpty {
return .fail(.generic)
/*return account.postbox.transaction { transaction -> (FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?) in
var threadInput: FetchMessageHistoryHoleThreadInput = .threadFromChannel(channelMessageId: messageId)
var threadMessageId: MessageId?
transaction.scanMessageAttributes(peerId: replyInfo.commentsPeerId, namespace: Namespaces.Message.Cloud, limit: 1000, { id, attributes in
transaction.scanMessageAttributes(peerId: messageId.peerId, namespace: Namespaces.Message.Cloud, limit: 1000, { id, attributes in
for attribute in attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
if attribute.messageId == messageId {
@ -745,7 +772,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
}
return (threadInput, replyInfo.commentsPeerId, threadMessageId, anchor, replyInfo.maxMessageId)
}
|> castError(FetchChannelReplyThreadMessageError.self)
|> castError(FetchChannelReplyThreadMessageError.self)*/
} else {
return discussionMessage.get()
|> take(1)
@ -770,7 +797,10 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
}
}
let preloadedHistory = preloadedHistoryPosition
let preloadedHistory: Signal<(FetchMessageHistoryHoleResult?, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError>
preloadedHistory = preloadedHistoryPosition
|> mapToSignal { peerInput, commentsPeerId, threadMessageId, anchor, maxMessageId -> Signal<(FetchMessageHistoryHoleResult?, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError> in
guard let maxMessageId = maxMessageId else {
return .single((FetchMessageHistoryHoleResult(removedIndices: IndexSet(integersIn: 1 ..< Int(Int32.max - 1)), strictRemovedIndices: IndexSet(), actualPeerId: nil, actualThreadId: nil, ids: []), .automatic))
@ -814,7 +844,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
anchor: inputAnchor,
namespaces: .not(Namespaces.Message.allScheduled)
)
if !testView.isLoading {
if !testView.isLoading || transaction.getMessageHistoryThreadInfo(peerId: threadMessageId.peerId, threadId: Int64(threadMessageId.id)) != nil {
let initialAnchor: ChatReplyThreadMessage.Anchor
switch anchor {
case .lowerBound:

View File

@ -140,7 +140,7 @@ public func donateSendMessageIntent(account: Account, sharedContext: SharedAccou
if peer.id == account.peerId {
signals.append(.single((peer, subject, savedMessagesAvatar)))
} else {
let peerAndAvatar = (peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.smallProfileImage, round: false) ?? .single(nil))
let peerAndAvatar = (peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.smallProfileImage, clipStyle: .none) ?? .single(nil))
|> map { imageVersions -> (Peer, SendMessageIntentSubject, UIImage?) in
var avatarImage: UIImage?
if let image = imageVersions?.0 {

View File

@ -407,7 +407,6 @@ public final class ForumTopicListScreen: ViewController {
}
func createPressed() {
self.forumChannelContext.createTopic(title: "Topic#\(Int.random(in: 0 ..< 100000))")
}
private func update(transition: Transition) {

View File

@ -8,7 +8,7 @@ import WebPBinding
public func cacheLottieAnimation(data: Data, width: Int, height: Int, keyframeOnly: Bool, writer: AnimationCacheItemWriter, firstFrameOnly: Bool) {
let work: () -> Void = {
let decompressedData = TGGUnzipData(data, 1 * 1024 * 1024) ?? data
let decompressedData = TGGUnzipData(data, 2 * 1024 * 1024) ?? data
guard let animation = LottieInstance(data: decompressedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
writer.finish()
return

View File

@ -8969,7 +8969,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if !(peer is TelegramGroup || peer is TelegramChannel) {
return
}
presentAddMembers(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, parentController: strongSelf, groupPeer: peer, selectAddMemberDisposable: strongSelf.selectAddMemberDisposable, addMemberDisposable: strongSelf.addMemberDisposable)
presentAddMembersImpl(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, parentController: strongSelf, groupPeer: peer, selectAddMemberDisposable: strongSelf.selectAddMemberDisposable, addMemberDisposable: strongSelf.addMemberDisposable)
}, presentGigagroupHelp: { [weak self] in
if let strongSelf = self {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_GigagroupDescription), elevatedLayout: false, action: { _ in return true }), in: .current)
@ -14422,7 +14422,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
present(statusController, nil)
}
let disposable = (fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId), atMessageId: atMessageId)
let disposable = (fetchAndPreloadReplyThreadInfo(context: context, subject: isChannelPost ? .channelPost(messageId) : .groupMessage(messageId), atMessageId: atMessageId, preload: true)
|> deliverOnMainQueue).start(next: { [weak statusController] result in
if displayModalProgress {
statusController?.dismiss()

View File

@ -336,7 +336,7 @@ enum ReplyThreadSubject {
case groupMessage(MessageId)
}
func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThreadSubject, atMessageId: MessageId?) -> Signal<ReplyThreadInfo, FetchChannelReplyThreadMessageError> {
func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThreadSubject, atMessageId: MessageId?, preload: Bool) -> Signal<ReplyThreadInfo, FetchChannelReplyThreadMessageError> {
let message: Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError>
switch subject {
case .channelPost(let messageId), .groupMessage(let messageId):
@ -380,42 +380,52 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
))
}
let preloadSignal = preloadedChatHistoryViewForLocation(
input,
context: context,
chatLocation: .replyThread(message: replyThreadMessage),
subject: nil,
chatLocationContextHolder: chatLocationContextHolder,
fixedCombinedReadStates: nil,
tagMask: nil,
additionalData: []
)
return preloadSignal
|> map { historyView -> Bool? in
switch historyView {
case .Loading:
return nil
case let .HistoryView(view, _, _, _, _, _, _):
return view.entries.isEmpty
if preload {
let preloadSignal = preloadedChatHistoryViewForLocation(
input,
context: context,
chatLocation: .replyThread(message: replyThreadMessage),
subject: nil,
chatLocationContextHolder: chatLocationContextHolder,
fixedCombinedReadStates: nil,
tagMask: nil,
additionalData: []
)
return preloadSignal
|> map { historyView -> Bool? in
switch historyView {
case .Loading:
return nil
case let .HistoryView(view, _, _, _, _, _, _):
return view.entries.isEmpty
}
}
}
|> mapToSignal { value -> Signal<Bool, NoError> in
if let value = value {
return .single(value)
} else {
return .complete()
|> mapToSignal { value -> Signal<Bool, NoError> in
if let value = value {
return .single(value)
} else {
return .complete()
}
}
}
|> take(1)
|> map { isEmpty -> ReplyThreadInfo in
return ReplyThreadInfo(
|> take(1)
|> map { isEmpty -> ReplyThreadInfo in
return ReplyThreadInfo(
message: replyThreadMessage,
isChannelPost: replyThreadMessage.isChannelPost,
isEmpty: isEmpty,
scrollToLowerBoundMessage: scrollToLowerBoundMessage,
contextHolder: chatLocationContextHolder
)
}
|> castError(FetchChannelReplyThreadMessageError.self)
} else {
return .single(ReplyThreadInfo(
message: replyThreadMessage,
isChannelPost: replyThreadMessage.isChannelPost,
isEmpty: isEmpty,
isEmpty: false,
scrollToLowerBoundMessage: scrollToLowerBoundMessage,
contextHolder: chatLocationContextHolder
)
))
}
|> castError(FetchChannelReplyThreadMessageError.self)
}
}

View File

@ -99,7 +99,8 @@ private enum ChatListSearchEntry: Comparable, Identifiable {
promoInfo: nil,
ignoreUnreadBadge: true,
displayAsMessage: true,
hasFailedMessages: false
hasFailedMessages: false,
forumThreadTitle: nil
),
editing: false,
hasActiveRevealControls: false,
@ -239,7 +240,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
return
}
switch item.content {
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _):
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
if let message = messages.first {
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.peerId), subject: .message(id: .id(message.id), highlight: true, timecode: nil), botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)

View File

@ -225,7 +225,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll
}
public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController) -> Signal<Never, NoError> {
return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: nil)
return fetchAndPreloadReplyThreadInfo(context: context, subject: .groupMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId))), atMessageId: nil, preload: false)
|> deliverOnMainQueue
|> beforeNext { [weak context, weak navigationController] result in
guard let context = context, let navigationController = navigationController else {

View File

@ -6359,7 +6359,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return
}
presentAddMembers(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, parentController: controller, groupPeer: groupPeer, selectAddMemberDisposable: self.selectAddMemberDisposable, addMemberDisposable: self.addMemberDisposable)
presentAddMembersImpl(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, parentController: controller, groupPeer: groupPeer, selectAddMemberDisposable: self.selectAddMemberDisposable, addMemberDisposable: self.addMemberDisposable)
}
private func openQrCode() {
@ -8828,7 +8828,7 @@ private final class PeerInfoContextReferenceContentSource: ContextReferenceConte
}
}
func presentAddMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) {
func presentAddMembersImpl(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) {
let members: Promise<[PeerId]> = Promise()
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
/*var membersDisposable: Disposable?

View File

@ -1278,6 +1278,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return PeerSelectionControllerImpl(params)
}
public func openAddPeerMembers(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) {
return presentAddMembersImpl(context: context, updatedPresentationData: updatedPresentationData, parentController: parentController, groupPeer: groupPeer, selectAddMemberDisposable: selectAddMemberDisposable, addMemberDisposable: addMemberDisposable)
}
public func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)? = nil, backgroundNode: ASDisplayNode?, availableReactions: AvailableReactions?, isCentered: Bool) -> ListViewItem {
let controllerInteraction: ChatControllerInteraction

View File

@ -325,7 +325,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P
}
func makeBridgeChat(_ entry: ChatListEntry, strings: PresentationStrings) -> (TGBridgeChat, [Int64 : TGBridgeUser])? {
if case let .MessageEntry(index, messages, readState, _, _, renderedPeer, _, _, hasFailed, _) = entry {
if case let .MessageEntry(index, messages, readState, _, _, renderedPeer, _, _, _, hasFailed, _) = entry {
guard index.messageIndex.id.peerId.namespace != Namespaces.Peer.SecretChat else {
return nil
}