mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-04 18:41:00 +00:00
Initial implementation for reactions API
This commit is contained in:
parent
0f72e95e24
commit
8f463038e4
@ -1242,6 +1242,7 @@ public class Account {
|
||||
self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
||||
|
||||
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||
|
@ -40,6 +40,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(TextEntitiesMessageAttribute.self, f: { TextEntitiesMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ReplyMessageAttribute.self, f: { ReplyMessageAttribute(decoder: $0) })
|
||||
declareEncodable(ReactionsMessageAttribute.self, f: { ReactionsMessageAttribute(decoder: $0) })
|
||||
declareEncodable(PendingReactionsMessageAttribute.self, f: { PendingReactionsMessageAttribute(decoder: $0) })
|
||||
declareEncodable(CloudDocumentMediaResource.self, f: { CloudDocumentMediaResource(decoder: $0) })
|
||||
declareEncodable(TelegramMediaWebpage.self, f: { TelegramMediaWebpage(decoder: $0) })
|
||||
declareEncodable(ViewCountMessageAttribute.self, f: { ViewCountMessageAttribute(decoder: $0) })
|
||||
@ -149,6 +150,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(CloudStickerPackThumbnailMediaResource.self, f: { CloudStickerPackThumbnailMediaResource(decoder: $0) })
|
||||
declareEncodable(AccountBackupDataAttribute.self, f: { AccountBackupDataAttribute(decoder: $0) })
|
||||
declareEncodable(ContentRequiresValidationMessageAttribute.self, f: { ContentRequiresValidationMessageAttribute(decoder: $0) })
|
||||
declareEncodable(UpdateMessageReactionsAction.self, f: { UpdateMessageReactionsAction(decoder: $0) })
|
||||
|
||||
return
|
||||
}()
|
||||
|
@ -647,8 +647,7 @@ func finalStateWithDifference(postbox: Postbox, network: Network, state: Account
|
||||
}
|
||||
|
||||
private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] {
|
||||
var result: [Api.Update] = []
|
||||
|
||||
var otherUpdates: [Api.Update] = []
|
||||
var updatesByChannel: [PeerId: [Api.Update]] = [:]
|
||||
|
||||
for update in updates {
|
||||
@ -675,7 +674,7 @@ private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] {
|
||||
updatesByChannel[peerId]!.append(update)
|
||||
}
|
||||
} else {
|
||||
result.append(update)
|
||||
otherUpdates.append(update)
|
||||
}
|
||||
case let .updateEditChannelMessage(message, _, _):
|
||||
if let peerId = apiMessagePeerId(message) {
|
||||
@ -685,7 +684,7 @@ private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] {
|
||||
updatesByChannel[peerId]!.append(update)
|
||||
}
|
||||
} else {
|
||||
result.append(update)
|
||||
otherUpdates.append(update)
|
||||
}
|
||||
case let .updateChannelWebPage(channelId, _, _, _):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
@ -702,10 +701,12 @@ private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] {
|
||||
updatesByChannel[peerId]!.append(update)
|
||||
}
|
||||
default:
|
||||
result.append(update)
|
||||
otherUpdates.append(update)
|
||||
}
|
||||
}
|
||||
|
||||
var result: [Api.Update] = []
|
||||
|
||||
for (_, updates) in updatesByChannel {
|
||||
let sortedUpdates = updates.sorted(by: { lhs, rhs in
|
||||
var lhsPts: Int32?
|
||||
@ -747,6 +748,7 @@ private func sortedUpdates(_ updates: [Api.Update]) -> [Api.Update] {
|
||||
})
|
||||
result.append(contentsOf: sortedUpdates)
|
||||
}
|
||||
result.append(contentsOf: otherUpdates)
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -7,16 +7,20 @@ import Foundation
|
||||
|
||||
public class EditedMessageAttribute: MessageAttribute {
|
||||
public let date: Int32
|
||||
public let isHidden: Bool
|
||||
|
||||
init(date: Int32) {
|
||||
init(date: Int32, isHidden: Bool) {
|
||||
self.date = date
|
||||
self.isHidden = isHidden
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.date = decoder.decodeInt32ForKey("d", orElse: 0)
|
||||
self.isHidden = decoder.decodeInt32ForKey("h", orElse: 0) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.date, forKey: "d")
|
||||
encoder.encodeInt32(self.isHidden ? 1 : 0, forKey: "h")
|
||||
}
|
||||
}
|
||||
|
@ -15,29 +15,241 @@ import MtProtoKitDynamic
|
||||
#endif
|
||||
#endif
|
||||
|
||||
final class UpdateMessageReactionsAction: PendingMessageActionData {
|
||||
init() {
|
||||
}
|
||||
|
||||
public enum RequestUpdateMessageReactionError {
|
||||
init(decoder: PostboxDecoder) {
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
}
|
||||
|
||||
func isEqual(to: PendingMessageActionData) -> Bool {
|
||||
if let _ = to as? UpdateMessageReactionsAction {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateMessageReactionsInteractively(postbox: Postbox, messageId: MessageId, reactions: [String]) -> Signal<Never, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction())
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
loop: for j in 0 ..< attributes.count {
|
||||
if let _ = attributes[j] as? PendingReactionsMessageAttribute {
|
||||
attributes.remove(at: j)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
attributes.append(PendingReactionsMessageAttribute(values: reactions))
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, 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: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
private enum RequestUpdateMessageReactionError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func requestUpdateMessageReaction(account: Account, messageId: MessageId, reactions: [String]) -> Signal<Never, RequestUpdateMessageReactionError> {
|
||||
return account.postbox.loadedPeerWithId(messageId.peerId)
|
||||
|> take(1)
|
||||
private func requestUpdateMessageReaction(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal<Never, RequestUpdateMessageReactionError> {
|
||||
return postbox.transaction { transaction -> (Peer, [String])? in
|
||||
guard let peer = transaction.getPeer(messageId.peerId) else {
|
||||
return nil
|
||||
}
|
||||
guard let message = transaction.getMessage(messageId) else {
|
||||
return nil
|
||||
}
|
||||
var values: [String] = []
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? PendingReactionsMessageAttribute {
|
||||
values = attribute.values
|
||||
break
|
||||
}
|
||||
}
|
||||
return (peer, values)
|
||||
}
|
||||
|> introduceError(RequestUpdateMessageReactionError.self)
|
||||
|> mapToSignal { peer in
|
||||
|> mapToSignal { peerAndValues in
|
||||
guard let (peer, values) = peerAndValues else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
guard let inputPeer = apiInputPeer(peer) else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
if messageId.namespace != Namespaces.Message.Cloud {
|
||||
return .fail(.generic)
|
||||
}
|
||||
return account.network.request(Api.functions.messages.sendReaction(peer: inputPeer, msgId: messageId.id, reaction: reactions))
|
||||
return network.request(Api.functions.messages.sendReaction(peer: inputPeer, msgId: messageId.id, reaction: values))
|
||||
|> mapError { _ -> RequestUpdateMessageReactionError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, RequestUpdateMessageReactionError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
return .complete()
|
||||
return postbox.transaction { transaction -> Void in
|
||||
transaction.setPendingMessageAction(type: .updateReaction, id: messageId, action: UpdateMessageReactionsAction())
|
||||
transaction.updateMessage(messageId, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
let reactions = mergedMessageReactions(attributes: currentMessage.attributes)
|
||||
var attributes = currentMessage.attributes
|
||||
for j in (0 ..< attributes.count).reversed() {
|
||||
if attributes[j] is PendingReactionsMessageAttribute || attributes[j] is ReactionsMessageAttribute {
|
||||
attributes.remove(at: j)
|
||||
}
|
||||
}
|
||||
if let reactions = reactions {
|
||||
attributes.append(reactions)
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, 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: attributes, media: currentMessage.media))
|
||||
})
|
||||
stateManager.addUpdates(result)
|
||||
}
|
||||
|> introduceError(RequestUpdateMessageReactionError.self)
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ManagedApplyPendingMessageReactionsActionsHelper {
|
||||
var operationDisposables: [MessageId: Disposable] = [:]
|
||||
|
||||
func update(entries: [PendingMessageActionsEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) {
|
||||
var disposeOperations: [Disposable] = []
|
||||
var beginOperations: [(PendingMessageActionsEntry, MetaDisposable)] = []
|
||||
|
||||
var hasRunningOperationForPeerId = Set<PeerId>()
|
||||
var validIds = Set<MessageId>()
|
||||
for entry in entries {
|
||||
if !hasRunningOperationForPeerId.contains(entry.id.peerId) {
|
||||
hasRunningOperationForPeerId.insert(entry.id.peerId)
|
||||
validIds.insert(entry.id)
|
||||
|
||||
if self.operationDisposables[entry.id] == nil {
|
||||
let disposable = MetaDisposable()
|
||||
beginOperations.append((entry, disposable))
|
||||
self.operationDisposables[entry.id] = disposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var removeMergedIds: [MessageId] = []
|
||||
for (id, disposable) in self.operationDisposables {
|
||||
if !validIds.contains(id) {
|
||||
removeMergedIds.append(id)
|
||||
disposeOperations.append(disposable)
|
||||
}
|
||||
}
|
||||
|
||||
for id in removeMergedIds {
|
||||
self.operationDisposables.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
return (disposeOperations, beginOperations)
|
||||
}
|
||||
|
||||
func reset() -> [Disposable] {
|
||||
let disposables = Array(self.operationDisposables.values)
|
||||
self.operationDisposables.removeAll()
|
||||
return disposables
|
||||
}
|
||||
}
|
||||
|
||||
private func withTakenAction(postbox: Postbox, type: PendingMessageActionType, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal<Never, NoError>) -> Signal<Never, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||
var result: PendingMessageActionsEntry?
|
||||
|
||||
if let action = transaction.getPendingMessageAction(type: type, id: id) as? UpdateMessageReactionsAction {
|
||||
result = PendingMessageActionsEntry(id: id, action: action)
|
||||
}
|
||||
|
||||
return f(transaction, result)
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
|
||||
func managedApplyPendingMessageReactionsActions(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||
return Signal { _ in
|
||||
let helper = Atomic<ManagedApplyPendingMessageReactionsActionsHelper>(value: ManagedApplyPendingMessageReactionsActionsHelper())
|
||||
|
||||
let actionsKey = PostboxViewKey.pendingMessageActions(type: .updateReaction)
|
||||
let disposable = postbox.combinedView(keys: [actionsKey]).start(next: { view in
|
||||
var entries: [PendingMessageActionsEntry] = []
|
||||
if let v = view.views[actionsKey] as? PendingMessageActionsView {
|
||||
entries = v.entries
|
||||
}
|
||||
|
||||
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) in
|
||||
return helper.update(entries: entries)
|
||||
}
|
||||
|
||||
for disposable in disposeOperations {
|
||||
disposable.dispose()
|
||||
}
|
||||
|
||||
for (entry, disposable) in beginOperations {
|
||||
let signal = withTakenAction(postbox: postbox, type: .updateReaction, id: entry.id, { transaction, entry -> Signal<Never, NoError> in
|
||||
if let entry = entry {
|
||||
if let _ = entry.action as? UpdateMessageReactionsAction {
|
||||
return synchronizeMessageReactions(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, id: entry.id)
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
})
|
||||
|> then(
|
||||
postbox.transaction { transaction -> Void in
|
||||
transaction.setPendingMessageAction(type: .updateReaction, id: entry.id, action: nil)
|
||||
}
|
||||
|> ignoreValues
|
||||
)
|
||||
|
||||
disposable.set(signal.start())
|
||||
}
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
let disposables = helper.with { helper -> [Disposable] in
|
||||
return helper.reset()
|
||||
}
|
||||
for disposable in disposables {
|
||||
disposable.dispose()
|
||||
}
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func synchronizeMessageReactions(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, id: MessageId) -> Signal<Never, NoError> {
|
||||
return requestUpdateMessageReaction(postbox: postbox, network: network, stateManager: stateManager, messageId: id)
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return postbox.transaction { transaction -> Void in
|
||||
transaction.setPendingMessageAction(type: .updateReaction, id: id, action: nil)
|
||||
transaction.updateMessage(id, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
var attributes = currentMessage.attributes
|
||||
loop: for j in 0 ..< attributes.count {
|
||||
if let _ = attributes[j] as? PendingReactionsMessageAttribute {
|
||||
attributes.remove(at: j)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, 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: attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ public extension LocalMessageTags {
|
||||
|
||||
public extension PendingMessageActionType {
|
||||
static let consumeUnseenPersonalMessage = PendingMessageActionType(rawValue: 0)
|
||||
static let updateReaction = PendingMessageActionType(rawValue: 1)
|
||||
}
|
||||
|
||||
let peerIdNamespacesWithInitialCloudMessageHoles = [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel]
|
||||
@ -135,9 +136,9 @@ public struct OperationLogTags {
|
||||
}
|
||||
|
||||
public extension PeerSummaryCounterTags {
|
||||
public static let regularChatsAndPrivateGroups = PeerSummaryCounterTags(rawValue: 1 << 0)
|
||||
public static let publicGroups = PeerSummaryCounterTags(rawValue: 1 << 1)
|
||||
public static let channels = PeerSummaryCounterTags(rawValue: 1 << 2)
|
||||
static let regularChatsAndPrivateGroups = PeerSummaryCounterTags(rawValue: 1 << 0)
|
||||
static let publicGroups = PeerSummaryCounterTags(rawValue: 1 << 1)
|
||||
static let channels = PeerSummaryCounterTags(rawValue: 1 << 2)
|
||||
}
|
||||
|
||||
private enum PreferencesKeyValues: Int32 {
|
||||
|
@ -31,7 +31,7 @@ public struct MessageReaction: Equatable, PostboxCoding {
|
||||
}
|
||||
}
|
||||
|
||||
public class ReactionsMessageAttribute: MessageAttribute {
|
||||
public final class ReactionsMessageAttribute: MessageAttribute {
|
||||
public let reactions: [MessageReaction]
|
||||
|
||||
init(reactions: [MessageReaction]) {
|
||||
@ -77,6 +77,72 @@ public class ReactionsMessageAttribute: MessageAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
public func mergedMessageReactions(attributes: [MessageAttribute]) -> ReactionsMessageAttribute? {
|
||||
var current: ReactionsMessageAttribute?
|
||||
var pending: PendingReactionsMessageAttribute?
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? ReactionsMessageAttribute {
|
||||
current = attribute
|
||||
} else if let attribute = attribute as? PendingReactionsMessageAttribute {
|
||||
pending = attribute
|
||||
}
|
||||
}
|
||||
|
||||
if let pending = pending {
|
||||
var reactions = current?.reactions ?? []
|
||||
for value in pending.values {
|
||||
var found = false
|
||||
for i in 0 ..< reactions.count {
|
||||
if reactions[i].value == value {
|
||||
found = true
|
||||
if !reactions[i].isSelected {
|
||||
reactions[i].isSelected = true
|
||||
reactions[i].count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
reactions.append(MessageReaction(value: value, count: 1, isSelected: true))
|
||||
}
|
||||
}
|
||||
for i in (0 ..< reactions.count).reversed() {
|
||||
if reactions[i].isSelected, !pending.values.contains(reactions[i].value) {
|
||||
if reactions[i].count == 1 {
|
||||
reactions.remove(at: i)
|
||||
} else {
|
||||
reactions[i].isSelected = false
|
||||
reactions[i].count -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if !reactions.isEmpty {
|
||||
return ReactionsMessageAttribute(reactions: reactions)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else if let current = current {
|
||||
return current
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public final class PendingReactionsMessageAttribute: MessageAttribute {
|
||||
public let values: [String]
|
||||
|
||||
init(values: [String]) {
|
||||
self.values = values
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.values = decoder.decodeStringArrayForKey("v")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeStringArray(self.values, forKey: "v")
|
||||
}
|
||||
}
|
||||
|
||||
extension ReactionsMessageAttribute {
|
||||
convenience init(apiReactions: Api.MessageReactions) {
|
||||
switch apiReactions {
|
||||
|
@ -506,7 +506,7 @@ extension StoreMessage {
|
||||
}
|
||||
|
||||
if let editDate = editDate {
|
||||
attributes.append(EditedMessageAttribute(date: editDate))
|
||||
attributes.append(EditedMessageAttribute(date: editDate, isHidden: (flags & (1 << 21)) != 0))
|
||||
}
|
||||
|
||||
var entitiesAttribute: TextEntitiesMessageAttribute?
|
||||
|
Loading…
x
Reference in New Issue
Block a user