mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
521 lines
21 KiB
Swift
521 lines
21 KiB
Swift
import Foundation
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import Postbox
|
|
import AccountContext
|
|
import ReactionSelectionNode
|
|
|
|
public enum AllowedReactions {
|
|
case set(Set<MessageReaction.Reaction>)
|
|
case all
|
|
}
|
|
|
|
public func peerMessageAllowedReactions(context: AccountContext, message: Message) -> Signal<(allowedReactions: AllowedReactions?, areStarsEnabled: Bool), NoError> {
|
|
if message.id.peerId == context.account.peerId {
|
|
return .single((.all, false))
|
|
}
|
|
|
|
// if message.containsSecretMedia {
|
|
// return .single((AllowedReactions.set(Set()), false))
|
|
// }
|
|
|
|
return combineLatest(
|
|
context.engine.data.get(
|
|
TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId),
|
|
TelegramEngine.EngineData.Item.Peer.ReactionSettings(id: message.id.peerId)
|
|
),
|
|
context.engine.stickers.availableReactions() |> take(1)
|
|
)
|
|
|> map { data, availableReactions -> (allowedReactions: AllowedReactions?, areStarsEnabled: Bool) in
|
|
let (peer, reactionSettings) = data
|
|
|
|
let maxReactionCount: Int
|
|
if let value = reactionSettings.knownValue?.maxReactionCount {
|
|
maxReactionCount = Int(value)
|
|
} else {
|
|
maxReactionCount = 11
|
|
}
|
|
|
|
var areStarsEnabled: Bool = false
|
|
if let value = reactionSettings.knownValue?.starsAllowed {
|
|
areStarsEnabled = value
|
|
}
|
|
|
|
if let effectiveReactions = message.effectiveReactions(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)), effectiveReactions.count >= maxReactionCount {
|
|
return (.set(Set(effectiveReactions.map(\.value))), areStarsEnabled)
|
|
}
|
|
|
|
switch reactionSettings {
|
|
case .unknown:
|
|
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
|
if let availableReactions = availableReactions {
|
|
return (.set(Set(availableReactions.reactions.map(\.value))), areStarsEnabled)
|
|
} else {
|
|
return (.set(Set()), areStarsEnabled)
|
|
}
|
|
}
|
|
return (.all, areStarsEnabled)
|
|
case let .known(value):
|
|
switch value.allowedReactions {
|
|
case .all:
|
|
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
|
if let availableReactions = availableReactions {
|
|
return (.set(Set(availableReactions.reactions.map(\.value))), areStarsEnabled)
|
|
} else {
|
|
return (.set(Set()), areStarsEnabled)
|
|
}
|
|
}
|
|
return (.all, areStarsEnabled)
|
|
case let .limited(reactions):
|
|
return (.set(Set(reactions)), areStarsEnabled)
|
|
case .empty:
|
|
return (.set(Set()), areStarsEnabled)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public func tagMessageReactions(context: AccountContext, subPeerId: EnginePeer.Id?) -> Signal<[ReactionItem], NoError> {
|
|
let topTags: Signal<([MessageReaction.Reaction], [Int64: TelegramMediaFile]), NoError> = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: subPeerId?.toInt64()))
|
|
|> mapToSignal { tagStats -> Signal<([MessageReaction.Reaction], [Int64: TelegramMediaFile]), NoError> in
|
|
let reactions = tagStats.sorted(by: { lhs, rhs in
|
|
if lhs.value != rhs.value {
|
|
return lhs.value > rhs.value
|
|
}
|
|
return lhs.key < rhs.key
|
|
}).filter({ $0.value > 0 }).map(\.key)
|
|
|
|
var customFileIds: [Int64] = []
|
|
for reaction in reactions {
|
|
if case let .custom(fileId) = reaction {
|
|
if !customFileIds.contains(fileId) {
|
|
customFileIds.append(fileId)
|
|
}
|
|
}
|
|
}
|
|
|
|
return context.engine.stickers.resolveInlineStickersLocal(fileIds: customFileIds)
|
|
|> map { files -> ([MessageReaction.Reaction], [Int64: TelegramMediaFile]) in
|
|
return (reactions, files)
|
|
}
|
|
}
|
|
|
|
return combineLatest(
|
|
context.engine.stickers.availableReactions(),
|
|
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudDefaultTagReactions], namespaces: [ItemCollectionId.Namespace.max - 1], aroundIndex: nil, count: 10000000),
|
|
topTags
|
|
)
|
|
|> take(1)
|
|
|> map { availableReactions, view, topTags -> [ReactionItem] in
|
|
var defaultTagReactions: OrderedItemListView?
|
|
for orderedView in view.orderedItemListsViews {
|
|
if orderedView.collectionId == Namespaces.OrderedItemList.CloudDefaultTagReactions {
|
|
defaultTagReactions = orderedView
|
|
}
|
|
}
|
|
|
|
var result: [ReactionItem] = []
|
|
var existingIds = Set<MessageReaction.Reaction>()
|
|
|
|
for reactionValue in topTags.0 {
|
|
switch reactionValue {
|
|
case let .builtin(value):
|
|
if let reaction = availableReactions?.reactions.first(where: { $0.value == .builtin(value) }) {
|
|
guard let centerAnimation = reaction.centerAnimation else {
|
|
continue
|
|
}
|
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
|
continue
|
|
}
|
|
|
|
if existingIds.contains(reaction.value) {
|
|
continue
|
|
}
|
|
existingIds.insert(reaction.value)
|
|
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
|
appearAnimation: reaction.appearAnimation,
|
|
stillAnimation: reaction.selectAnimation,
|
|
listAnimation: centerAnimation,
|
|
largeListAnimation: reaction.activateAnimation,
|
|
applicationAnimation: aroundAnimation,
|
|
largeApplicationAnimation: reaction.effectAnimation,
|
|
isCustom: false
|
|
))
|
|
} else {
|
|
continue
|
|
}
|
|
case let .custom(fileId):
|
|
guard let file = topTags.1[fileId] else {
|
|
continue
|
|
}
|
|
|
|
if existingIds.contains(.custom(file.fileId.id)) {
|
|
continue
|
|
}
|
|
existingIds.insert(.custom(file.fileId.id))
|
|
|
|
let itemFile = TelegramMediaFile.Accessor(file)
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)),
|
|
appearAnimation: itemFile,
|
|
stillAnimation: itemFile,
|
|
listAnimation: itemFile,
|
|
largeListAnimation: itemFile,
|
|
applicationAnimation: nil,
|
|
largeApplicationAnimation: nil,
|
|
isCustom: true
|
|
))
|
|
case .stars:
|
|
continue
|
|
}
|
|
}
|
|
|
|
if let defaultTagReactions {
|
|
for item in defaultTagReactions.items {
|
|
guard let topReaction = item.contents.get(RecentReactionItem.self) else {
|
|
continue
|
|
}
|
|
switch topReaction.content {
|
|
case let .builtin(value):
|
|
if let reaction = availableReactions?.reactions.first(where: { $0.value == .builtin(value) }) {
|
|
guard let centerAnimation = reaction.centerAnimation else {
|
|
continue
|
|
}
|
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
|
continue
|
|
}
|
|
|
|
if existingIds.contains(reaction.value) {
|
|
continue
|
|
}
|
|
existingIds.insert(reaction.value)
|
|
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
|
appearAnimation: reaction.appearAnimation,
|
|
stillAnimation: reaction.selectAnimation,
|
|
listAnimation: centerAnimation,
|
|
largeListAnimation: reaction.activateAnimation,
|
|
applicationAnimation: aroundAnimation,
|
|
largeApplicationAnimation: reaction.effectAnimation,
|
|
isCustom: false
|
|
))
|
|
} else {
|
|
continue
|
|
}
|
|
case let .custom(file):
|
|
if existingIds.contains(.custom(file.fileId.id)) {
|
|
continue
|
|
}
|
|
existingIds.insert(.custom(file.fileId.id))
|
|
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)),
|
|
appearAnimation: file,
|
|
stillAnimation: file,
|
|
listAnimation: file,
|
|
largeListAnimation: file,
|
|
applicationAnimation: nil,
|
|
largeApplicationAnimation: nil,
|
|
isCustom: true
|
|
))
|
|
case .stars:
|
|
if let reaction = availableReactions?.reactions.first(where: { $0.value == .stars }) {
|
|
guard let centerAnimation = reaction.centerAnimation else {
|
|
continue
|
|
}
|
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
|
continue
|
|
}
|
|
|
|
if existingIds.contains(reaction.value) {
|
|
continue
|
|
}
|
|
existingIds.insert(reaction.value)
|
|
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
|
appearAnimation: reaction.appearAnimation,
|
|
stillAnimation: reaction.selectAnimation,
|
|
listAnimation: centerAnimation,
|
|
largeListAnimation: reaction.activateAnimation,
|
|
applicationAnimation: aroundAnimation,
|
|
largeApplicationAnimation: reaction.effectAnimation,
|
|
isCustom: false
|
|
))
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
public func topMessageReactions(context: AccountContext, message: Message, subPeerId: EnginePeer.Id?) -> Signal<[ReactionItem], NoError> {
|
|
if message.id.peerId == context.account.peerId {
|
|
var loadTags = false
|
|
if let effectiveReactionsAttribute = message.effectiveReactionsAttribute(isTags: message.areReactionsTags(accountPeerId: context.account.peerId)) {
|
|
loadTags = true
|
|
if !effectiveReactionsAttribute.reactions.isEmpty {
|
|
if !effectiveReactionsAttribute.isTags {
|
|
loadTags = false
|
|
}
|
|
}
|
|
} else {
|
|
loadTags = true
|
|
}
|
|
|
|
if loadTags {
|
|
return tagMessageReactions(context: context, subPeerId: subPeerId)
|
|
}
|
|
}
|
|
|
|
let viewKey: PostboxViewKey = .orderedItemList(id: Namespaces.OrderedItemList.CloudTopReactions)
|
|
let topReactions = context.account.postbox.combinedView(keys: [viewKey])
|
|
|> map { views -> [RecentReactionItem] in
|
|
guard let view = views.views[viewKey] as? OrderedItemListView else {
|
|
return []
|
|
}
|
|
return view.items.compactMap { item -> RecentReactionItem? in
|
|
return item.contents.get(RecentReactionItem.self)
|
|
}
|
|
}
|
|
|
|
let allowedReactionsWithFiles: Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool)?, NoError> = peerMessageAllowedReactions(context: context, message: message)
|
|
|> mapToSignal { allowedReactions, areStarsEnabled -> Signal<(reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool)?, NoError> in
|
|
guard let allowedReactions = allowedReactions else {
|
|
return .single(nil)
|
|
}
|
|
|
|
if case let .set(reactions) = allowedReactions {
|
|
return context.engine.stickers.resolveInlineStickers(fileIds: reactions.compactMap { item -> Int64? in
|
|
switch item {
|
|
case .builtin:
|
|
return nil
|
|
case let .custom(fileId):
|
|
return fileId
|
|
case .stars:
|
|
return nil
|
|
}
|
|
})
|
|
|> map { files -> (reactions: AllowedReactions, files: [Int64: TelegramMediaFile], areStarsEnabled: Bool) in
|
|
return (.set(reactions), files, areStarsEnabled)
|
|
}
|
|
} else {
|
|
return .single((allowedReactions, [:], areStarsEnabled))
|
|
}
|
|
}
|
|
|
|
return combineLatest(
|
|
context.engine.stickers.availableReactions(),
|
|
allowedReactionsWithFiles,
|
|
topReactions
|
|
)
|
|
|> take(1)
|
|
|> map { availableReactions, allowedReactionsAndFiles, topReactions -> [ReactionItem] in
|
|
guard let availableReactions = availableReactions, let allowedReactionsAndFiles = allowedReactionsAndFiles else {
|
|
return []
|
|
}
|
|
|
|
var result: [ReactionItem] = []
|
|
var existingIds = Set<MessageReaction.Reaction>()
|
|
|
|
for topReaction in topReactions {
|
|
switch topReaction.content {
|
|
case let .builtin(value):
|
|
if let reaction = availableReactions.reactions.first(where: { $0.value == .builtin(value) }) {
|
|
guard let centerAnimation = reaction.centerAnimation else {
|
|
continue
|
|
}
|
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
|
continue
|
|
}
|
|
|
|
if existingIds.contains(reaction.value) {
|
|
continue
|
|
}
|
|
existingIds.insert(reaction.value)
|
|
|
|
switch allowedReactionsAndFiles.reactions {
|
|
case let .set(set):
|
|
if !set.contains(reaction.value) {
|
|
continue
|
|
}
|
|
case .all:
|
|
break
|
|
}
|
|
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
|
appearAnimation: reaction.appearAnimation,
|
|
stillAnimation: reaction.selectAnimation,
|
|
listAnimation: centerAnimation,
|
|
largeListAnimation: reaction.activateAnimation,
|
|
applicationAnimation: aroundAnimation,
|
|
largeApplicationAnimation: reaction.effectAnimation,
|
|
isCustom: false
|
|
))
|
|
} else {
|
|
continue
|
|
}
|
|
case let .custom(file):
|
|
switch allowedReactionsAndFiles.reactions {
|
|
case let .set(set):
|
|
if !set.contains(.custom(file.fileId.id)) {
|
|
continue
|
|
}
|
|
case .all:
|
|
break
|
|
}
|
|
|
|
if existingIds.contains(.custom(file.fileId.id)) {
|
|
continue
|
|
}
|
|
existingIds.insert(.custom(file.fileId.id))
|
|
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)),
|
|
appearAnimation: file,
|
|
stillAnimation: file,
|
|
listAnimation: file,
|
|
largeListAnimation: file,
|
|
applicationAnimation: nil,
|
|
largeApplicationAnimation: nil,
|
|
isCustom: true
|
|
))
|
|
case .stars:
|
|
break
|
|
}
|
|
}
|
|
|
|
for reaction in availableReactions.reactions {
|
|
guard let centerAnimation = reaction.centerAnimation else {
|
|
continue
|
|
}
|
|
guard let aroundAnimation = reaction.aroundAnimation else {
|
|
continue
|
|
}
|
|
if !reaction.isEnabled {
|
|
continue
|
|
}
|
|
|
|
switch allowedReactionsAndFiles.reactions {
|
|
case let .set(set):
|
|
if !set.contains(reaction.value) {
|
|
continue
|
|
}
|
|
case .all:
|
|
continue
|
|
}
|
|
|
|
if existingIds.contains(reaction.value) {
|
|
continue
|
|
}
|
|
existingIds.insert(reaction.value)
|
|
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
|
appearAnimation: reaction.appearAnimation,
|
|
stillAnimation: reaction.selectAnimation,
|
|
listAnimation: centerAnimation,
|
|
largeListAnimation: reaction.activateAnimation,
|
|
applicationAnimation: aroundAnimation,
|
|
largeApplicationAnimation: reaction.effectAnimation,
|
|
isCustom: false
|
|
))
|
|
}
|
|
|
|
if case let .set(reactions) = allowedReactionsAndFiles.reactions {
|
|
for reaction in reactions {
|
|
if existingIds.contains(reaction) {
|
|
continue
|
|
}
|
|
existingIds.insert(reaction)
|
|
|
|
switch reaction {
|
|
case .builtin:
|
|
break
|
|
case let .custom(fileId):
|
|
if let file = allowedReactionsAndFiles.files[fileId] {
|
|
let file = TelegramMediaFile.Accessor(file)
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: .custom(file.fileId.id)),
|
|
appearAnimation: file,
|
|
stillAnimation: file,
|
|
listAnimation: file,
|
|
largeListAnimation: file,
|
|
applicationAnimation: nil,
|
|
largeApplicationAnimation: nil,
|
|
isCustom: true
|
|
))
|
|
}
|
|
case .stars:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if allowedReactionsAndFiles.areStarsEnabled {
|
|
result.removeAll(where: { $0.reaction.rawValue == .stars })
|
|
if let reaction = availableReactions.reactions.first(where: { $0.value == .stars }) {
|
|
if let centerAnimation = reaction.centerAnimation, let aroundAnimation = reaction.aroundAnimation {
|
|
existingIds.insert(reaction.value)
|
|
|
|
result.insert(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
|
appearAnimation: reaction.appearAnimation,
|
|
stillAnimation: reaction.selectAnimation,
|
|
listAnimation: centerAnimation,
|
|
largeListAnimation: reaction.activateAnimation,
|
|
applicationAnimation: aroundAnimation,
|
|
largeApplicationAnimation: reaction.effectAnimation,
|
|
isCustom: false
|
|
), at: 0)
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
public func effectMessageReactions(context: AccountContext) -> Signal<[ReactionItem], NoError> {
|
|
return context.engine.stickers.availableMessageEffects()
|
|
|> take(1)
|
|
|> map { availableMessageEffects -> [ReactionItem] in
|
|
guard let availableMessageEffects else {
|
|
return []
|
|
}
|
|
|
|
var result: [ReactionItem] = []
|
|
var existingIds = Set<Int64>()
|
|
|
|
for messageEffect in availableMessageEffects.messageEffects {
|
|
if existingIds.contains(messageEffect.id) {
|
|
continue
|
|
}
|
|
existingIds.insert(messageEffect.id)
|
|
|
|
let mainFile = messageEffect.effectSticker
|
|
|
|
result.append(ReactionItem(
|
|
reaction: ReactionItem.Reaction(rawValue: .custom(messageEffect.id)),
|
|
appearAnimation: mainFile,
|
|
stillAnimation: mainFile,
|
|
listAnimation: mainFile,
|
|
largeListAnimation: mainFile,
|
|
applicationAnimation: nil,
|
|
largeApplicationAnimation: nil,
|
|
isCustom: true
|
|
))
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|