Swiftgram/submodules/TelegramCore/Sources/State/AvailableReactions.swift
2022-12-17 00:17:31 +04:00

346 lines
15 KiB
Swift

import Foundation
import TelegramApi
import Postbox
import SwiftSignalKit
public final class AvailableReactions: Equatable, Codable {
public final class Reaction: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case isEnabled
case isPremium
case value
case title
case staticIcon
case appearAnimation
case selectAnimation
case activateAnimation
case effectAnimation
case aroundAnimation
case centerAnimation
}
public let isEnabled: Bool
public let isPremium: Bool
public let value: MessageReaction.Reaction
public let title: String
public let staticIcon: TelegramMediaFile
public let appearAnimation: TelegramMediaFile
public let selectAnimation: TelegramMediaFile
public let activateAnimation: TelegramMediaFile
public let effectAnimation: TelegramMediaFile
public let aroundAnimation: TelegramMediaFile?
public let centerAnimation: TelegramMediaFile?
public init(
isEnabled: Bool,
isPremium: Bool,
value: MessageReaction.Reaction,
title: String,
staticIcon: TelegramMediaFile,
appearAnimation: TelegramMediaFile,
selectAnimation: TelegramMediaFile,
activateAnimation: TelegramMediaFile,
effectAnimation: TelegramMediaFile,
aroundAnimation: TelegramMediaFile?,
centerAnimation: TelegramMediaFile?
) {
self.isEnabled = isEnabled
self.isPremium = isPremium
self.value = value
self.title = title
self.staticIcon = staticIcon
self.appearAnimation = appearAnimation
self.selectAnimation = selectAnimation
self.activateAnimation = activateAnimation
self.effectAnimation = effectAnimation
self.aroundAnimation = aroundAnimation
self.centerAnimation = centerAnimation
}
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
if lhs.isEnabled != rhs.isEnabled {
return false
}
if lhs.isPremium != rhs.isPremium {
return false
}
if lhs.value != rhs.value {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.staticIcon != rhs.staticIcon {
return false
}
if lhs.appearAnimation != rhs.appearAnimation {
return false
}
if lhs.selectAnimation != rhs.selectAnimation {
return false
}
if lhs.activateAnimation != rhs.activateAnimation {
return false
}
if lhs.effectAnimation != rhs.effectAnimation {
return false
}
if lhs.aroundAnimation != rhs.aroundAnimation {
return false
}
if lhs.centerAnimation != rhs.centerAnimation {
return false
}
return true
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.isEnabled = try container.decode(Bool.self, forKey: .isEnabled)
self.isPremium = try container.decodeIfPresent(Bool.self, forKey: .isPremium) ?? false
self.value = .builtin(try container.decode(String.self, forKey: .value))
self.title = try container.decode(String.self, forKey: .title)
let staticIconData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .staticIcon)
self.staticIcon = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: staticIconData.data)))
let appearAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .appearAnimation)
self.appearAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: appearAnimationData.data)))
let selectAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .selectAnimation)
self.selectAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: selectAnimationData.data)))
let activateAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .activateAnimation)
self.activateAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: activateAnimationData.data)))
let effectAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectAnimation)
self.effectAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectAnimationData.data)))
if let aroundAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .aroundAnimation) {
self.aroundAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: aroundAnimationData.data)))
} else {
self.aroundAnimation = nil
}
if let centerAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .centerAnimation) {
self.centerAnimation = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: centerAnimationData.data)))
} else {
self.centerAnimation = nil
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.isEnabled, forKey: .isEnabled)
try container.encode(self.isPremium, forKey: .isPremium)
switch self.value {
case let .builtin(value):
try container.encode(value, forKey: .value)
case .custom:
break
}
try container.encode(self.title, forKey: .title)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.staticIcon), forKey: .staticIcon)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.appearAnimation), forKey: .appearAnimation)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.selectAnimation), forKey: .selectAnimation)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.activateAnimation), forKey: .activateAnimation)
try container.encode(PostboxEncoder().encodeObjectToRawData(self.effectAnimation), forKey: .effectAnimation)
if let aroundAnimation = self.aroundAnimation {
try container.encode(PostboxEncoder().encodeObjectToRawData(aroundAnimation), forKey: .aroundAnimation)
}
if let centerAnimation = self.centerAnimation {
try container.encode(PostboxEncoder().encodeObjectToRawData(centerAnimation), forKey: .centerAnimation)
}
}
}
private enum CodingKeys: String, CodingKey {
case newHash
case reactions
}
public let hash: Int32
public let reactions: [Reaction]
public init(
hash: Int32,
reactions: [Reaction]
) {
self.hash = hash
self.reactions = reactions
}
public static func ==(lhs: AvailableReactions, rhs: AvailableReactions) -> Bool {
if lhs.hash != rhs.hash {
return false
}
if lhs.reactions != rhs.reactions {
return false
}
return true
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.hash = try container.decodeIfPresent(Int32.self, forKey: .newHash) ?? 0
self.reactions = try container.decode([Reaction].self, forKey: .reactions)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.hash, forKey: .newHash)
try container.encode(self.reactions, forKey: .reactions)
}
}
private extension AvailableReactions.Reaction {
convenience init?(apiReaction: Api.AvailableReaction) {
switch apiReaction {
case let .availableReaction(flags, reaction, title, staticIcon, appearAnimation, selectAnimation, activateAnimation, effectAnimation, aroundAnimation, centerIcon):
guard let staticIconFile = telegramMediaFileFromApiDocument(staticIcon) else {
return nil
}
guard let appearAnimationFile = telegramMediaFileFromApiDocument(appearAnimation) else {
return nil
}
guard let selectAnimationFile = telegramMediaFileFromApiDocument(selectAnimation) else {
return nil
}
guard let activateAnimationFile = telegramMediaFileFromApiDocument(activateAnimation) else {
return nil
}
guard let effectAnimationFile = telegramMediaFileFromApiDocument(effectAnimation) else {
return nil
}
let aroundAnimationFile = aroundAnimation.flatMap { telegramMediaFileFromApiDocument($0) }
let centerAnimationFile = centerIcon.flatMap { telegramMediaFileFromApiDocument($0) }
let isEnabled = (flags & (1 << 0)) == 0
let isPremium = (flags & (1 << 2)) != 0
self.init(
isEnabled: isEnabled,
isPremium: isPremium,
value: .builtin(reaction),
title: title,
staticIcon: staticIconFile,
appearAnimation: appearAnimationFile,
selectAnimation: selectAnimationFile,
activateAnimation: activateAnimationFile,
effectAnimation: effectAnimationFile,
aroundAnimation: aroundAnimationFile,
centerAnimation: centerAnimationFile
)
}
}
}
func _internal_cachedAvailableReactions(postbox: Postbox) -> Signal<AvailableReactions?, NoError> {
return postbox.transaction { transaction -> AvailableReactions? in
return _internal_cachedAvailableReactions(transaction: transaction)
}
}
func _internal_cachedAvailableReactions(transaction: Transaction) -> AvailableReactions? {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: 0)
let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.availableReactions, key: key))?.get(AvailableReactions.self)
if let cached = cached {
return cached
} else {
return nil
}
}
func _internal_setCachedAvailableReactions(transaction: Transaction, availableReactions: AvailableReactions) {
let key = ValueBoxKey(length: 8)
key.setInt64(0, value: 0)
if let entry = CodableEntry(availableReactions) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.availableReactions, key: key), entry: entry)
}
}
func managedSynchronizeAvailableReactions(postbox: Postbox, network: Network) -> Signal<Never, NoError> {
let poll = Signal<Never, NoError> { subscriber in
let signal: Signal<Never, NoError> = _internal_cachedAvailableReactions(postbox: postbox)
|> mapToSignal { current in
return (network.request(Api.functions.messages.getAvailableReactions(hash: current?.hash ?? 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.AvailableReactions?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Never, NoError> in
return postbox.transaction { transaction -> Signal<Never, NoError> in
guard let result = result else {
return .complete()
}
switch result {
case let .availableReactions(hash, reactions):
let availableReactions = AvailableReactions(
hash: hash,
reactions: reactions.compactMap(AvailableReactions.Reaction.init(apiReaction:))
)
_internal_setCachedAvailableReactions(transaction: transaction, availableReactions: availableReactions)
case .availableReactionsNotModified:
break
}
var signals: [Signal<Never, NoError>] = []
if let availableReactions = _internal_cachedAvailableReactions(transaction: transaction) {
var resources: [MediaResource] = []
for reaction in availableReactions.reactions {
resources.append(reaction.staticIcon.resource)
resources.append(reaction.appearAnimation.resource)
resources.append(reaction.selectAnimation.resource)
resources.append(reaction.activateAnimation.resource)
resources.append(reaction.effectAnimation.resource)
if let centerAnimation = reaction.centerAnimation {
resources.append(centerAnimation.resource)
}
if let aroundAnimation = reaction.aroundAnimation {
resources.append(aroundAnimation.resource)
}
}
for resource in resources {
signals.append(
fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .other, reference: .standalone(resource: resource))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
)
}
}
return combineLatest(signals)
|> ignoreValues
}
|> switchToLatest
})
}
return signal.start(completed: {
subscriber.putCompletion()
})
}
return (
poll
|> then(
.complete()
|> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue())
)
)
|> restart
}