mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-17 11:50:56 +00:00
[WIP] Topics
This commit is contained in:
parent
012aacf202
commit
b5a59ba488
@ -36,7 +36,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
|||||||
|> mapToSignal { view -> Signal<[INMessage], NoError> in
|
|> mapToSignal { view -> Signal<[INMessage], NoError> in
|
||||||
var signals: [Signal<[INMessage], NoError>] = []
|
var signals: [Signal<[INMessage], NoError>] = []
|
||||||
for entry in view.0.entries {
|
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 {
|
if index.messageIndex.id.peerId.namespace != Namespaces.Peer.CloudUser {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 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 openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
|
||||||
func presentContactsWarningSuppression(context: AccountContext, present: (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 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
|
func makeRecentSessionsController(context: AccountContext, activeSessionsContext: ActiveSessionsContext) -> ViewController & RecentSessionsController
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@ public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
|
|||||||
public enum AvatarNodeClipStyle {
|
public enum AvatarNodeClipStyle {
|
||||||
case none
|
case none
|
||||||
case round
|
case round
|
||||||
|
case roundedRect
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AvatarNodeParameters: NSObject {
|
private class AvatarNodeParameters: NSObject {
|
||||||
@ -330,7 +331,7 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
|
|
||||||
let account = account ?? genericContext.account
|
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.contents = nil
|
||||||
self.displaySuspended = true
|
self.displaySuspended = true
|
||||||
self.imageReady.set(self.imageNode.contentReady)
|
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:
|
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: bounds.size.width, height:
|
||||||
bounds.size.height))
|
bounds.size.height))
|
||||||
context.clip()
|
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 {
|
if let explicitColorIndex = parameters.explicitColorIndex {
|
||||||
|
|||||||
@ -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> {
|
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>
|
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) {
|
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))
|
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|
||||||
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
|
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
|
||||||
@ -131,7 +131,7 @@ public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, size: CG
|
|||||||
return iconSignal
|
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) {
|
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
||||||
return imageData
|
return imageData
|
||||||
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
|
|> 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.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
||||||
context.setBlendMode(.copy)
|
context.setBlendMode(.copy)
|
||||||
|
|
||||||
if round && displayDimensions.width != 60.0 {
|
switch clipStyle {
|
||||||
context.addEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
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()
|
context.clip()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,30 +194,46 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
|
|||||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||||
context.setBlendMode(.copy)
|
context.setBlendMode(.copy)
|
||||||
}
|
}
|
||||||
if round {
|
switch clipStyle {
|
||||||
|
case .none:
|
||||||
|
break
|
||||||
|
case .round:
|
||||||
if displayDimensions.width == 60.0 {
|
if displayDimensions.width == 60.0 {
|
||||||
context.setBlendMode(.destinationOut)
|
context.setBlendMode(.destinationOut)
|
||||||
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
||||||
}
|
}
|
||||||
|
case .roundedRect:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let emptyColor = emptyColor {
|
if let emptyColor = emptyColor {
|
||||||
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
||||||
context.setFillColor(emptyColor.cgColor)
|
context.setFillColor(emptyColor.cgColor)
|
||||||
if round {
|
switch clipStyle {
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
case .none:
|
||||||
} else {
|
|
||||||
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
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 {
|
} else if let emptyColor = emptyColor {
|
||||||
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
||||||
context.setFillColor(emptyColor.cgColor)
|
context.setFillColor(emptyColor.cgColor)
|
||||||
if round {
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
switch clipStyle {
|
||||||
} else {
|
case .none:
|
||||||
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
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 {
|
} else if let emptyColor = emptyColor {
|
||||||
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
|
||||||
context.setFillColor(emptyColor.cgColor)
|
context.setFillColor(emptyColor.cgColor)
|
||||||
if round {
|
switch clipStyle {
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
case .none:
|
||||||
} else {
|
|
||||||
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -78,6 +78,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||||
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
|
"//submodules/TelegramUI/Components/EmojiStatusSelectionComponent",
|
||||||
"//submodules/TelegramUI/Components/EntityKeyboard",
|
"//submodules/TelegramUI/Components/EntityKeyboard",
|
||||||
|
"//submodules/AnimationUI:AnimationUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import MultiAnimationRenderer
|
|||||||
import EmojiStatusSelectionComponent
|
import EmojiStatusSelectionComponent
|
||||||
import EntityKeyboard
|
import EntityKeyboard
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
|
import AnimationUI
|
||||||
|
|
||||||
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
||||||
if listNode.scroller.isDragging {
|
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 {
|
public class ChatListControllerImpl: TelegramBaseController, ChatListController {
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
|
|
||||||
@ -174,8 +330,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
private weak var emojiStatusSelectionController: ViewController?
|
private weak var emojiStatusSelectionController: ViewController?
|
||||||
|
|
||||||
|
private let moreBarButton: MoreHeaderButton
|
||||||
|
private let moreBarButtonItem: UIBarButtonItem
|
||||||
private var forumChannelTracker: ForumChannelTopics?
|
private var forumChannelTracker: ForumChannelTopics?
|
||||||
|
|
||||||
|
private let selectAddMemberDisposable = MetaDisposable()
|
||||||
|
private let addMemberDisposable = MetaDisposable()
|
||||||
|
|
||||||
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
if self.isNodeLoaded {
|
if self.isNodeLoaded {
|
||||||
self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition)
|
self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition)
|
||||||
@ -204,6 +365,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
animationRenderer: self.animationRenderer
|
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()
|
self.tabContainerNode = ChatListFilterTabContainerNode()
|
||||||
|
|
||||||
super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: .all)
|
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"
|
title = "Forum"
|
||||||
|
|
||||||
self.forumChannelTracker = ForumChannelTopics(account: self.context.account, peerId: peerId)
|
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)
|
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
|
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
|
||||||
self.navigationItem.backBarButtonItem = backBarButtonItem
|
self.navigationItem.backBarButtonItem = backBarButtonItem
|
||||||
} else {
|
} else {
|
||||||
let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
switch self.location {
|
||||||
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit
|
case .chatList:
|
||||||
self.navigationItem.rightBarButtonItem = rightBarButtonItem
|
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)
|
let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||||
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
|
backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back
|
||||||
self.navigationItem.backBarButtonItem = backBarButtonItem
|
self.navigationItem.backBarButtonItem = backBarButtonItem
|
||||||
@ -454,9 +632,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(strongSelf.editPressed))
|
switch strongSelf.location {
|
||||||
editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Edit
|
case .chatList:
|
||||||
strongSelf.navigationItem.setRightBarButton(editItem, animated: true)
|
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
|
let (hasProxy, connectsViaProxy) = proxy
|
||||||
@ -865,6 +1048,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
self.filterDisposable.dispose()
|
self.filterDisposable.dispose()
|
||||||
self.featuredFiltersDisposable.dispose()
|
self.featuredFiltersDisposable.dispose()
|
||||||
self.activeDownloadsDisposable?.dispose()
|
self.activeDownloadsDisposable?.dispose()
|
||||||
|
self.selectAddMemberDisposable.dispose()
|
||||||
|
self.addMemberDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openStatusSetup(sourceView: UIView) {
|
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 = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||||
editItem.accessibilityLabel = self.presentationData.strings.Common_Done
|
editItem.accessibilityLabel = self.presentationData.strings.Common_Done
|
||||||
} else {
|
} else {
|
||||||
editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
switch self.location {
|
||||||
editItem.accessibilityLabel = self.presentationData.strings.Common_Edit
|
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 {
|
if case .chatList(.root) = self.location {
|
||||||
self.navigationItem.leftBarButtonItem = editItem
|
self.navigationItem.leftBarButtonItem = editItem
|
||||||
@ -1258,7 +1448,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var joined = false
|
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 {
|
for media in message.media {
|
||||||
if let action = media as? TelegramMediaAction, action.action == .peerJoined {
|
if let action = media as? TelegramMediaAction, action.action == .peerJoined {
|
||||||
joined = true
|
joined = true
|
||||||
@ -1272,7 +1462,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
chatListController.navigationPresentation = .master
|
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)
|
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)
|
strongSelf.presentInGlobalOverlay(contextController)
|
||||||
case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
|
case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _):
|
||||||
let source: ContextContentSource
|
let source: ContextContentSource
|
||||||
if let location = location {
|
if let location = location {
|
||||||
source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location))
|
source = .location(ChatListContextLocationContentSource(controller: strongSelf, location: location))
|
||||||
@ -2020,11 +2210,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func editPressed() {
|
@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))
|
let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed))
|
||||||
editItem.accessibilityLabel = self.presentationData.strings.Common_Done
|
editItem.accessibilityLabel = self.presentationData.strings.Common_Done
|
||||||
if case .chatList(.root) = self.location {
|
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 var initializedFilters = false
|
||||||
private func reloadFilters(firstUpdate: (() -> Void)? = nil) {
|
private func reloadFilters(firstUpdate: (() -> Void)? = nil) {
|
||||||
let filterItems = chatListFilterItems(context: self.context)
|
let filterItems = chatListFilterItems(context: self.context)
|
||||||
@ -3606,3 +3868,17 @@ private final class ChatListContextLocationContentSource: ContextLocationContent
|
|||||||
return ContextControllerLocationViewInfo(location: self.location, contentAreaInScreenSpace: UIScreen.main.bounds)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -213,7 +213,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
|
|||||||
)
|
)
|
||||||
let readState = EnginePeerReadCounters()
|
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] = []
|
var itemNodes: [ChatListItemNode] = []
|
||||||
|
|||||||
@ -694,7 +694,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
if isMedia {
|
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)
|
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 {
|
} 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):
|
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: {
|
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
|
return
|
||||||
}
|
}
|
||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
if let peer = peer.peer, let message = messages.first {
|
if let peer = peer.peer, let message = messages.first {
|
||||||
peerContextAction(peer, .search(message.id), node, gesture, location)
|
peerContextAction(peer, .search(message.id), node, gesture, location)
|
||||||
}
|
}
|
||||||
@ -2794,7 +2794,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
bounds = selectedItemNode.bounds
|
bounds = selectedItemNode.bounds
|
||||||
}
|
}
|
||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId)
|
return (selectedItemNode.view, bounds, messages.last?.id ?? peer.peerId)
|
||||||
case let .groupReference(groupId, _, _, _, _):
|
case let .groupReference(groupId, _, _, _, _):
|
||||||
return (selectedItemNode.view, bounds, groupId)
|
return (selectedItemNode.view, bounds, groupId)
|
||||||
@ -2982,7 +2982,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
|
|||||||
associatedMedia: [:]
|
associatedMedia: [:]
|
||||||
)
|
)
|
||||||
let readState = EnginePeerReadCounters()
|
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:
|
case .media:
|
||||||
return nil
|
return nil
|
||||||
case .links:
|
case .links:
|
||||||
|
|||||||
@ -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)
|
case groupReference(groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, unreadCount: Int, hiddenByDefault: Bool)
|
||||||
|
|
||||||
public var chatLocation: ChatLocation? {
|
public var chatLocation: ChatLocation? {
|
||||||
switch self {
|
switch self {
|
||||||
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return .peer(id: peer.peerId)
|
return .peer(id: peer.peerId)
|
||||||
case .groupReference:
|
case .groupReference:
|
||||||
return nil
|
return nil
|
||||||
@ -158,7 +158,7 @@ public class ChatListItem: ListViewItem, ChatListSearchItemNeighbour {
|
|||||||
|
|
||||||
public func selected(listView: ListView) {
|
public func selected(listView: ListView) {
|
||||||
switch self.content {
|
switch self.content {
|
||||||
case let .peer(messages, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
|
case let .peer(messages, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _):
|
||||||
if let message = messages.last, let peer = peer.peer {
|
if let message = messages.last, let peer = peer.peer {
|
||||||
var threadId: Int64?
|
var threadId: Int64?
|
||||||
if case let .forum(_, threadIdValue, _, _) = self.index {
|
if case let .forum(_, threadIdValue, _, _) = self.index {
|
||||||
@ -539,7 +539,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
result += "\n\(item.presentationData.strings.VoiceOver_Chat_UnreadMessages(Int32(allCount)))"
|
result += "\n\(item.presentationData.strings.VoiceOver_Chat_UnreadMessages(Int32(allCount)))"
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
case let .peer(_, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(_, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
guard let chatMainPeer = peer.chatMainPeer else {
|
guard let chatMainPeer = peer.chatMainPeer else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -599,7 +599,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else {
|
} else {
|
||||||
return item.presentationData.strings.VoiceOver_ChatList_MessageEmpty
|
return item.presentationData.strings.VoiceOver_ChatList_MessageEmpty
|
||||||
}
|
}
|
||||||
case let .peer(messages, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(messages, peer, _, combinedReadState, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
if let message = messages.last {
|
if let message = messages.last {
|
||||||
var result = ""
|
var result = ""
|
||||||
if message.flags.contains(.Incoming) {
|
if message.flags.contains(.Incoming) {
|
||||||
@ -792,7 +792,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
var displayAsMessage = false
|
var displayAsMessage = false
|
||||||
var enablePreview = true
|
var enablePreview = true
|
||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _):
|
case let .peer(messages, peerValue, _, _, _, _, _, _, _, _, _, _, displayAsMessageValue, _, _):
|
||||||
displayAsMessage = displayAsMessageValue
|
displayAsMessage = displayAsMessageValue
|
||||||
if displayAsMessage, case let .user(author) = messages.last?.author {
|
if displayAsMessage, case let .user(author) = messages.last?.author {
|
||||||
peer = .user(author)
|
peer = .user(author)
|
||||||
@ -819,7 +819,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else if peer.isDeleted {
|
} else if peer.isDeleted {
|
||||||
overrideImage = .deletedIcon
|
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 {
|
if peer.isPremium && peer.id != item.context.account.peerId {
|
||||||
let context = item.context
|
let context = item.context
|
||||||
@ -958,7 +962,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
guard let item = self.item, item.editing else {
|
guard let item = self.item, item.editing else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _) = item.content {
|
if case let .peer(_, peer, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _) = item.content {
|
||||||
if promoInfo == nil, let mainPeer = peer.peer {
|
if promoInfo == nil, let mainPeer = peer.peer {
|
||||||
item.interaction.togglePeerSelected(mainPeer)
|
item.interaction.togglePeerSelected(mainPeer)
|
||||||
}
|
}
|
||||||
@ -1007,12 +1011,13 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let promoInfo: ChatListNodeEntryPromoInfo?
|
let promoInfo: ChatListNodeEntryPromoInfo?
|
||||||
let displayAsMessage: Bool
|
let displayAsMessage: Bool
|
||||||
let hasFailedMessages: Bool
|
let hasFailedMessages: Bool
|
||||||
var threadInfo: EngineMessageHistoryThreads.Info?
|
var threadInfo: EngineMessageHistoryThread.Info?
|
||||||
|
var forumThreadTitle: String?
|
||||||
|
|
||||||
var groupHiddenByDefault = false
|
var groupHiddenByDefault = false
|
||||||
|
|
||||||
switch item.content {
|
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
|
messages = messagesValue
|
||||||
contentPeer = .chat(peerValue)
|
contentPeer = .chat(peerValue)
|
||||||
combinedReadState = combinedReadStateValue
|
combinedReadState = combinedReadStateValue
|
||||||
@ -1033,6 +1038,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
threadInfo = threadInfoValue
|
threadInfo = threadInfoValue
|
||||||
hasUnseenMentions = hasUnseenMentionsValue
|
hasUnseenMentions = hasUnseenMentionsValue
|
||||||
hasUnseenReactions = hasUnseenReactionsValue
|
hasUnseenReactions = hasUnseenReactionsValue
|
||||||
|
forumThreadTitle = forumThreadTitleValue
|
||||||
|
|
||||||
switch peerValue.peer {
|
switch peerValue.peer {
|
||||||
case .user, .secretChat:
|
case .user, .secretChat:
|
||||||
@ -1144,7 +1150,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let leftInset: CGFloat = params.leftInset + avatarLeftInset
|
let leftInset: CGFloat = params.leftInset + avatarLeftInset
|
||||||
|
|
||||||
enum ContentData {
|
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])
|
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
|
let messageText: String
|
||||||
if let currentChatListText = currentChatListText, currentChatListText.0 == text {
|
if let currentChatListText = currentChatListText, currentChatListText.0 == text {
|
||||||
@ -1451,7 +1461,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
switch item.content {
|
switch item.content {
|
||||||
case let .groupReference(_, _, message, _, _):
|
case let .groupReference(_, _, message, _, _):
|
||||||
topIndex = message?.index
|
topIndex = message?.index
|
||||||
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
topIndex = messages.first?.index
|
topIndex = messages.first?.index
|
||||||
}
|
}
|
||||||
if let topIndex {
|
if let topIndex {
|
||||||
@ -1588,7 +1598,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
if !isPeerGroup && !isAccountPeer {
|
if !isPeerGroup && !isAccountPeer {
|
||||||
if displayAsMessage {
|
if displayAsMessage {
|
||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
if let peer = messages.last?.author {
|
if let peer = messages.last?.author {
|
||||||
if case let .user(user) = peer, let emojiStatus = user.emojiStatus, !premiumConfiguration.isPremiumDisabled {
|
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))
|
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 peerRevealOptions: [ItemListRevealOption]
|
||||||
let peerLeftRevealOptions: [ItemListRevealOption]
|
let peerLeftRevealOptions: [ItemListRevealOption]
|
||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _):
|
case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _, _):
|
||||||
if !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 {
|
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)
|
let updatedPresence = EnginePeer.Presence(status: presence.status, lastActivity: 0)
|
||||||
@ -2547,7 +2557,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
close = false
|
close = false
|
||||||
case RevealOptionKey.delete.rawValue:
|
case RevealOptionKey.delete.rawValue:
|
||||||
var joined = false
|
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 {
|
for media in message.media {
|
||||||
if let action = media as? TelegramMediaAction, action.action == .peerJoined {
|
if let action = media as? TelegramMediaAction, action.action == .peerJoined {
|
||||||
joined = true
|
joined = true
|
||||||
@ -2583,7 +2593,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
item.interaction.toggleArchivedFolderHiddenByDefault()
|
item.interaction.toggleArchivedFolderHiddenByDefault()
|
||||||
close = false
|
close = false
|
||||||
case RevealOptionKey.hidePsa.rawValue:
|
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)
|
item.interaction.hidePsa(peer.peerId)
|
||||||
}
|
}
|
||||||
close = false
|
close = false
|
||||||
|
|||||||
@ -242,7 +242,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
nodeInteraction.additionalCategorySelected(id)
|
nodeInteraction.additionalCategorySelected(id)
|
||||||
}
|
}
|
||||||
), directionHint: entry.directionHint)
|
), 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 {
|
switch mode {
|
||||||
case .chatList:
|
case .chatList:
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
||||||
@ -265,7 +265,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
promoInfo: promoInfo,
|
promoInfo: promoInfo,
|
||||||
ignoreUnreadBadge: false,
|
ignoreUnreadBadge: false,
|
||||||
displayAsMessage: false,
|
displayAsMessage: false,
|
||||||
hasFailedMessages: hasFailedMessages
|
hasFailedMessages: hasFailedMessages,
|
||||||
|
forumThreadTitle: forumThreadTitle
|
||||||
),
|
),
|
||||||
editing: editing,
|
editing: editing,
|
||||||
hasActiveRevealControls: hasActiveRevealControls,
|
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] {
|
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||||
return entries.map { entry -> ListViewUpdateItem in
|
return entries.map { entry -> ListViewUpdateItem in
|
||||||
switch entry.entry {
|
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 {
|
switch mode {
|
||||||
case .chatList:
|
case .chatList:
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
||||||
@ -455,7 +456,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
promoInfo: promoInfo,
|
promoInfo: promoInfo,
|
||||||
ignoreUnreadBadge: false,
|
ignoreUnreadBadge: false,
|
||||||
displayAsMessage: false,
|
displayAsMessage: false,
|
||||||
hasFailedMessages: hasFailedMessages
|
hasFailedMessages: hasFailedMessages,
|
||||||
|
forumThreadTitle: forumThreadTitle
|
||||||
),
|
),
|
||||||
editing: editing,
|
editing: editing,
|
||||||
hasActiveRevealControls: hasActiveRevealControls,
|
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 (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode)
|
||||||
let entries = rawEntries.filter { entry in
|
let entries = rawEntries.filter { entry in
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _):
|
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
switch mode {
|
switch mode {
|
||||||
case .chatList:
|
case .chatList:
|
||||||
return true
|
return true
|
||||||
@ -1225,7 +1227,7 @@ public final class ChatListNode: ListView {
|
|||||||
var didIncludeHiddenByDefaultArchive = false
|
var didIncludeHiddenByDefaultArchive = false
|
||||||
if let previous = previousView {
|
if let previous = previousView {
|
||||||
for entry in previous.filteredEntries {
|
for entry in previous.filteredEntries {
|
||||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||||
if case let .chatList(chatListIndex) = index {
|
if case let .chatList(chatListIndex) = index {
|
||||||
if chatListIndex.pinningIndex != nil {
|
if chatListIndex.pinningIndex != nil {
|
||||||
previousPinnedChats.append(chatListIndex.messageIndex.id.peerId)
|
previousPinnedChats.append(chatListIndex.messageIndex.id.peerId)
|
||||||
@ -1243,7 +1245,7 @@ public final class ChatListNode: ListView {
|
|||||||
var doesIncludeArchive = false
|
var doesIncludeArchive = false
|
||||||
var doesIncludeHiddenByDefaultArchive = false
|
var doesIncludeHiddenByDefaultArchive = false
|
||||||
for entry in processedView.filteredEntries {
|
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 {
|
if case let .chatList(index) = index, index.pinningIndex != nil {
|
||||||
updatedPinnedChats.append(index.messageIndex.id.peerId)
|
updatedPinnedChats.append(index.messageIndex.id.peerId)
|
||||||
}
|
}
|
||||||
@ -1507,7 +1509,7 @@ public final class ChatListNode: ListView {
|
|||||||
var referenceId: EngineChatList.PinnedItem.Id?
|
var referenceId: EngineChatList.PinnedItem.Id?
|
||||||
var beforeAll = false
|
var beforeAll = false
|
||||||
switch toEntry {
|
switch toEntry {
|
||||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _):
|
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
|
||||||
if promoInfo != nil {
|
if promoInfo != nil {
|
||||||
beforeAll = true
|
beforeAll = true
|
||||||
} else {
|
} else {
|
||||||
@ -1534,7 +1536,7 @@ public final class ChatListNode: ListView {
|
|||||||
|
|
||||||
var itemId: EngineChatList.PinnedItem.Id?
|
var itemId: EngineChatList.PinnedItem.Id?
|
||||||
switch fromEntry {
|
switch fromEntry {
|
||||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
if case let .chatList(index) = index {
|
if case let .chatList(index) = index {
|
||||||
itemId = .peer(index.messageIndex.id.peerId)
|
itemId = .peer(index.messageIndex.id.peerId)
|
||||||
}
|
}
|
||||||
@ -1785,7 +1787,7 @@ public final class ChatListNode: ListView {
|
|||||||
if !transition.chatListView.originalList.hasLater {
|
if !transition.chatListView.originalList.hasLater {
|
||||||
for entry in filteredEntries.reversed() {
|
for entry in filteredEntries.reversed() {
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _):
|
case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _):
|
||||||
if promoInfo == nil {
|
if promoInfo == nil {
|
||||||
var hasUnread = false
|
var hasUnread = false
|
||||||
if let combinedReadState = combinedReadState {
|
if let combinedReadState = combinedReadState {
|
||||||
@ -1908,7 +1910,7 @@ public final class ChatListNode: ListView {
|
|||||||
for item in transition.insertItems {
|
for item in transition.insertItems {
|
||||||
if let item = item.item as? ChatListItem {
|
if let item = item.item as? ChatListItem {
|
||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
insertedPeerIds.append(peer.peerId)
|
insertedPeerIds.append(peer.peerId)
|
||||||
case .groupReference:
|
case .groupReference:
|
||||||
break
|
break
|
||||||
@ -2084,7 +2086,7 @@ public final class ChatListNode: ListView {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
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) {
|
if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peer.peerId) {
|
||||||
current = (index, peer.peer!, entryCount - i - 1)
|
current = (index, peer.peer!, entryCount - i - 1)
|
||||||
break outer
|
break outer
|
||||||
@ -2131,10 +2133,10 @@ public final class ChatListNode: ListView {
|
|||||||
case .previous(unread: false), .next(unread: false):
|
case .previous(unread: false), .next(unread: false):
|
||||||
var target: (EngineChatList.Item.Index, EnginePeer)? = nil
|
var target: (EngineChatList.Item.Index, EnginePeer)? = nil
|
||||||
if let current = current, entryCount > 1 {
|
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!)
|
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!)
|
previous = (index, peer.peer!)
|
||||||
}
|
}
|
||||||
if case .previous = option {
|
if case .previous = option {
|
||||||
@ -2143,7 +2145,7 @@ public final class ChatListNode: ListView {
|
|||||||
target = next
|
target = next
|
||||||
}
|
}
|
||||||
} else if entryCount > 0 {
|
} 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!)
|
target = (index, peer.peer!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2221,7 +2223,7 @@ public final class ChatListNode: ListView {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
switch chatListView.filteredEntries[entryCount - i - 1] {
|
||||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return index
|
return index
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -2237,7 +2239,7 @@ public final class ChatListNode: ListView {
|
|||||||
if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) {
|
if resultPeer == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) {
|
||||||
if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item {
|
if let itemNode = itemNode as? ChatListItemNode, let item = itemNode.item {
|
||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
resultPeer = peer.peer
|
resultPeer = peer.peer
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -47,7 +47,27 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
|
|||||||
|
|
||||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||||
case HeaderEntry
|
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 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 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)
|
case ArchiveIntro(presentationData: ChatListPresentationData)
|
||||||
@ -57,7 +77,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .HeaderEntry:
|
case .HeaderEntry:
|
||||||
return .index(.chatList(.absoluteUpperBound))
|
return .index(.chatList(.absoluteUpperBound))
|
||||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return .index(index)
|
return .index(index)
|
||||||
case let .HoleEntry(holeIndex, _):
|
case let .HoleEntry(holeIndex, _):
|
||||||
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
|
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
|
||||||
@ -74,7 +94,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .HeaderEntry:
|
case .HeaderEntry:
|
||||||
return .Header
|
return .Header
|
||||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
switch index {
|
switch index {
|
||||||
case let .chatList(index):
|
case let .chatList(index):
|
||||||
return .PeerId(index.messageIndex.id.peerId.toInt64())
|
return .PeerId(index.messageIndex.id.peerId.toInt64())
|
||||||
@ -104,9 +124,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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 {
|
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 {
|
if lhsIndex != rhsIndex {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -201,6 +221,9 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||||||
if lhsIsContact != rhsIsContact {
|
if lhsIsContact != rhsIsContact {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhsForumThreadTitle != rhsForumThreadTitle {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@ -355,7 +378,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
|||||||
inputActivities = state.peerInputActivities?.activities[peerId]
|
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 {
|
if !view.hasLater {
|
||||||
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
|
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
|
||||||
@ -388,7 +411,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
|||||||
inputActivities: nil,
|
inputActivities: nil,
|
||||||
promoInfo: nil,
|
promoInfo: nil,
|
||||||
hasFailedMessages: false,
|
hasFailedMessages: false,
|
||||||
isContact: false
|
isContact: false,
|
||||||
|
forumThreadTitle: nil
|
||||||
))
|
))
|
||||||
if foundPinningIndex != 0 {
|
if foundPinningIndex != 0 {
|
||||||
foundPinningIndex -= 1
|
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 {
|
} else {
|
||||||
if !filteredAdditionalItemEntries.isEmpty {
|
if !filteredAdditionalItemEntries.isEmpty {
|
||||||
for item in filteredAdditionalItemEntries.reversed() {
|
for item in filteredAdditionalItemEntries.reversed() {
|
||||||
@ -434,7 +458,8 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
|||||||
inputActivities: state.peerInputActivities?.activities[peerId],
|
inputActivities: state.peerInputActivities?.activities[peerId],
|
||||||
promoInfo: promoInfo,
|
promoInfo: promoInfo,
|
||||||
hasFailedMessages: item.item.hasFailed,
|
hasFailedMessages: item.item.hasFailed,
|
||||||
isContact: item.item.isContact
|
isContact: item.item.isContact,
|
||||||
|
forumThreadTitle: item.item.forumTopicTitle
|
||||||
))
|
))
|
||||||
if pinningIndex != 0 {
|
if pinningIndex != 0 {
|
||||||
pinningIndex -= 1
|
pinningIndex -= 1
|
||||||
|
|||||||
@ -179,21 +179,22 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
|
|||||||
guard let peer = view.peer else {
|
guard let peer = view.peer else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
guard let info = item.info.get(EngineMessageHistoryThreads.Info.self) else {
|
guard let data = item.info.get(MessageHistoryThreadData.self) else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
items.append(EngineChatList.Item(
|
items.append(EngineChatList.Item(
|
||||||
id: .forum(item.id),
|
id: .forum(item.id),
|
||||||
index: .forum(timestamp: item.index.timestamp, threadId: item.id, namespace: item.index.id.namespace, id: item.index.id.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)] } ?? [],
|
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,
|
isMuted: false,
|
||||||
draft: nil,
|
draft: nil,
|
||||||
threadInfo: info,
|
threadInfo: data.info,
|
||||||
renderedPeer: EngineRenderedPeer(peer: EnginePeer(peer)),
|
renderedPeer: EngineRenderedPeer(peer: EnginePeer(peer)),
|
||||||
presence: nil,
|
presence: nil,
|
||||||
hasUnseenMentions: false,
|
hasUnseenMentions: false,
|
||||||
hasUnseenReactions: false,
|
hasUnseenReactions: false,
|
||||||
|
forumTopicTitle: nil,
|
||||||
hasFailed: false,
|
hasFailed: false,
|
||||||
isContact: false
|
isContact: false
|
||||||
))
|
))
|
||||||
|
|||||||
@ -1265,7 +1265,7 @@ public final class ContactListNode: ASDisplayNode {
|
|||||||
return context.engine.data.get(EngineDataMap(
|
return context.engine.data.get(EngineDataMap(
|
||||||
view.entries.compactMap { entry -> EnginePeer.Id? in
|
view.entries.compactMap { entry -> EnginePeer.Id? in
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _):
|
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _):
|
||||||
if let peer = renderedPeer.peer {
|
if let peer = renderedPeer.peer {
|
||||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||||
return peer.id
|
return peer.id
|
||||||
@ -1281,7 +1281,7 @@ public final class ContactListNode: ASDisplayNode {
|
|||||||
var peers: [(EnginePeer, Int32)] = []
|
var peers: [(EnginePeer, Int32)] = []
|
||||||
for entry in view.entries {
|
for entry in view.entries {
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _):
|
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _):
|
||||||
if let peer = renderedPeer.peer {
|
if let peer = renderedPeer.peer {
|
||||||
if peer is TelegramGroup {
|
if peer is TelegramGroup {
|
||||||
peers.append((EnginePeer(peer), 0))
|
peers.append((EnginePeer(peer), 0))
|
||||||
|
|||||||
@ -101,12 +101,12 @@ public struct ChatListGroupReferenceEntry: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum ChatListEntry: Comparable {
|
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)
|
case HoleEntry(ChatListHole)
|
||||||
|
|
||||||
public var index: ChatListIndex {
|
public var index: ChatListIndex {
|
||||||
switch self {
|
switch self {
|
||||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _):
|
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
|
||||||
return index
|
return index
|
||||||
case let .HoleEntry(hole):
|
case let .HoleEntry(hole):
|
||||||
return ChatListIndex(pinningIndex: nil, messageIndex: hole.index)
|
return ChatListIndex(pinningIndex: nil, messageIndex: hole.index)
|
||||||
@ -115,9 +115,9 @@ public enum ChatListEntry: Comparable {
|
|||||||
|
|
||||||
public static func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool {
|
public static func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool {
|
||||||
switch lhs {
|
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 {
|
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 {
|
if lhsIndex != rhsIndex {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -155,6 +155,9 @@ public enum ChatListEntry: Comparable {
|
|||||||
if lhsInfo != rhsInfo {
|
if lhsInfo != rhsInfo {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhsForumTopicData != rhsForumTopicData {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhsHasFailed != rhsHasFailed {
|
if lhsHasFailed != rhsHasFailed {
|
||||||
return false
|
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 {
|
enum MutableChatListEntry: Equatable {
|
||||||
case IntermediateMessageEntry(index: ChatListIndex, messageIndex: MessageIndex?)
|
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)
|
case HoleEntry(ChatListHole)
|
||||||
|
|
||||||
init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) {
|
init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) {
|
||||||
@ -214,7 +200,7 @@ enum MutableChatListEntry: Equatable {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .IntermediateMessageEntry(index, _):
|
case let .IntermediateMessageEntry(index, _):
|
||||||
return index
|
return index
|
||||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
|
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return index
|
return index
|
||||||
case let .HoleEntry(hole):
|
case let .HoleEntry(hole):
|
||||||
return ChatListIndex(pinningIndex: nil, messageIndex: hole.index)
|
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:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -684,8 +675,8 @@ public final class ChatListView {
|
|||||||
var entries: [ChatListEntry] = []
|
var entries: [ChatListEntry] = []
|
||||||
for entry in mutableView.sampledState.entries {
|
for entry in mutableView.sampledState.entries {
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, hasFailed, 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, hasFailed: hasFailed, isContact: 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):
|
case let .HoleEntry(hole):
|
||||||
entries.append(.HoleEntry(hole))
|
entries.append(.HoleEntry(hole))
|
||||||
case .IntermediateMessageEntry:
|
case .IntermediateMessageEntry:
|
||||||
@ -702,9 +693,9 @@ public final class ChatListView {
|
|||||||
var additionalItemEntries: [ChatListAdditionalItemEntry] = []
|
var additionalItemEntries: [ChatListAdditionalItemEntry] = []
|
||||||
for entry in mutableView.additionalItemEntries {
|
for entry in mutableView.additionalItemEntries {
|
||||||
switch entry.entry {
|
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(
|
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
|
info: entry.info
|
||||||
))
|
))
|
||||||
case .HoleEntry:
|
case .HoleEntry:
|
||||||
|
|||||||
@ -502,7 +502,7 @@ private final class ChatListViewSpaceState {
|
|||||||
let entryPeer: Peer
|
let entryPeer: Peer
|
||||||
let entryNotificationsPeerId: PeerId
|
let entryNotificationsPeerId: PeerId
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _):
|
case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _, _):
|
||||||
if let peer = renderedPeer.peer {
|
if let peer = renderedPeer.peer {
|
||||||
entryPeer = peer
|
entryPeer = peer
|
||||||
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
||||||
@ -604,13 +604,13 @@ private final class ChatListViewSpaceState {
|
|||||||
|
|
||||||
if self.orderedEntries.mutableScan({ entry in
|
if self.orderedEntries.mutableScan({ entry in
|
||||||
switch entry {
|
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 {
|
if let peer = renderedPeer.peer {
|
||||||
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
||||||
if let (_, updated) = transaction.currentUpdatedPeerNotificationSettings[notificationsPeerId] {
|
if let (_, updated) = transaction.currentUpdatedPeerNotificationSettings[notificationsPeerId] {
|
||||||
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: updated)
|
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 {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -628,7 +628,7 @@ private final class ChatListViewSpaceState {
|
|||||||
if !transaction.updatedFailedMessagePeerIds.isEmpty {
|
if !transaction.updatedFailedMessagePeerIds.isEmpty {
|
||||||
if self.orderedEntries.mutableScan({ entry in
|
if self.orderedEntries.mutableScan({ entry in
|
||||||
switch entry {
|
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) {
|
if transaction.updatedFailedMessagePeerIds.contains(index.messageIndex.id.peerId) {
|
||||||
return .MessageEntry(
|
return .MessageEntry(
|
||||||
index: index,
|
index: index,
|
||||||
@ -640,6 +640,7 @@ private final class ChatListViewSpaceState {
|
|||||||
renderedPeer: renderedPeer,
|
renderedPeer: renderedPeer,
|
||||||
presence: presence,
|
presence: presence,
|
||||||
tagSummaryInfo: tagSummaryInfo,
|
tagSummaryInfo: tagSummaryInfo,
|
||||||
|
forumTopicData: forumTopicData,
|
||||||
hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId),
|
hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId),
|
||||||
isContact: isContact
|
isContact: isContact
|
||||||
)
|
)
|
||||||
@ -657,7 +658,7 @@ private final class ChatListViewSpaceState {
|
|||||||
if !transaction.currentUpdatedPeers.isEmpty {
|
if !transaction.currentUpdatedPeers.isEmpty {
|
||||||
if self.orderedEntries.mutableScan({ entry in
|
if self.orderedEntries.mutableScan({ entry in
|
||||||
switch entry {
|
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 updatedMessages: [Message] = messages
|
||||||
var hasUpdatedMessages = false
|
var hasUpdatedMessages = false
|
||||||
for i in 0 ..< updatedMessages.count {
|
for i in 0 ..< updatedMessages.count {
|
||||||
@ -679,6 +680,7 @@ private final class ChatListViewSpaceState {
|
|||||||
renderedPeer: renderedPeer ?? entryRenderedPeer,
|
renderedPeer: renderedPeer ?? entryRenderedPeer,
|
||||||
presence: presence,
|
presence: presence,
|
||||||
tagSummaryInfo: tagSummaryInfo,
|
tagSummaryInfo: tagSummaryInfo,
|
||||||
|
forumTopicData: forumTopicData,
|
||||||
hasFailedMessages: hasFailedMessages,
|
hasFailedMessages: hasFailedMessages,
|
||||||
isContact: isContact)
|
isContact: isContact)
|
||||||
} else {
|
} else {
|
||||||
@ -695,7 +697,7 @@ private final class ChatListViewSpaceState {
|
|||||||
if !transaction.currentUpdatedPeerPresences.isEmpty {
|
if !transaction.currentUpdatedPeerPresences.isEmpty {
|
||||||
if self.orderedEntries.mutableScan({ entry in
|
if self.orderedEntries.mutableScan({ entry in
|
||||||
switch entry {
|
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
|
var presencePeerId = entryRenderedPeer.peerId
|
||||||
if let peer = entryRenderedPeer.peers[entryRenderedPeer.peerId], let associatedPeerId = peer.associatedPeerId {
|
if let peer = entryRenderedPeer.peers[entryRenderedPeer.peerId], let associatedPeerId = peer.associatedPeerId {
|
||||||
presencePeerId = associatedPeerId
|
presencePeerId = associatedPeerId
|
||||||
@ -711,6 +713,7 @@ private final class ChatListViewSpaceState {
|
|||||||
renderedPeer: entryRenderedPeer,
|
renderedPeer: entryRenderedPeer,
|
||||||
presence: presence,
|
presence: presence,
|
||||||
tagSummaryInfo: tagSummaryInfo,
|
tagSummaryInfo: tagSummaryInfo,
|
||||||
|
forumTopicData: forumTopicData,
|
||||||
hasFailedMessages: hasFailedMessages,
|
hasFailedMessages: hasFailedMessages,
|
||||||
isContact: isContact
|
isContact: isContact
|
||||||
)
|
)
|
||||||
@ -731,7 +734,7 @@ private final class ChatListViewSpaceState {
|
|||||||
let entryPeer: Peer
|
let entryPeer: Peer
|
||||||
let entryNotificationsPeerId: PeerId
|
let entryNotificationsPeerId: PeerId
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _):
|
case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _, _):
|
||||||
if let peer = entryRenderedPeer.peer {
|
if let peer = entryRenderedPeer.peer {
|
||||||
entryPeer = peer
|
entryPeer = peer
|
||||||
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
||||||
@ -842,7 +845,7 @@ private final class ChatListViewSpaceState {
|
|||||||
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty {
|
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty {
|
||||||
if self.orderedEntries.mutableScan({ entry in
|
if self.orderedEntries.mutableScan({ entry in
|
||||||
switch entry {
|
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 updatedChatListMessageTagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = tagSummaryInfo
|
||||||
var didUpdateSummaryInfo = false
|
var didUpdateSummaryInfo = false
|
||||||
|
|
||||||
@ -883,6 +886,7 @@ private final class ChatListViewSpaceState {
|
|||||||
renderedPeer: entryRenderedPeer,
|
renderedPeer: entryRenderedPeer,
|
||||||
presence: presence,
|
presence: presence,
|
||||||
tagSummaryInfo: updatedChatListMessageTagSummaryInfo,
|
tagSummaryInfo: updatedChatListMessageTagSummaryInfo,
|
||||||
|
forumTopicData: forumTopicData,
|
||||||
hasFailedMessages: hasFailedMessages,
|
hasFailedMessages: hasFailedMessages,
|
||||||
isContact: isContact
|
isContact: isContact
|
||||||
)
|
)
|
||||||
@ -1018,7 +1022,7 @@ private extension MutableChatListEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .IntermediateMessageEntry(index, _):
|
case let .IntermediateMessageEntry(index, _):
|
||||||
return index.messageIndex.id.peerId
|
return index.messageIndex.id.peerId
|
||||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
|
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return index.messageIndex.id.peerId
|
return index.messageIndex.id.peerId
|
||||||
case .HoleEntry:
|
case .HoleEntry:
|
||||||
return nil
|
return nil
|
||||||
@ -1029,7 +1033,7 @@ private extension MutableChatListEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .IntermediateMessageEntry(index, _):
|
case let .IntermediateMessageEntry(index, _):
|
||||||
return MutableChatListEntryIndex(index: index, isMessage: true)
|
return MutableChatListEntryIndex(index: index, isMessage: true)
|
||||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
|
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return MutableChatListEntryIndex(index: index, isMessage: true)
|
return MutableChatListEntryIndex(index: index, isMessage: true)
|
||||||
case let .HoleEntry(hole):
|
case let .HoleEntry(hole):
|
||||||
return MutableChatListEntryIndex(index: ChatListIndex(pinningIndex: nil, messageIndex: hole.index), isMessage: false)
|
return MutableChatListEntryIndex(index: ChatListIndex(pinningIndex: nil, messageIndex: hole.index), isMessage: false)
|
||||||
@ -1040,7 +1044,7 @@ private extension MutableChatListEntry {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .IntermediateMessageEntry(index, _):
|
case let .IntermediateMessageEntry(index, _):
|
||||||
return .peer(index.messageIndex.id.peerId)
|
return .peer(index.messageIndex.id.peerId)
|
||||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _):
|
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return .peer(index.messageIndex.id.peerId)
|
return .peer(index.messageIndex.id.peerId)
|
||||||
case let .HoleEntry(hole):
|
case let .HoleEntry(hole):
|
||||||
return .hole(hole.index)
|
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 {
|
if directionIndex == 0 {
|
||||||
self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry)
|
self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -590,7 +590,8 @@ final class MediaBoxPartialFile {
|
|||||||
} else {
|
} else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
}
|
}
|
||||||
} catch {
|
} catch let e {
|
||||||
|
postboxLog("moveLocalFile error: \(e)")
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,3 +129,51 @@ public final class MessageHistoryThreadIndexView: PostboxView {
|
|||||||
self.items = items
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -110,6 +110,26 @@ class MessageHistoryThreadIndexTable: Table {
|
|||||||
return self.lowerBound(peerId: peerId).successor
|
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) {
|
func set(peerId: PeerId, threadId: Int64, info: CodableEntry) {
|
||||||
self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] = info
|
self.updatedInfoItems[MessageHistoryThreadsTable.ItemId(peerId: peerId, threadId: threadId)] = info
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1164,6 +1164,11 @@ public final class Transaction {
|
|||||||
return self.postbox!.messageHistoryThreadIndexTable.getAll(peerId: peerId)
|
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) {
|
public func setMessageHistoryThreadInfo(peerId: PeerId, threadId: Int64, info: CodableEntry) {
|
||||||
assert(!self.disposed)
|
assert(!self.disposed)
|
||||||
self.postbox!.messageHistoryThreadIndexTable.set(peerId: peerId, threadId: threadId, info: info)
|
self.postbox!.messageHistoryThreadIndexTable.set(peerId: peerId, threadId: threadId, info: info)
|
||||||
|
|||||||
@ -39,6 +39,7 @@ public enum PostboxViewKey: Hashable {
|
|||||||
case chatListIndex(id: PeerId)
|
case chatListIndex(id: PeerId)
|
||||||
case peerTimeoutAttributes
|
case peerTimeoutAttributes
|
||||||
case messageHistoryThreadIndex(id: PeerId)
|
case messageHistoryThreadIndex(id: PeerId)
|
||||||
|
case messageHistoryThreadInfo(peerId: PeerId, threadId: Int64)
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -127,6 +128,9 @@ public enum PostboxViewKey: Hashable {
|
|||||||
hasher.combine(17)
|
hasher.combine(17)
|
||||||
case let .messageHistoryThreadIndex(id):
|
case let .messageHistoryThreadIndex(id):
|
||||||
hasher.combine(id)
|
hasher.combine(id)
|
||||||
|
case let .messageHistoryThreadInfo(peerId, threadId):
|
||||||
|
hasher.combine(peerId)
|
||||||
|
hasher.combine(threadId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +364,12 @@ public enum PostboxViewKey: Hashable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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)
|
return MutablePeerTimeoutAttributesView(postbox: postbox)
|
||||||
case let .messageHistoryThreadIndex(id):
|
case let .messageHistoryThreadIndex(id):
|
||||||
return MutableMessageHistoryThreadIndexView(postbox: postbox, peerId: id)
|
return MutableMessageHistoryThreadIndexView(postbox: postbox, peerId: id)
|
||||||
|
case let .messageHistoryThreadInfo(peerId, threadId):
|
||||||
|
return MutableMessageHistoryThreadInfoView(postbox: postbox, peerId: peerId, threadId: threadId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -282,7 +282,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
|||||||
promoInfo: nil,
|
promoInfo: nil,
|
||||||
ignoreUnreadBadge: false,
|
ignoreUnreadBadge: false,
|
||||||
displayAsMessage: false,
|
displayAsMessage: false,
|
||||||
hasFailedMessages: false
|
hasFailedMessages: false,
|
||||||
|
forumThreadTitle: nil
|
||||||
),
|
),
|
||||||
editing: false,
|
editing: false,
|
||||||
hasActiveRevealControls: false,
|
hasActiveRevealControls: false,
|
||||||
|
|||||||
@ -902,7 +902,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
|||||||
promoInfo: nil,
|
promoInfo: nil,
|
||||||
ignoreUnreadBadge: false,
|
ignoreUnreadBadge: false,
|
||||||
displayAsMessage: false,
|
displayAsMessage: false,
|
||||||
hasFailedMessages: false
|
hasFailedMessages: false,
|
||||||
|
forumThreadTitle: nil
|
||||||
),
|
),
|
||||||
editing: false,
|
editing: false,
|
||||||
hasActiveRevealControls: false,
|
hasActiveRevealControls: false,
|
||||||
|
|||||||
@ -425,7 +425,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
promoInfo: nil,
|
promoInfo: nil,
|
||||||
ignoreUnreadBadge: false,
|
ignoreUnreadBadge: false,
|
||||||
displayAsMessage: false,
|
displayAsMessage: false,
|
||||||
hasFailedMessages: false
|
hasFailedMessages: false,
|
||||||
|
forumThreadTitle: nil
|
||||||
),
|
),
|
||||||
editing: false,
|
editing: false,
|
||||||
hasActiveRevealControls: false,
|
hasActiveRevealControls: false,
|
||||||
|
|||||||
@ -991,7 +991,7 @@ public final class ShareController: ViewController {
|
|||||||
var peers: [EngineRenderedPeer] = []
|
var peers: [EngineRenderedPeer] = []
|
||||||
for entry in view.0.entries.reversed() {
|
for entry in view.0.entries.reversed() {
|
||||||
switch entry {
|
switch entry {
|
||||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _):
|
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _):
|
||||||
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) {
|
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) {
|
||||||
peers.append(EngineRenderedPeer(renderedPeer))
|
peers.append(EngineRenderedPeer(renderedPeer))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,6 +117,7 @@ enum AccountStateMutationOperation {
|
|||||||
case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String)
|
case UpdateAudioTranscription(messageId: MessageId, id: Int64, isPending: Bool, text: String)
|
||||||
case UpdateConfig
|
case UpdateConfig
|
||||||
case UpdateExtendedMedia(MessageId, Api.MessageExtendedMedia)
|
case UpdateExtendedMedia(MessageId, Api.MessageExtendedMedia)
|
||||||
|
case ResetForumTopic(topicId: MessageId, data: MessageHistoryThreadData, pts: Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HoleFromPreviousState {
|
struct HoleFromPreviousState {
|
||||||
@ -151,6 +152,8 @@ struct AccountMutableState {
|
|||||||
var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]]
|
var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]]
|
||||||
var updatedOutgoingUniqueMessageIds: [Int64: Int32]
|
var updatedOutgoingUniqueMessageIds: [Int64: Int32]
|
||||||
|
|
||||||
|
var resetForumTopicLists: [PeerId: [MessageHistoryThreadData]] = [:]
|
||||||
|
|
||||||
var storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>]
|
var storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>]
|
||||||
var displayAlerts: [(text: String, isDropAuth: Bool)] = []
|
var displayAlerts: [(text: String, isDropAuth: Bool)] = []
|
||||||
var dismissBotWebViews: [Int64] = []
|
var dismissBotWebViews: [Int64] = []
|
||||||
@ -228,6 +231,8 @@ struct AccountMutableState {
|
|||||||
self.updatedOutgoingUniqueMessageIds.merge(other.updatedOutgoingUniqueMessageIds, uniquingKeysWith: { lhs, _ in lhs })
|
self.updatedOutgoingUniqueMessageIds.merge(other.updatedOutgoingUniqueMessageIds, uniquingKeysWith: { lhs, _ in lhs })
|
||||||
self.displayAlerts.append(contentsOf: other.displayAlerts)
|
self.displayAlerts.append(contentsOf: other.displayAlerts)
|
||||||
self.dismissBotWebViews.append(contentsOf: other.dismissBotWebViews)
|
self.dismissBotWebViews.append(contentsOf: other.dismissBotWebViews)
|
||||||
|
|
||||||
|
self.resetForumTopicLists.merge(other.resetForumTopicLists, uniquingKeysWith: { lhs, _ in lhs })
|
||||||
}
|
}
|
||||||
|
|
||||||
mutating func addPreCachedResource(_ resource: MediaResource, data: Data) {
|
mutating func addPreCachedResource(_ resource: MediaResource, data: Data) {
|
||||||
@ -530,7 +535,7 @@ struct AccountMutableState {
|
|||||||
|
|
||||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||||
switch operation {
|
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
|
break
|
||||||
case let .AddMessages(messages, location):
|
case let .AddMessages(messages, location):
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import SwiftSignalKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
|
|
||||||
public final class EngineMessageHistoryThreads {
|
public extension EngineMessageHistoryThread {
|
||||||
public final class Info: Equatable, Codable {
|
final class Info: Equatable, Codable {
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case title
|
case title
|
||||||
case icon
|
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> {
|
func _internal_setChannelForumMode(account: Account, peerId: PeerId, isForum: Bool) -> Signal<Never, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||||
return transaction.getPeer(peerId).flatMap(apiInputChannel)
|
return transaction.getPeer(peerId).flatMap(apiInputChannel)
|
||||||
@ -130,8 +142,18 @@ func _internal_loadMessageHistoryThreads(account: Account, peerId: PeerId) -> Si
|
|||||||
|
|
||||||
for topic in topics {
|
for topic in topics {
|
||||||
switch topic {
|
switch topic {
|
||||||
case let .forumTopic(_, id, _, title, iconEmojiId, _, _, _, _):
|
case let .forumTopic(_, id, _, title, iconEmojiId, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount):
|
||||||
guard let info = CodableEntry(EngineMessageHistoryThreads.Info(title: title, icon: iconEmojiId)) else {
|
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
|
continue
|
||||||
}
|
}
|
||||||
transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: Int64(id), info: info)
|
transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: Int64(id), info: info)
|
||||||
@ -161,7 +183,7 @@ public final class ForumChannelTopics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let loadMoreDisposable = MetaDisposable()
|
private let loadMoreDisposable = MetaDisposable()
|
||||||
private let createTopicDisposable = MetaDisposable()
|
private let updateDisposable = MetaDisposable()
|
||||||
|
|
||||||
init(queue: Queue, account: Account, peerId: PeerId) {
|
init(queue: Queue, account: Account, peerId: PeerId) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
@ -177,30 +199,32 @@ public final class ForumChannelTopics {
|
|||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
return State(items: view.items.compactMap { item -> ForumChannelTopics.Item? in
|
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 nil
|
||||||
}
|
}
|
||||||
return ForumChannelTopics.Item(
|
return ForumChannelTopics.Item(
|
||||||
id: item.id,
|
id: item.id,
|
||||||
info: info,
|
info: data.info,
|
||||||
index: item.index,
|
index: item.index,
|
||||||
topMessage: item.topMessage.flatMap(EngineMessage.init)
|
topMessage: item.topMessage.flatMap(EngineMessage.init)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.updateDisposable.set(account.viewTracker.polledChannel(peerId: peerId).start())
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
self.loadMoreDisposable.dispose()
|
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 peerId = self.peerId
|
||||||
let account = self.account
|
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 fileId: Int64? = nil
|
||||||
|
|
||||||
var filteredFiles: [TelegramMediaFile] = []
|
var filteredFiles: [TelegramMediaFile] = []
|
||||||
@ -223,9 +247,10 @@ public final class ForumChannelTopics {
|
|||||||
|
|
||||||
return (transaction.getPeer(peerId).flatMap(apiInputChannel), fileId)
|
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 {
|
guard let inputChannel = inputChannel else {
|
||||||
return .single(nil)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
if fileId != nil {
|
if fileId != nil {
|
||||||
@ -239,37 +264,46 @@ public final class ForumChannelTopics {
|
|||||||
randomId: Int64.random(in: Int64.min ..< Int64.max),
|
randomId: Int64.random(in: Int64.min ..< Int64.max),
|
||||||
sendAs: nil
|
sendAs: nil
|
||||||
))
|
))
|
||||||
|> map(Optional.init)
|
|> mapError { _ -> CreateForumChannelTopicError in
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
return .generic
|
||||||
return .single(nil)
|
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<Int32?, NoError> in
|
|> mapToSignal { result -> Signal<Int64, CreateForumChannelTopicError> in
|
||||||
guard let result = result else {
|
|
||||||
return .single(nil)
|
|
||||||
}
|
|
||||||
account.stateManager.addUpdates(result)
|
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 struct Item: Equatable {
|
||||||
public var id: Int64
|
public var id: Int64
|
||||||
public var info: EngineMessageHistoryThreads.Info
|
public var info: EngineMessageHistoryThread.Info
|
||||||
public var index: MessageIndex
|
public var index: MessageIndex
|
||||||
public var topMessage: EngineMessage?
|
public var topMessage: EngineMessage?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
id: Int64,
|
id: Int64,
|
||||||
info: EngineMessageHistoryThreads.Info,
|
info: EngineMessageHistoryThread.Info,
|
||||||
index: MessageIndex,
|
index: MessageIndex,
|
||||||
topMessage: EngineMessage?
|
topMessage: EngineMessage?
|
||||||
) {
|
) {
|
||||||
@ -311,9 +345,19 @@ public final class ForumChannelTopics {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public func createTopic(title: String) {
|
public func createTopic(title: String) -> Signal<Int64, CreateForumChannelTopicError> {
|
||||||
self.impl.with { impl in
|
return Signal { subscriber in
|
||||||
impl.createTopic(title: title)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1586,14 +1586,15 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
|||||||
} else {
|
} else {
|
||||||
for peerId in channelsToPoll.union(missingUpdatesFromChannels) {
|
for peerId in channelsToPoll.union(missingUpdatesFromChannels) {
|
||||||
if let peer = updatedState.peers[peerId] {
|
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 {
|
} else {
|
||||||
Logger.shared.log("State", "can't poll channel \(peerId): no peer found")
|
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 finalState: AccountMutableState = updatedState
|
||||||
var hadError = false
|
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 resolveForumThreads(postbox: postbox, network: network, state: finalState)
|
||||||
return resolveMissingPeerChatInfos(network: network, state: resultingState)
|
|> mapToSignal { finalState in
|
||||||
|> map { resultingState, resolveError -> AccountFinalState in
|
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
||||||
return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError || resolveError, incomplete: missingUpdates, missingUpdatesFromChannels: Set(), discard: resolveError)
|
|> 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>) {
|
func extractEmojiFileIds(message: StoreMessage, fileIds: inout Set<Int64>) {
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
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)
|
let missingMessageIds = state.referencedMessageIds.subtracting(state.storedMessages)
|
||||||
if missingMessageIds.isEmpty {
|
if missingMessageIds.isEmpty {
|
||||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), reactions: reactionsFromState(state), result: state)
|
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 {
|
} else {
|
||||||
var missingPeers = false
|
var missingPeers = false
|
||||||
let _ = missingPeers
|
let _ = missingPeers
|
||||||
@ -1735,6 +1930,9 @@ private func resolveAssociatedMessages(postbox: Postbox, network: Network, state
|
|||||||
}
|
}
|
||||||
|> mapToSignal { updatedState -> Signal<AccountMutableState, NoError> in
|
|> mapToSignal { updatedState -> Signal<AccountMutableState, NoError> in
|
||||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: updatedState), reactions: reactionsFromState(updatedState), result: updatedState)
|
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: [:])
|
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
|
|> mapToSignal { (finalState, _, timeout) -> Signal<Int32, NoError> in
|
||||||
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
||||||
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
|> 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: [:])
|
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
|
|> mapToSignal { (finalState, _, timeout) -> Signal<Never, NoError> in
|
||||||
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
return resolveAssociatedMessages(postbox: postbox, network: network, state: finalState)
|
||||||
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
|
|> 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) {
|
if let inputChannel = apiInputChannel(peer) {
|
||||||
let limit: Int32
|
let limit: Int32
|
||||||
limit = 100
|
limit = 100
|
||||||
@ -2154,161 +2352,191 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|> retryRequest
|
|> retryRequest
|
||||||
|> map { difference -> (AccountMutableState, Bool, Int32?) in
|
|> mapToSignal { difference -> Signal<(AccountMutableState, Bool, Int32?), NoError> in
|
||||||
var updatedState = state
|
guard let difference = difference else {
|
||||||
var apiTimeout: Int32?
|
return .single((state, false, nil))
|
||||||
if let difference = difference {
|
}
|
||||||
switch difference {
|
|
||||||
case let .channelDifference(_, pts, timeout, newMessages, otherUpdates, chats, users):
|
switch difference {
|
||||||
apiTimeout = timeout
|
case let .channelDifference(_, pts, timeout, newMessages, otherUpdates, chats, users):
|
||||||
let channelPts: Int32
|
var updatedState = state
|
||||||
if let _ = updatedState.channelStates[peer.id] {
|
var apiTimeout: Int32?
|
||||||
channelPts = pts
|
|
||||||
} else {
|
apiTimeout = timeout
|
||||||
channelPts = pts
|
let channelPts: Int32
|
||||||
}
|
if let _ = updatedState.channelStates[peer.id] {
|
||||||
updatedState.updateChannelState(peer.id, pts: channelPts)
|
channelPts = pts
|
||||||
|
} else {
|
||||||
updatedState.mergeChats(chats)
|
channelPts = pts
|
||||||
updatedState.mergeUsers(users)
|
}
|
||||||
|
updatedState.updateChannelState(peer.id, pts: channelPts)
|
||||||
for apiMessage in newMessages {
|
|
||||||
if var message = StoreMessage(apiMessage: apiMessage) {
|
updatedState.mergeChats(chats)
|
||||||
var attributes = message.attributes
|
updatedState.mergeUsers(users)
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
|
|
||||||
message = message.withUpdatedAttributes(attributes)
|
var forumThreadIds = Set<MessageId>()
|
||||||
|
|
||||||
if let preCachedResources = apiMessage.preCachedResources {
|
for apiMessage in newMessages {
|
||||||
for (resource, data) in preCachedResources {
|
if var message = StoreMessage(apiMessage: apiMessage) {
|
||||||
updatedState.addPreCachedResource(resource, data: data)
|
var attributes = message.attributes
|
||||||
}
|
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
|
||||||
}
|
message = message.withUpdatedAttributes(attributes)
|
||||||
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
|
||||||
if case let .Id(id) = message.id {
|
if let preCachedResources = apiMessage.preCachedResources {
|
||||||
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
|
for (resource, data) in preCachedResources {
|
||||||
}
|
updatedState.addPreCachedResource(resource, data: data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for update in otherUpdates {
|
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
||||||
switch update {
|
if case let .Id(id) = message.id {
|
||||||
case let .updateDeleteChannelMessages(_, messages, _, _):
|
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
|
||||||
let peerId = peer.id
|
|
||||||
updatedState.deleteMessages(messages.map({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }))
|
if let threadId = message.threadId {
|
||||||
case let .updateEditChannelMessage(apiMessage, _, _):
|
if let channel = updatedState.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) {
|
||||||
if let message = StoreMessage(apiMessage: apiMessage), case let .Id(messageId) = message.id, messageId.peerId == peer.id {
|
forumThreadIds.insert(MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: Int32(clamping: threadId)))
|
||||||
if let preCachedResources = apiMessage.preCachedResources {
|
}
|
||||||
for (resource, data) in preCachedResources {
|
}
|
||||||
updatedState.addPreCachedResource(resource, data: data)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var attributes = message.attributes
|
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
|
for update in otherUpdates {
|
||||||
updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes))
|
switch update {
|
||||||
} else {
|
case let .updateDeleteChannelMessages(_, messages, _, _):
|
||||||
Logger.shared.log("State", "Invalid updateEditChannelMessage")
|
let peerId = peer.id
|
||||||
}
|
updatedState.deleteMessages(messages.map({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }))
|
||||||
case let .updatePinnedChannelMessages(flags, channelId, messages, _, _):
|
case let .updateEditChannelMessage(apiMessage, _, _):
|
||||||
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
|
if let message = StoreMessage(apiMessage: apiMessage), case let .Id(messageId) = message.id, messageId.peerId == peer.id {
|
||||||
updatedState.updateMessagesPinned(ids: messages.map { id in
|
if let preCachedResources = apiMessage.preCachedResources {
|
||||||
MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: id)
|
for (resource, data) in preCachedResources {
|
||||||
}, pinned: (flags & (1 << 0)) != 0)
|
updatedState.addPreCachedResource(resource, data: data)
|
||||||
case let .updateChannelReadMessagesContents(_, messages):
|
}
|
||||||
updatedState.addReadMessagesContents((peer.id, messages))
|
}
|
||||||
case let .updateChannelMessageViews(_, id, views):
|
var attributes = message.attributes
|
||||||
updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)
|
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
|
||||||
/*case let .updateChannelMessageForwards(_, id, views):
|
updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes))
|
||||||
updatedState.addUpdateMessageForwardsCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)*/
|
|
||||||
case let .updateChannelWebPage(_, apiWebpage, _, _):
|
if let threadId = message.threadId {
|
||||||
switch apiWebpage {
|
if let channel = updatedState.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info, channel.flags.contains(.isForum) {
|
||||||
case let .webPageEmpty(id):
|
forumThreadIds.insert(MessageId(peerId: message.id.peerId, namespace: message.id.namespace, id: Int32(clamping: threadId)))
|
||||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
}
|
||||||
default:
|
}
|
||||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
} else {
|
||||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
Logger.shared.log("State", "Invalid updateEditChannelMessage")
|
||||||
}
|
}
|
||||||
}
|
case let .updatePinnedChannelMessages(flags, channelId, messages, _, _):
|
||||||
case let .updateChannelAvailableMessages(_, minId):
|
let channelPeerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
|
||||||
let messageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: minId)
|
updatedState.updateMessagesPinned(ids: messages.map { id in
|
||||||
updatedState.updateMinAvailableMessage(messageId)
|
MessageId(peerId: channelPeerId, namespace: Namespaces.Message.Cloud, id: id)
|
||||||
updatedState.updateCachedPeerData(peer.id, { current in
|
}, pinned: (flags & (1 << 0)) != 0)
|
||||||
let previous: CachedChannelData
|
case let .updateChannelReadMessagesContents(_, messages):
|
||||||
if let current = current as? CachedChannelData {
|
updatedState.addReadMessagesContents((peer.id, messages))
|
||||||
previous = current
|
case let .updateChannelMessageViews(_, id, views):
|
||||||
} else {
|
updatedState.addUpdateMessageImpressionCount(id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: id), count: views)
|
||||||
previous = CachedChannelData()
|
case let .updateChannelWebPage(_, apiWebpage, _, _):
|
||||||
}
|
switch apiWebpage {
|
||||||
return previous.withUpdatedMinAvailableMessageId(messageId)
|
case let .webPageEmpty(id):
|
||||||
})
|
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||||
default:
|
default:
|
||||||
break
|
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
||||||
}
|
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||||
}
|
}
|
||||||
case let .channelDifferenceEmpty(_, pts, timeout):
|
}
|
||||||
apiTimeout = timeout
|
case let .updateChannelAvailableMessages(_, minId):
|
||||||
|
let messageId = MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: minId)
|
||||||
let channelPts: Int32
|
updatedState.updateMinAvailableMessage(messageId)
|
||||||
if let _ = updatedState.channelStates[peer.id] {
|
updatedState.updateCachedPeerData(peer.id, { current in
|
||||||
channelPts = pts
|
let previous: CachedChannelData
|
||||||
} else {
|
if let current = current as? CachedChannelData {
|
||||||
channelPts = pts
|
previous = current
|
||||||
}
|
} else {
|
||||||
updatedState.updateChannelState(peer.id, pts: channelPts)
|
previous = CachedChannelData()
|
||||||
case let .channelDifferenceTooLong(_, timeout, dialog, messages, chats, users):
|
}
|
||||||
apiTimeout = timeout
|
return previous.withUpdatedMinAvailableMessageId(messageId)
|
||||||
|
})
|
||||||
var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32)?
|
default:
|
||||||
|
break
|
||||||
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)
|
return resolveForumThreads(postbox: postbox, network: network, state: updatedState)
|
||||||
}
|
|> map { updatedState -> (AccountMutableState, Bool, Int32?) in
|
||||||
case .dialogFolder:
|
return (updatedState, true, apiTimeout)
|
||||||
break
|
}
|
||||||
}
|
case let .channelDifferenceEmpty(_, pts, timeout):
|
||||||
|
var updatedState = state
|
||||||
if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) = parameters {
|
var apiTimeout: Int32?
|
||||||
updatedState.updateChannelState(peer.peerId, pts: pts)
|
|
||||||
updatedState.updateChannelInvalidationPts(peer.peerId, invalidationPts: pts)
|
apiTimeout = timeout
|
||||||
|
|
||||||
updatedState.mergeChats(chats)
|
let channelPts: Int32
|
||||||
updatedState.mergeUsers(users)
|
if let _ = updatedState.channelStates[peer.id] {
|
||||||
|
channelPts = pts
|
||||||
updatedState.setNeedsHoleFromPreviousState(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, validateChannelPts: pts)
|
} else {
|
||||||
|
channelPts = pts
|
||||||
for apiMessage in messages {
|
}
|
||||||
if var message = StoreMessage(apiMessage: apiMessage) {
|
updatedState.updateChannelState(peer.id, pts: channelPts)
|
||||||
var attributes = message.attributes
|
|
||||||
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
|
return .single((updatedState, true, apiTimeout))
|
||||||
message = message.withUpdatedAttributes(attributes)
|
case let .channelDifferenceTooLong(_, timeout, dialog, messages, chats, users):
|
||||||
|
var updatedState = state
|
||||||
if let preCachedResources = apiMessage.preCachedResources {
|
var apiTimeout: Int32?
|
||||||
for (resource, data) in preCachedResources {
|
|
||||||
updatedState.addPreCachedResource(resource, data: data)
|
apiTimeout = timeout
|
||||||
}
|
|
||||||
}
|
var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32)?
|
||||||
|
|
||||||
let location: AddMessagesLocation
|
switch dialog {
|
||||||
if case let .Id(id) = message.id, id.id == topMessage {
|
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, _, pts, _, _):
|
||||||
location = .UpperHistoryBlock
|
if let pts = pts {
|
||||||
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
|
parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount)
|
||||||
} else {
|
}
|
||||||
location = .Random
|
case .dialogFolder:
|
||||||
}
|
break
|
||||||
updatedState.addMessages([message], location: location)
|
}
|
||||||
}
|
|
||||||
}
|
if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) = parameters {
|
||||||
|
updatedState.updateChannelState(peer.peerId, pts: pts)
|
||||||
updatedState.resetReadState(peer.peerId, namespace: Namespaces.Message.Cloud, maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: nil)
|
updatedState.updateChannelInvalidationPts(peer.peerId, invalidationPts: pts)
|
||||||
|
|
||||||
updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage))
|
updatedState.mergeChats(chats)
|
||||||
updatedState.resetMessageTagSummary(peer.peerId, tag: .unseenReaction, namespace: Namespaces.Message.Cloud, count: unreadReactionsCount, range: MessageHistoryTagNamespaceCountValidityRange(maxId: topMessage))
|
updatedState.mergeUsers(users)
|
||||||
} else {
|
|
||||||
assertionFailure()
|
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 {
|
} else {
|
||||||
Logger.shared.log("State", "can't poll channel \(peer.id): can't create inputChannel")
|
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?
|
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||||
for operation in operations {
|
for operation in operations {
|
||||||
switch operation {
|
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 {
|
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||||
}
|
}
|
||||||
@ -2631,12 +2859,48 @@ func replayFinalState(
|
|||||||
for message in messages {
|
for message in messages {
|
||||||
if case let .Id(id) = message.id {
|
if case let .Id(id) = message.id {
|
||||||
if let threadId = message.threadId {
|
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)
|
let messageThreadId = makeThreadIdMessageId(peerId: message.id.peerId, threadId: threadId)
|
||||||
if id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
if id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
if !transaction.messageExists(id: id) {
|
if !transaction.messageExists(id: id) {
|
||||||
addMessageThreadStatsDifference(threadMessageId: messageThreadId, remove: 0, addedMessagePeer: message.authorId, addedMessageId: id, isOutgoing: !message.flags.contains(.Incoming))
|
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:
|
case .ReadGroupFeedInbox:
|
||||||
break
|
break
|
||||||
//transaction.applyGroupFeedReadMaxIndex(groupId: groupId, index: index)
|
|
||||||
case let .UpdateReadThread(threadMessageId, readMaxId, isIncoming, mainChannelMessage):
|
case let .UpdateReadThread(threadMessageId, readMaxId, isIncoming, mainChannelMessage):
|
||||||
if isIncoming {
|
if isIncoming {
|
||||||
if let currentId = updatedIncomingThreadReadStates[threadMessageId] {
|
if let currentId = updatedIncomingThreadReadStates[threadMessageId] {
|
||||||
@ -2895,6 +3158,23 @@ func replayFinalState(
|
|||||||
} else {
|
} else {
|
||||||
updatedIncomingThreadReadStates[threadMessageId] = readMaxId
|
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 {
|
if let mainChannelMessage = mainChannelMessage {
|
||||||
transaction.updateMessage(mainChannelMessage, update: { currentMessage in
|
transaction.updateMessage(mainChannelMessage, update: { currentMessage in
|
||||||
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
|
||||||
@ -2919,6 +3199,17 @@ func replayFinalState(
|
|||||||
} else {
|
} else {
|
||||||
updatedOutgoingThreadReadStates[threadMessageId] = readMaxId
|
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):
|
case let .ResetReadState(peerId, namespace, maxIncomingReadId, maxOutgoingReadId, maxKnownId, count, markedUnread):
|
||||||
var markedUnreadValue: Bool = false
|
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))
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -287,10 +287,10 @@ final class ChatHistoryPreloadManager {
|
|||||||
self.canPreloadHistoryDisposable = (networkState
|
self.canPreloadHistoryDisposable = (networkState
|
||||||
|> map { state -> Bool in
|
|> map { state -> Bool in
|
||||||
switch state {
|
switch state {
|
||||||
case .online:
|
case .online:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
@ -298,6 +298,13 @@ final class ChatHistoryPreloadManager {
|
|||||||
guard let strongSelf = self, strongSelf.canPreloadHistoryValue != value else {
|
guard let strongSelf = self, strongSelf.canPreloadHistoryValue != value else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if "".isEmpty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
strongSelf.canPreloadHistoryValue = value
|
strongSelf.canPreloadHistoryValue = value
|
||||||
if value {
|
if value {
|
||||||
for i in 0 ..< min(3, strongSelf.entries.count) {
|
for i in 0 ..< min(3, strongSelf.entries.count) {
|
||||||
|
|||||||
@ -187,22 +187,23 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct FetchedChatList {
|
struct FetchedChatList {
|
||||||
let chatPeerIds: [PeerId]
|
var chatPeerIds: [PeerId]
|
||||||
let peers: [Peer]
|
var peers: [Peer]
|
||||||
let peerPresences: [PeerId: Api.User]
|
var peerPresences: [PeerId: Api.User]
|
||||||
let notificationSettings: [PeerId: PeerNotificationSettings]
|
var notificationSettings: [PeerId: PeerNotificationSettings]
|
||||||
let readStates: [PeerId: [MessageId.Namespace: PeerReadState]]
|
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]]
|
||||||
let mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
|
var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
|
||||||
let reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
|
var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
|
||||||
let channelStates: [PeerId: Int32]
|
var channelStates: [PeerId: Int32]
|
||||||
let storeMessages: [StoreMessage]
|
var storeMessages: [StoreMessage]
|
||||||
let topMessageIds: [PeerId: MessageId]
|
var topMessageIds: [PeerId: MessageId]
|
||||||
|
|
||||||
let lowerNonPinnedIndex: MessageIndex?
|
var lowerNonPinnedIndex: MessageIndex?
|
||||||
|
|
||||||
let pinnedItemIds: [PeerId]?
|
var pinnedItemIds: [PeerId]?
|
||||||
let folderSummaries: [PeerGroupId: PeerGroupUnreadCountersSummary]
|
var folderSummaries: [PeerGroupId: PeerGroupUnreadCountersSummary]
|
||||||
let peerGroupIds: [PeerId: PeerGroupId]
|
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> {
|
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,
|
pinnedItemIds: pinnedItemIds,
|
||||||
folderSummaries: folderSummaries,
|
folderSummaries: folderSummaries,
|
||||||
peerGroupIds: peerGroupIds
|
peerGroupIds: peerGroupIds,
|
||||||
|
threadInfos: [:]
|
||||||
)
|
)
|
||||||
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: storeMessages, reactions: [], result: result)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
@ -735,6 +735,12 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId
|
|||||||
return updated
|
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)
|
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: fetchedChats.peerPresences)
|
||||||
transaction.updateCurrentPeerNotificationSettings(fetchedChats.notificationSettings)
|
transaction.updateCurrentPeerNotificationSettings(fetchedChats.notificationSettings)
|
||||||
let _ = transaction.addMessages(fetchedChats.storeMessages, location: .UpperHistoryBlock)
|
let _ = transaction.addMessages(fetchedChats.storeMessages, location: .UpperHistoryBlock)
|
||||||
|
|||||||
@ -72,11 +72,12 @@ public final class EngineChatList: Equatable {
|
|||||||
public let readCounters: EnginePeerReadCounters?
|
public let readCounters: EnginePeerReadCounters?
|
||||||
public let isMuted: Bool
|
public let isMuted: Bool
|
||||||
public let draft: Draft?
|
public let draft: Draft?
|
||||||
public let threadInfo: EngineMessageHistoryThreads.Info?
|
public let threadInfo: EngineMessageHistoryThread.Info?
|
||||||
public let renderedPeer: EngineRenderedPeer
|
public let renderedPeer: EngineRenderedPeer
|
||||||
public let presence: EnginePeer.Presence?
|
public let presence: EnginePeer.Presence?
|
||||||
public let hasUnseenMentions: Bool
|
public let hasUnseenMentions: Bool
|
||||||
public let hasUnseenReactions: Bool
|
public let hasUnseenReactions: Bool
|
||||||
|
public let forumTopicTitle: String?
|
||||||
public let hasFailed: Bool
|
public let hasFailed: Bool
|
||||||
public let isContact: Bool
|
public let isContact: Bool
|
||||||
|
|
||||||
@ -87,11 +88,12 @@ public final class EngineChatList: Equatable {
|
|||||||
readCounters: EnginePeerReadCounters?,
|
readCounters: EnginePeerReadCounters?,
|
||||||
isMuted: Bool,
|
isMuted: Bool,
|
||||||
draft: Draft?,
|
draft: Draft?,
|
||||||
threadInfo: EngineMessageHistoryThreads.Info?,
|
threadInfo: EngineMessageHistoryThread.Info?,
|
||||||
renderedPeer: EngineRenderedPeer,
|
renderedPeer: EngineRenderedPeer,
|
||||||
presence: EnginePeer.Presence?,
|
presence: EnginePeer.Presence?,
|
||||||
hasUnseenMentions: Bool,
|
hasUnseenMentions: Bool,
|
||||||
hasUnseenReactions: Bool,
|
hasUnseenReactions: Bool,
|
||||||
|
forumTopicTitle: String?,
|
||||||
hasFailed: Bool,
|
hasFailed: Bool,
|
||||||
isContact: Bool
|
isContact: Bool
|
||||||
) {
|
) {
|
||||||
@ -106,6 +108,7 @@ public final class EngineChatList: Equatable {
|
|||||||
self.presence = presence
|
self.presence = presence
|
||||||
self.hasUnseenMentions = hasUnseenMentions
|
self.hasUnseenMentions = hasUnseenMentions
|
||||||
self.hasUnseenReactions = hasUnseenReactions
|
self.hasUnseenReactions = hasUnseenReactions
|
||||||
|
self.forumTopicTitle = forumTopicTitle
|
||||||
self.hasFailed = hasFailed
|
self.hasFailed = hasFailed
|
||||||
self.isContact = isContact
|
self.isContact = isContact
|
||||||
}
|
}
|
||||||
@ -144,6 +147,9 @@ public final class EngineChatList: Equatable {
|
|||||||
if lhs.hasUnseenReactions != rhs.hasUnseenReactions {
|
if lhs.hasUnseenReactions != rhs.hasUnseenReactions {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.forumTopicTitle != rhs.forumTopicTitle {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.hasFailed != rhs.hasFailed {
|
if lhs.hasFailed != rhs.hasFailed {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -351,7 +357,7 @@ public extension EngineChatList.RelativePosition {
|
|||||||
extension EngineChatList.Item {
|
extension EngineChatList.Item {
|
||||||
convenience init?(_ entry: ChatListEntry) {
|
convenience init?(_ entry: ChatListEntry) {
|
||||||
switch entry {
|
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?
|
var draft: EngineChatList.Draft?
|
||||||
if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp {
|
if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp {
|
||||||
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
|
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
|
||||||
@ -377,6 +383,11 @@ extension EngineChatList.Item {
|
|||||||
hasUnseenReactions = (info.tagSummaryCount ?? 0) != 0// > (info.actionsSummaryCount ?? 0)
|
hasUnseenReactions = (info.tagSummaryCount ?? 0) != 0// > (info.actionsSummaryCount ?? 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var forumTopicTitle: String?
|
||||||
|
if let forumTopicData = forumTopicData?.get(MessageHistoryThreadData.self) {
|
||||||
|
forumTopicTitle = forumTopicData.info.title
|
||||||
|
}
|
||||||
|
|
||||||
self.init(
|
self.init(
|
||||||
id: .chatList(index.messageIndex.id.peerId),
|
id: .chatList(index.messageIndex.id.peerId),
|
||||||
index: .chatList(index),
|
index: .chatList(index),
|
||||||
@ -389,6 +400,7 @@ extension EngineChatList.Item {
|
|||||||
presence: presence.flatMap(EnginePeer.Presence.init),
|
presence: presence.flatMap(EnginePeer.Presence.init),
|
||||||
hasUnseenMentions: hasUnseenMentions,
|
hasUnseenMentions: hasUnseenMentions,
|
||||||
hasUnseenReactions: hasUnseenReactions,
|
hasUnseenReactions: hasUnseenReactions,
|
||||||
|
forumTopicTitle: forumTopicTitle,
|
||||||
hasFailed: hasFailed,
|
hasFailed: hasFailed,
|
||||||
isContact: isContact
|
isContact: isContact
|
||||||
)
|
)
|
||||||
|
|||||||
@ -332,6 +332,20 @@ private class ReplyThreadHistoryContextImpl {
|
|||||||
let account = self.account
|
let account = self.account
|
||||||
|
|
||||||
let _ = (self.account.postbox.transaction { transaction -> (Api.InputPeer?, MessageId?, Int?) in
|
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) {
|
if let message = transaction.getMessage(messageId) {
|
||||||
for attribute in message.attributes {
|
for attribute in message.attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
@ -574,8 +588,10 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
|
|||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|
|
||||||
let replyInfo = Promise<AccountViewTracker.UpdatedMessageReplyInfo?>()
|
let replyInfo = Promise<MessageHistoryThreadData?>()
|
||||||
replyInfo.set(.single(nil))
|
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))
|
let remoteDiscussionMessageSignal: Signal<DiscussionMessage?, NoError> = account.network.request(Api.functions.messages.getDiscussionMessage(peer: inputPeer, msgId: messageId.id))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
@ -664,12 +680,22 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
|
|||||||
}
|
}
|
||||||
let discussionMessageSignal = (replyInfo.get()
|
let discussionMessageSignal = (replyInfo.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> mapToSignal { replyInfo -> Signal<DiscussionMessage?, NoError> in
|
|> mapToSignal { threadData -> Signal<DiscussionMessage?, NoError> in
|
||||||
guard let replyInfo = replyInfo else {
|
guard let threadData = threadData else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
return account.postbox.transaction { transaction -> DiscussionMessage? in
|
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
|
transaction.scanMessageAttributes(peerId: replyInfo.commentsPeerId, namespace: Namespaces.Message.Cloud, limit: 1000, { id, attributes in
|
||||||
for attribute in attributes {
|
for attribute in attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
@ -696,7 +722,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
|
|||||||
maxReadIncomingMessageId: replyInfo.maxReadIncomingMessageId,
|
maxReadIncomingMessageId: replyInfo.maxReadIncomingMessageId,
|
||||||
maxReadOutgoingMessageId: nil,
|
maxReadOutgoingMessageId: nil,
|
||||||
unreadCount: 0
|
unreadCount: 0
|
||||||
)
|
)*/
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|> mapToSignal { result -> Signal<DiscussionMessage?, NoError> in
|
|> 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()
|
let preloadedHistoryPosition: Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?), FetchChannelReplyThreadMessageError> = replyInfo.get()
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> castError(FetchChannelReplyThreadMessageError.self)
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|> mapToSignal { replyInfo -> Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?), FetchChannelReplyThreadMessageError> in
|
|> mapToSignal { threadData -> Signal<(FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?), FetchChannelReplyThreadMessageError> in
|
||||||
if let replyInfo = replyInfo {
|
if let _ = threadData, !"".isEmpty {
|
||||||
return account.postbox.transaction { transaction -> (FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?) in
|
return .fail(.generic)
|
||||||
|
/*return account.postbox.transaction { transaction -> (FetchMessageHistoryHoleThreadInput, PeerId, MessageId?, Anchor, MessageId?) in
|
||||||
var threadInput: FetchMessageHistoryHoleThreadInput = .threadFromChannel(channelMessageId: messageId)
|
var threadInput: FetchMessageHistoryHoleThreadInput = .threadFromChannel(channelMessageId: messageId)
|
||||||
var threadMessageId: 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 {
|
for attribute in attributes {
|
||||||
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
if let attribute = attribute as? SourceReferenceMessageAttribute {
|
||||||
if attribute.messageId == messageId {
|
if attribute.messageId == messageId {
|
||||||
@ -745,7 +772,7 @@ func _internal_fetchChannelReplyThreadMessage(account: Account, messageId: Messa
|
|||||||
}
|
}
|
||||||
return (threadInput, replyInfo.commentsPeerId, threadMessageId, anchor, replyInfo.maxMessageId)
|
return (threadInput, replyInfo.commentsPeerId, threadMessageId, anchor, replyInfo.maxMessageId)
|
||||||
}
|
}
|
||||||
|> castError(FetchChannelReplyThreadMessageError.self)
|
|> castError(FetchChannelReplyThreadMessageError.self)*/
|
||||||
} else {
|
} else {
|
||||||
return discussionMessage.get()
|
return discussionMessage.get()
|
||||||
|> take(1)
|
|> 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
|
|> mapToSignal { peerInput, commentsPeerId, threadMessageId, anchor, maxMessageId -> Signal<(FetchMessageHistoryHoleResult?, ChatReplyThreadMessage.Anchor), FetchChannelReplyThreadMessageError> in
|
||||||
guard let maxMessageId = maxMessageId else {
|
guard let maxMessageId = maxMessageId else {
|
||||||
return .single((FetchMessageHistoryHoleResult(removedIndices: IndexSet(integersIn: 1 ..< Int(Int32.max - 1)), strictRemovedIndices: IndexSet(), actualPeerId: nil, actualThreadId: nil, ids: []), .automatic))
|
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,
|
anchor: inputAnchor,
|
||||||
namespaces: .not(Namespaces.Message.allScheduled)
|
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
|
let initialAnchor: ChatReplyThreadMessage.Anchor
|
||||||
switch anchor {
|
switch anchor {
|
||||||
case .lowerBound:
|
case .lowerBound:
|
||||||
|
|||||||
@ -140,7 +140,7 @@ public func donateSendMessageIntent(account: Account, sharedContext: SharedAccou
|
|||||||
if peer.id == account.peerId {
|
if peer.id == account.peerId {
|
||||||
signals.append(.single((peer, subject, savedMessagesAvatar)))
|
signals.append(.single((peer, subject, savedMessagesAvatar)))
|
||||||
} else {
|
} 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
|
|> map { imageVersions -> (Peer, SendMessageIntentSubject, UIImage?) in
|
||||||
var avatarImage: UIImage?
|
var avatarImage: UIImage?
|
||||||
if let image = imageVersions?.0 {
|
if let image = imageVersions?.0 {
|
||||||
|
|||||||
@ -407,7 +407,6 @@ public final class ForumTopicListScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createPressed() {
|
func createPressed() {
|
||||||
self.forumChannelContext.createTopic(title: "Topic#\(Int.random(in: 0 ..< 100000))")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func update(transition: Transition) {
|
private func update(transition: Transition) {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import WebPBinding
|
|||||||
|
|
||||||
public func cacheLottieAnimation(data: Data, width: Int, height: Int, keyframeOnly: Bool, writer: AnimationCacheItemWriter, firstFrameOnly: Bool) {
|
public func cacheLottieAnimation(data: Data, width: Int, height: Int, keyframeOnly: Bool, writer: AnimationCacheItemWriter, firstFrameOnly: Bool) {
|
||||||
let work: () -> Void = {
|
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 {
|
guard let animation = LottieInstance(data: decompressedData, fitzModifier: .none, colorReplacements: nil, cacheKey: "") else {
|
||||||
writer.finish()
|
writer.finish()
|
||||||
return
|
return
|
||||||
|
|||||||
@ -8969,7 +8969,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if !(peer is TelegramGroup || peer is TelegramChannel) {
|
if !(peer is TelegramGroup || peer is TelegramChannel) {
|
||||||
return
|
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
|
}, presentGigagroupHelp: { [weak self] in
|
||||||
if let strongSelf = self {
|
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)
|
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)
|
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
|
|> deliverOnMainQueue).start(next: { [weak statusController] result in
|
||||||
if displayModalProgress {
|
if displayModalProgress {
|
||||||
statusController?.dismiss()
|
statusController?.dismiss()
|
||||||
|
|||||||
@ -336,7 +336,7 @@ enum ReplyThreadSubject {
|
|||||||
case groupMessage(MessageId)
|
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>
|
let message: Signal<ChatReplyThreadMessage, FetchChannelReplyThreadMessageError>
|
||||||
switch subject {
|
switch subject {
|
||||||
case .channelPost(let messageId), .groupMessage(let messageId):
|
case .channelPost(let messageId), .groupMessage(let messageId):
|
||||||
@ -380,42 +380,52 @@ func fetchAndPreloadReplyThreadInfo(context: AccountContext, subject: ReplyThrea
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
let preloadSignal = preloadedChatHistoryViewForLocation(
|
if preload {
|
||||||
input,
|
let preloadSignal = preloadedChatHistoryViewForLocation(
|
||||||
context: context,
|
input,
|
||||||
chatLocation: .replyThread(message: replyThreadMessage),
|
context: context,
|
||||||
subject: nil,
|
chatLocation: .replyThread(message: replyThreadMessage),
|
||||||
chatLocationContextHolder: chatLocationContextHolder,
|
subject: nil,
|
||||||
fixedCombinedReadStates: nil,
|
chatLocationContextHolder: chatLocationContextHolder,
|
||||||
tagMask: nil,
|
fixedCombinedReadStates: nil,
|
||||||
additionalData: []
|
tagMask: nil,
|
||||||
)
|
additionalData: []
|
||||||
return preloadSignal
|
)
|
||||||
|> map { historyView -> Bool? in
|
return preloadSignal
|
||||||
switch historyView {
|
|> map { historyView -> Bool? in
|
||||||
case .Loading:
|
switch historyView {
|
||||||
return nil
|
case .Loading:
|
||||||
case let .HistoryView(view, _, _, _, _, _, _):
|
return nil
|
||||||
return view.entries.isEmpty
|
case let .HistoryView(view, _, _, _, _, _, _):
|
||||||
|
return view.entries.isEmpty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|> mapToSignal { value -> Signal<Bool, NoError> in
|
||||||
|> mapToSignal { value -> Signal<Bool, NoError> in
|
if let value = value {
|
||||||
if let value = value {
|
return .single(value)
|
||||||
return .single(value)
|
} else {
|
||||||
} else {
|
return .complete()
|
||||||
return .complete()
|
}
|
||||||
}
|
}
|
||||||
}
|
|> take(1)
|
||||||
|> take(1)
|
|> map { isEmpty -> ReplyThreadInfo in
|
||||||
|> map { isEmpty -> ReplyThreadInfo in
|
return ReplyThreadInfo(
|
||||||
return ReplyThreadInfo(
|
message: replyThreadMessage,
|
||||||
|
isChannelPost: replyThreadMessage.isChannelPost,
|
||||||
|
isEmpty: isEmpty,
|
||||||
|
scrollToLowerBoundMessage: scrollToLowerBoundMessage,
|
||||||
|
contextHolder: chatLocationContextHolder
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|> castError(FetchChannelReplyThreadMessageError.self)
|
||||||
|
} else {
|
||||||
|
return .single(ReplyThreadInfo(
|
||||||
message: replyThreadMessage,
|
message: replyThreadMessage,
|
||||||
isChannelPost: replyThreadMessage.isChannelPost,
|
isChannelPost: replyThreadMessage.isChannelPost,
|
||||||
isEmpty: isEmpty,
|
isEmpty: false,
|
||||||
scrollToLowerBoundMessage: scrollToLowerBoundMessage,
|
scrollToLowerBoundMessage: scrollToLowerBoundMessage,
|
||||||
contextHolder: chatLocationContextHolder
|
contextHolder: chatLocationContextHolder
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|> castError(FetchChannelReplyThreadMessageError.self)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,7 +99,8 @@ private enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
promoInfo: nil,
|
promoInfo: nil,
|
||||||
ignoreUnreadBadge: true,
|
ignoreUnreadBadge: true,
|
||||||
displayAsMessage: true,
|
displayAsMessage: true,
|
||||||
hasFailedMessages: false
|
hasFailedMessages: false,
|
||||||
|
forumThreadTitle: nil
|
||||||
),
|
),
|
||||||
editing: false,
|
editing: false,
|
||||||
hasActiveRevealControls: false,
|
hasActiveRevealControls: false,
|
||||||
@ -239,7 +240,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch item.content {
|
switch item.content {
|
||||||
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .peer(messages, peer, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
if let message = messages.first {
|
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))
|
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)
|
chatController.canReadHistory.set(false)
|
||||||
|
|||||||
@ -225,7 +225,7 @@ public func isOverlayControllerForChatNotificationOverlayPresentation(_ controll
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func navigateToForumThreadImpl(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, navigationController: NavigationController) -> Signal<Never, NoError> {
|
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
|
|> deliverOnMainQueue
|
||||||
|> beforeNext { [weak context, weak navigationController] result in
|
|> beforeNext { [weak context, weak navigationController] result in
|
||||||
guard let context = context, let navigationController = navigationController else {
|
guard let context = context, let navigationController = navigationController else {
|
||||||
|
|||||||
@ -6359,7 +6359,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
return
|
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() {
|
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()
|
let members: Promise<[PeerId]> = Promise()
|
||||||
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
|
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||||
/*var membersDisposable: Disposable?
|
/*var membersDisposable: Disposable?
|
||||||
|
|||||||
@ -1278,6 +1278,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return PeerSelectionControllerImpl(params)
|
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 {
|
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
|
let controllerInteraction: ChatControllerInteraction
|
||||||
|
|
||||||
|
|||||||
@ -325,7 +325,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeBridgeChat(_ entry: ChatListEntry, strings: PresentationStrings) -> (TGBridgeChat, [Int64 : TGBridgeUser])? {
|
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 {
|
guard index.messageIndex.id.peerId.namespace != Namespaces.Peer.SecretChat else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user