[WIP] Topics

This commit is contained in:
Ali 2022-10-21 14:54:30 +04:00
parent dc9c076e45
commit 0378e1c5c4
10 changed files with 167 additions and 88 deletions

View File

@ -1267,7 +1267,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
}
#if DEBUG
//debugSaveState(basePath: basePath, name: "previous1")
debugSaveState(basePath: basePath, name: "previous1")
//debugRestoreState(basePath: basePath, name: "previous1")
#endif

View File

@ -1328,6 +1328,49 @@ public final class AccountStateManager {
}
}
func resolveNotificationSettings(list: [TelegramPeerNotificationSettings], defaultSettings: MessageNotificationSettings) -> (sound: PeerMessageSound, notify: Bool, displayContents: Bool) {
var sound: PeerMessageSound = defaultSettings.sound
var notify = defaultSettings.enabled
var displayContents = defaultSettings.displayPreviews
for item in list.reversed() {
if case .default = item.messageSound {
} else {
sound = item.messageSound
}
switch item.displayPreviews {
case .default:
break
case .show:
displayContents = true
case .hide:
displayContents = false
}
switch item.muteState {
case .default:
break
case .unmuted:
notify = true
case let .muted(deadline):
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
if deadline > timestamp {
notify = false
} else {
notify = true
}
}
}
if case .default = sound {
sound = defaultCloudPeerNotificationSound
}
return (sound, notify, displayContents)
}
public func messagesForNotification(transaction: Transaction, id: MessageId, alwaysReturnMessage: Bool) -> (messages: [Message], notify: Bool, sound: PeerMessageSound, displayContents: Bool, threadData: MessageHistoryThreadData?) {
guard let message = transaction.getMessage(id) else {
Logger.shared.log("AccountStateManager", "notification message doesn't exist")
@ -1335,7 +1378,6 @@ public func messagesForNotification(transaction: Transaction, id: MessageId, alw
}
var notify = true
var sound: PeerMessageSound = defaultCloudPeerNotificationSound
var muted = false
var displayContents = true
var threadData: MessageHistoryThreadData?
@ -1365,8 +1407,6 @@ public func messagesForNotification(transaction: Transaction, id: MessageId, alw
}
}
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var notificationPeerId = id.peerId
let peer = transaction.getPeer(id.peerId)
if let peer = peer, let associatedPeerId = peer.associatedPeerId {
@ -1376,47 +1416,38 @@ public func messagesForNotification(transaction: Transaction, id: MessageId, alw
notificationPeerId = author.id
}
var notificationSettingsStack: [TelegramPeerNotificationSettings] = []
if let threadId = message.threadId, let threadData = transaction.getMessageHistoryThreadInfo(peerId: message.id.peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {
notificationSettingsStack.append(threadData.notificationSettings)
}
if let notificationSettings = transaction.getPeerNotificationSettings(id: notificationPeerId) as? TelegramPeerNotificationSettings {
var defaultSound: PeerMessageSound = defaultCloudPeerNotificationSound
var defaultNotify: Bool = true
if let globalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) {
if id.peerId.namespace == Namespaces.Peer.CloudUser {
defaultNotify = globalNotificationSettings.effective.privateChats.enabled
defaultSound = globalNotificationSettings.effective.privateChats.sound
displayContents = globalNotificationSettings.effective.privateChats.displayPreviews
} else if id.peerId.namespace == Namespaces.Peer.SecretChat {
defaultNotify = globalNotificationSettings.effective.privateChats.enabled
defaultSound = globalNotificationSettings.effective.privateChats.sound
displayContents = false
} else if id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = peer as? TelegramChannel, case .broadcast = peer.info {
defaultNotify = globalNotificationSettings.effective.channels.enabled
defaultSound = globalNotificationSettings.effective.channels.sound
displayContents = globalNotificationSettings.effective.channels.displayPreviews
} else {
defaultNotify = globalNotificationSettings.effective.groupChats.enabled
defaultSound = globalNotificationSettings.effective.groupChats.sound
displayContents = globalNotificationSettings.effective.groupChats.displayPreviews
}
}
switch notificationSettings.muteState {
case .default:
if !defaultNotify {
notify = false
}
case let .muted(until):
if until >= timestamp {
notify = false
}
case .unmuted:
break
}
if case .default = notificationSettings.messageSound {
sound = defaultSound
} else {
sound = notificationSettings.messageSound
}
notificationSettingsStack.append(notificationSettings)
}
let globalNotificationSettings = transaction.getPreferencesEntry(key: PreferencesKeys.globalNotifications)?.get(GlobalNotificationSettings.self) ?? GlobalNotificationSettings.defaultSettings
let defaultNotificationSettings: MessageNotificationSettings
if id.peerId.namespace == Namespaces.Peer.CloudUser {
defaultNotificationSettings = globalNotificationSettings.effective.privateChats
} else if id.peerId.namespace == Namespaces.Peer.SecretChat {
defaultNotificationSettings = globalNotificationSettings.effective.privateChats
displayContents = false
} else if id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = peer as? TelegramChannel, case .broadcast = peer.info {
defaultNotificationSettings = globalNotificationSettings.effective.channels
} else {
Logger.shared.log("AccountStateManager", "notification settings for \(notificationPeerId) are undefined")
defaultNotificationSettings = globalNotificationSettings.effective.groupChats
}
let (resolvedSound, resolvedNotify, resolvedDisplayContents) = resolveNotificationSettings(list: notificationSettingsStack, defaultSettings: defaultNotificationSettings)
var sound = resolvedSound
if !resolvedNotify {
notify = false
}
if !resolvedDisplayContents {
displayContents = false
}
if muted {
@ -1425,10 +1456,10 @@ public func messagesForNotification(transaction: Transaction, id: MessageId, alw
if let channel = message.peers[message.id.peerId] as? TelegramChannel {
switch channel.participationStatus {
case .kicked, .left:
return ([], false, sound, false, threadData)
case .member:
break
case .kicked, .left:
return ([], false, sound, false, threadData)
case .member:
break
}
}

View File

@ -270,14 +270,17 @@ private class AdMessagesHistoryContextImpl {
struct CachedState: Codable, PostboxCoding {
enum CodingKeys: String, CodingKey {
case timestamp
case interPostInterval
case messages
}
var timestamp: Int32
var interPostInterval: Int32?
var messages: [CachedMessage]
init(timestamp: Int32, messages: [CachedMessage]) {
init(timestamp: Int32, interPostInterval: Int32?, messages: [CachedMessage]) {
self.timestamp = timestamp
self.interPostInterval = interPostInterval
self.messages = messages
}
@ -285,6 +288,7 @@ private class AdMessagesHistoryContextImpl {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.timestamp = try container.decode(Int32.self, forKey: .timestamp)
self.interPostInterval = try container.decodeIfPresent(Int32.self, forKey: .interPostInterval)
self.messages = try container.decode([CachedMessage].self, forKey: .messages)
}
@ -292,11 +296,13 @@ private class AdMessagesHistoryContextImpl {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.timestamp, forKey: .timestamp)
try container.encodeIfPresent(self.interPostInterval, forKey: .interPostInterval)
try container.encode(self.messages, forKey: .messages)
}
init(decoder: PostboxDecoder) {
self.timestamp = decoder.decodeInt32ForKey("timestamp", orElse: 0)
self.interPostInterval = decoder.decodeOptionalInt32ForKey("interPostInterval")
if let messagesData = decoder.decodeOptionalDataArrayForKey("messages") {
self.messages = messagesData.compactMap { data -> CachedMessage? in
return try? AdaptedPostboxDecoder().decode(CachedMessage.self, from: data)
@ -308,6 +314,11 @@ private class AdMessagesHistoryContextImpl {
func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.timestamp, forKey: "timestamp")
if let interPostInterval = self.interPostInterval {
encoder.encodeInt32(interPostInterval, forKey: "interPostInterval")
} else {
encoder.encodeNil(forKey: "interPostInterval")
}
encoder.encodeDataArray(self.messages.compactMap { message -> Data? in
return try? AdaptedPostboxEncoder().encode(message)
}, forKey: "messages")
@ -338,9 +349,13 @@ private class AdMessagesHistoryContextImpl {
}
struct State: Equatable {
var interPostInterval: Int32?
var messages: [Message]
static func ==(lhs: State, rhs: State) -> Bool {
if lhs.interPostInterval != rhs.interPostInterval {
return false
}
if lhs.messages.count != rhs.messages.count {
return false
}
@ -372,43 +387,41 @@ private class AdMessagesHistoryContextImpl {
self.account = account
self.peerId = peerId
self.stateValue = State(messages: [])
self.stateValue = State(interPostInterval: nil, messages: [])
self.state.set(CachedState.getCached(postbox: account.postbox, peerId: peerId)
|> mapToSignal { cachedState -> Signal<State, NoError> in
if let cachedState = cachedState, cachedState.timestamp >= Int32(Date().timeIntervalSince1970) - 5 * 60 {
return account.postbox.transaction { transaction -> State in
return State(messages: cachedState.messages.compactMap { message -> Message? in
return State(interPostInterval: cachedState.interPostInterval, messages: cachedState.messages.compactMap { message -> Message? in
return message.toMessage(peerId: peerId, transaction: transaction)
})
}
} else {
return .single(State(messages: []))
return .single(State(interPostInterval: nil, messages: []))
}
})
let signal: Signal<[Message], NoError> = account.postbox.transaction { transaction -> Api.InputChannel? in
let signal: Signal<(interPostInterval: Int32?, messages: [Message]), NoError> = account.postbox.transaction { transaction -> Api.InputChannel? in
return transaction.getPeer(peerId).flatMap(apiInputChannel)
}
|> mapToSignal { inputChannel -> Signal<[Message], NoError> in
|> mapToSignal { inputChannel -> Signal<(interPostInterval: Int32?, messages: [Message]), NoError> in
guard let inputChannel = inputChannel else {
return .single([])
return .single((nil, []))
}
return account.network.request(Api.functions.channels.getSponsoredMessages(channel: inputChannel))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.SponsoredMessages?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[Message], NoError> in
|> mapToSignal { result -> Signal<(interPostInterval: Int32?, messages: [Message]), NoError> in
guard let result = result else {
return .single([])
return .single((nil, []))
}
return account.postbox.transaction { transaction -> [Message] in
return account.postbox.transaction { transaction -> (interPostInterval: Int32?, messages: [Message]) in
switch result {
case let .sponsoredMessages(_, postsBetween, messages, chats, users):
let _ = postsBetween
var peers: [Peer] = []
var peerPresences: [PeerId: Api.User] = [:]
@ -501,24 +514,24 @@ private class AdMessagesHistoryContextImpl {
}
}
CachedState.setCached(transaction: transaction, peerId: peerId, state: CachedState(timestamp: Int32(Date().timeIntervalSince1970), messages: parsedMessages))
CachedState.setCached(transaction: transaction, peerId: peerId, state: CachedState(timestamp: Int32(Date().timeIntervalSince1970), interPostInterval: postsBetween, messages: parsedMessages))
return parsedMessages.compactMap { message -> Message? in
return (postsBetween, parsedMessages.compactMap { message -> Message? in
return message.toMessage(peerId: peerId, transaction: transaction)
}
})
case .sponsoredMessagesEmpty:
return []
return (nil, [])
}
}
}
}
self.disposable.set((signal
|> deliverOn(self.queue)).start(next: { [weak self] messages in
|> deliverOn(self.queue)).start(next: { [weak self] interPostInterval, messages in
guard let strongSelf = self else {
return
}
strongSelf.stateValue = State(messages: messages)
strongSelf.stateValue = State(interPostInterval: interPostInterval, messages: messages)
}))
}
@ -549,13 +562,13 @@ public class AdMessagesHistoryContext {
private let queue = Queue()
private let impl: QueueLocalObject<AdMessagesHistoryContextImpl>
public var state: Signal<[Message], NoError> {
public var state: Signal<(interPostInterval: Int32?, messages: [Message]), NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
let stateDisposable = impl.state.get().start(next: { state in
subscriber.putNext(state.messages)
subscriber.putNext((state.interPostInterval, state.messages))
})
disposable.set(stateDisposable)
}

View File

@ -324,7 +324,7 @@ final class AuthorizedApplicationContext {
var chatIsVisible = false
if let topController = strongSelf.rootController.topViewController as? ChatControllerImpl, topController.traceVisibility() {
if topController.chatLocation.peerId == firstMessage.id.peerId {
if topController.chatLocation.peerId == firstMessage.id.peerId, (topController.chatLocation.threadId == nil || topController.chatLocation.threadId == firstMessage.threadId) {
chatIsVisible = true
}
}
@ -335,7 +335,7 @@ final class AuthorizedApplicationContext {
if !chatIsVisible {
strongSelf.mainWindow.forEachViewController({ controller in
if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, controller.chatLocation.threadId == chatLocation.threadId {
if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (chatLocation.threadId == nil || chatLocation.threadId == controller.chatLocation.threadId) {
chatIsVisible = true
return false
}
@ -415,14 +415,14 @@ final class AuthorizedApplicationContext {
return true
}
if let topController = strongSelf.rootController.topViewController as? ChatControllerImpl, topController.chatLocation.peerId == chatLocation.peerId, topController.chatLocation.threadId == chatLocation.threadId {
if let topController = strongSelf.rootController.topViewController as? ChatControllerImpl, topController.chatLocation.peerId == chatLocation.peerId, (topController.chatLocation.threadId == nil || topController.chatLocation.threadId == chatLocation.threadId) {
strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId)
return false
}
for controller in strongSelf.rootController.viewControllers {
if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, controller.chatLocation.threadId == chatLocation.threadId {
if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) {
return true
}
}

View File

@ -5565,9 +5565,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
private func topPinnedMessageSignal(latest: Bool) -> Signal<ChatPinnedMessage?, NoError> {
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError>
var pinnedPeerId: EnginePeer.Id?
let threadId = self.chatLocation.threadId
switch self.chatLocation {
case let .peer(peerId):
case let .peer(id):
pinnedPeerId = id
case let .replyThread(message):
if message.isForumPost {
pinnedPeerId = self.chatLocation.peerId
}
default:
break
}
if let peerId = pinnedPeerId {
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError>
struct ReferenceMessage {
var id: MessageId
var isScrolled: Bool
@ -5611,7 +5625,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
location = .Initial(count: count)
}
return (chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), ignoreMessagesInTimestampRange: nil, context: context, chatLocation: .peer(id: peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.pinned, appendMessagesFromTheSameGroup: false, additionalData: [], orderStatistics: .combinedLocation)
let chatLocation: ChatLocation
if let threadId {
chatLocation = .replyThread(message: ChatReplyThreadMessage(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
} else {
chatLocation = .peer(id: peerId)
}
return (chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), ignoreMessagesInTimestampRange: nil, context: context, chatLocation: chatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.pinned, appendMessagesFromTheSameGroup: false, additionalData: [], orderStatistics: .combinedLocation)
|> castError(Bool.self)
|> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in
switch update {
@ -5831,10 +5852,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return message
}
|> distinctUntilChanged
case .replyThread, .feed:
return topPinnedMessage
} else {
return .single(nil)
}
return topPinnedMessage
}
override public func loadDisplayNode() {
@ -6241,7 +6263,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch strongSelf.chatLocation {
case let .replyThread(replyThreadMessage):
if isForum {
pinnedMessageId = nil
pinnedMessageId = topPinnedMessage?.message.id
pinnedMessage = topPinnedMessage
} else {
if isTopReplyThreadMessageShown {
pinnedMessageId = nil
@ -8325,7 +8348,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}, unblockPeer: { [weak self] in
self?.unblockPeer()
}, pinMessage: { [weak self] messageId, contextController in
if let strongSelf = self, case let .peer(currentPeerId) = strongSelf.chatLocation {
if let strongSelf = self, let currentPeerId = strongSelf.chatLocation.peerId {
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
if strongSelf.canManagePin() {
let pinAction: (Bool, Bool) -> Void = { notify, forThisPeerOnlyIfPossible in

View File

@ -1052,13 +1052,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var extraTransition = transition
if let titleAccessoryPanelNode = titlePanelForChatPresentationInterfaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.titleAccessoryPanelNode, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction) {
if self.titleAccessoryPanelNode != titleAccessoryPanelNode {
dismissedTitleAccessoryPanelNode = self.titleAccessoryPanelNode
dismissedTitleAccessoryPanelNode = self.titleAccessoryPanelNode
self.titleAccessoryPanelNode = titleAccessoryPanelNode
immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance = true
self.titleAccessoryPanelContainer.addSubnode(titleAccessoryPanelNode)
titleAccessoryPanelNode.clipsToBounds = true
extraTransition = .animated(duration: 0.2, curve: .easeInOut)
if transition.isAnimated {
extraTransition = .animated(duration: 0.2, curve: .easeInOut)
}
}
let layoutResult = titleAccessoryPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: immediatelyLayoutTitleAccessoryPanelNodeAndAnimateAppearance ? .immediate : transition, interfaceState: self.chatPresentationInterfaceState)

View File

@ -25,7 +25,7 @@ func chatHistoryEntriesForView(
customChannelDiscussionReadState: MessageId?,
customThreadOutgoingReadState: MessageId?,
cachedData: CachedPeerData?,
adMessages: [Message]
adMessages: (interPostInterval: Int32?, messages: [Message])
) -> [ChatHistoryEntry] {
if historyAppearsCleared {
return []
@ -329,9 +329,9 @@ func chatHistoryEntriesForView(
}
if view.laterId == nil && !view.isLoading {
if !entries.isEmpty, case let .MessageEntry(lastMessage, _, _, _, _, _) = entries[entries.count - 1], !adMessages.isEmpty {
if !entries.isEmpty, case let .MessageEntry(lastMessage, _, _, _, _, _) = entries[entries.count - 1], !adMessages.messages.isEmpty {
var nextAdMessageId: Int32 = 1
for message in adMessages {
if let message = adMessages.messages.first {
let updatedMessage = Message(
stableId: UInt32.max - 1 - UInt32(nextAdMessageId),
stableVersion: message.stableVersion,

View File

@ -638,14 +638,14 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
default:
break
}
var adMessages: Signal<[Message], NoError>
var adMessages: Signal<(interPostInterval: Int32?, messages: [Message]), NoError>
if case .bubbles = mode, let peerId = displayAdPeer {
let adMessagesContext = context.engine.messages.adMessages(peerId: peerId)
self.adMessagesContext = adMessagesContext
adMessages = adMessagesContext.state
} else {
self.adMessagesContext = nil
adMessages = .single([])
adMessages = .single((nil, []))
}
/*if case .bubbles = mode, let peerId = sparseScrollPeerId {
@ -661,7 +661,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
super.init()
adMessages = adMessages
|> afterNext { [weak self] messages in
|> afterNext { [weak self] interPostInterval, messages in
Queue.mainQueue().async {
guard let strongSelf = self else {
return

View File

@ -1250,7 +1250,17 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
if data.canPin && !isMigrated, case .peer = chatPresentationInterfaceState.chatLocation {
var canPin = data.canPin
if case let .replyThread(message) = chatPresentationInterfaceState.chatLocation {
if !message.isForumPost {
canPin = false
}
}
if isMigrated {
canPin = false
}
if canPin {
var pinnedSelectedMessageId: MessageId?
for message in messages {
if message.tags.contains(.pinned) {