From af01ec906f9637b8fcad31d00e778dcd7923ca9f Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 23 Dec 2021 00:34:53 +0400 Subject: [PATCH] Reaction improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + ...tControllerExtractedPresentationNode.swift | 27 ++++++-- .../PeerAllowedReactionListController.swift | 6 ++ .../Sources/ReactionContextNode.swift | 20 +++--- .../Sources/ReactionSelectionNode.swift | 45 +++++++++---- .../QuickReactionSetupController.swift | 16 +++-- submodules/TelegramApi/Sources/Api0.swift | 3 +- submodules/TelegramApi/Sources/Api2.swift | 66 +++++++++++++++---- .../Sources/Settings/ReactionSettings.swift | 14 ++-- .../Sources/State/AvailableReactions.swift | 15 ++++- .../ManagedAppConfigurationUpdates.swift | 8 +++ .../Sources/State/MessageReactions.swift | 8 +++ .../Peers/ChannelAdminEventLogs.swift | 3 + .../Stickers/TelegramEngineStickers.swift | 9 +++ .../TelegramUI/Sources/ChatController.swift | 4 ++ .../ChatRecentActionsHistoryTransition.swift | 32 +++++++++ .../PeerInfoScreenDisclosureItem.swift | 6 +- 17 files changed, 230 insertions(+), 55 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 08524f9e6c..ac24856e0f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7195,3 +7195,6 @@ Sorry for the inconvenience."; "Localization.InterfaceLanguage" = "Interface Language"; "DoNotTranslate.Title" = "Do Not Translate"; + +"Channel.AdminLog.AllowedReactionsUpdated" = "%1$@ updated the list of allowed reactions to: %2$@"; +"Channel.AdminLog.ReactionsDisabled" = "%1$@ disabled reactions"; diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index b6ccc1f53f..7a8dbdbf98 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -532,13 +532,28 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo public var updateDistractionFreeMode: ((Bool) -> Void)? public var requestDismiss: (() -> Void)*/ case let .animateOut(result, completion): - let duration: Double = self.reactionContextNodeIsAnimatingOut ? 0.25 : 0.2 + let duration: Double + let timingFunction: String + switch result { + case .default, .dismissWithoutContent: + duration = self.reactionContextNodeIsAnimatingOut ? 0.25 : 0.2 + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + case let .custom(customTransition): + switch customTransition { + case let .animated(customDuration, curve): + duration = customDuration + timingFunction = curve.timingFunction + case .immediate: + duration = self.reactionContextNodeIsAnimatingOut ? 0.25 : 0.2 + timingFunction = CAMediaTimingFunctionName.easeInEaseOut.rawValue + } + } let putBackInfo = self.source.putBack() if let putBackInfo = putBackInfo { - self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, removeOnCompletion: false) - self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, removeOnCompletion: false) + self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false) + self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false) } let currentContentScreenFrame = convertFrame(contentNode.containingNode.contentRect, from: contentNode.containingNode.view, to: self.view) @@ -574,7 +589,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo from: animationInContentDistance as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", - timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, + timingFunction: timingFunction, duration: duration, delay: 0.0, additive: true, @@ -597,7 +612,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo from: 1.0 as NSNumber, to: 0.01 as NSNumber, keyPath: "transform.scale", - timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, + timingFunction: timingFunction, duration: duration, delay: 0.0, removeOnCompletion: false @@ -611,7 +626,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo from: NSValue(cgPoint: CGPoint()), to: NSValue(cgPoint: CGPoint(x: actionsPositionDeltaXDistance, y: actionsPositionDeltaYDistance)), keyPath: "position", - timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, + timingFunction: timingFunction, duration: duration, delay: 0.0, removeOnCompletion: false, diff --git a/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift b/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift index 93807265cb..bdf88e6e35 100644 --- a/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift +++ b/submodules/PeerInfoUI/Sources/PeerAllowedReactionListController.swift @@ -162,6 +162,9 @@ private func peerAllowedReactionListControllerEntries( entries.append(.itemsHeader("AVAILABLE REACTIONS")) var index = 0 for availableReaction in availableReactions.reactions { + if !availableReaction.isEnabled { + continue + } entries.append(.item(index: index, value: availableReaction.value, file: availableReaction.staticIcon, text: availableReaction.title, isEnabled: allowedReactions.contains(availableReaction.value))) index += 1 } @@ -217,6 +220,9 @@ public func peerAllowedReactionListController( if var updatedAllowedReactions = state.updatedAllowedReactions { if updatedAllowedReactions.isEmpty { for availableReaction in availableReactions.reactions { + if !availableReaction.isEnabled { + continue + } updatedAllowedReactions.insert(availableReaction.value) } } else { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index afdf304fe0..5846b215e2 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -194,13 +194,17 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { let itemOffsetY: CGFloat = -1.0 - let itemFrame = CGRect(origin: CGPoint(x: sideInset + column * (itemSize + itemSpacing), y: verticalInset + floor((rowHeight - itemSize) / 2.0) + itemOffsetY), size: CGSize(width: itemSize, height: itemSize)) - /*if self.highlightedReaction == self.items[i].reaction { - itemFrame = itemFrame.insetBy(dx: -6.0, dy: -6.0) - }*/ - if visibleBounds.intersects(itemFrame) { + let baseItemFrame = CGRect(origin: CGPoint(x: sideInset + column * (itemSize + itemSpacing), y: verticalInset + floor((rowHeight - itemSize) / 2.0) + itemOffsetY), size: CGSize(width: itemSize, height: itemSize)) + if visibleBounds.intersects(baseItemFrame) { validIndices.insert(i) + var itemFrame = baseItemFrame + var isPreviewing = false + if self.highlightedReaction == self.items[i].reaction { + itemFrame = itemFrame.insetBy(dx: -6.0, dy: -6.0) + isPreviewing = true + } + var animateIn = false let itemNode: ReactionNode @@ -216,7 +220,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { if !itemNode.isExtracted { transition.updateFrame(node: itemNode, frame: itemFrame, beginWithCurrentState: true) - itemNode.updateLayout(size: itemFrame.size, isExpanded: false, transition: transition) + itemNode.updateLayout(size: itemFrame.size, isExpanded: false, isPreviewing: isPreviewing, transition: transition) if animateIn { itemNode.animateIn() @@ -433,7 +437,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { //itemNode.position = selfSourceRect.center itemNode.position = expandedFrame.center transition.updateBounds(node: itemNode, bounds: CGRect(origin: CGPoint(), size: expandedFrame.size)) - itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, transition: transition) + itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: transition) transition.animatePositionWithKeyframes(node: itemNode, keyframes: generateParabollicMotionKeyframes(from: selfSourceRect.center, to: expandedFrame.center, elevation: 30.0)) @@ -632,7 +636,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode { self.addSubnode(itemNode) itemNode.frame = expandedFrame - itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, transition: .immediate) + itemNode.updateLayout(size: expandedFrame.size, isExpanded: true, isPreviewing: false, transition: .immediate) itemNode.layer.animateSpring(from: (selfTargetRect.width / expandedFrame.width) as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.04) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index c184fb9f99..03d3741765 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -61,7 +61,6 @@ final class ReactionNode: ASDisplayNode { self.staticAnimationNode.isHidden = true self.animateInAnimationNode = AnimatedStickerNode() - self.stillAnimationNode = AnimatedStickerNode() super.init() @@ -105,7 +104,7 @@ final class ReactionNode: ASDisplayNode { self.animateInAnimationNode?.visibility = true } - func updateLayout(size: CGSize, isExpanded: Bool, transition: ContainedViewLayoutTransition) { + func updateLayout(size: CGSize, isExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) { let intrinsicSize = size let animationSize = self.item.stillAnimation.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) @@ -186,27 +185,50 @@ final class ReactionNode: ASDisplayNode { self.validSize = size } + /*if isPreviewing { + if self.stillAnimationNode == nil { + let stillAnimationNode = AnimatedStickerNode() + self.stillAnimationNode = stillAnimationNode + self.addSubnode(stillAnimationNode) + + stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id))) + stillAnimationNode.position = animationFrame.center + stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) + stillAnimationNode.updateLayout(size: animationFrame.size) + stillAnimationNode.started = { [weak self] in + guard let strongSelf = self else { + return + } + let _ = strongSelf + } + stillAnimationNode.visibility = true + } else { + if let stillAnimationNode = self.stillAnimationNode { + transition.updatePosition(node: stillAnimationNode, position: animationFrame.center, beginWithCurrentState: true) + transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true) + } + } + } else if let stillAnimationNode = self.stillAnimationNode { + self.stillAnimationNode = nil + stillAnimationNode.removeFromSupernode() + }*/ + if !self.didSetupStillAnimation { if self.animationNode == nil { self.didSetupStillAnimation = true - self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.5), height: Int(animationDisplaySize.height * 2.5), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id))) + self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id))) self.staticAnimationNode.position = animationFrame.center self.staticAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) self.staticAnimationNode.updateLayout(size: animationFrame.size) self.staticAnimationNode.visibility = true if let animateInAnimationNode = self.animateInAnimationNode { - animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource), width: Int(animationDisplaySize.width * 2.5), height: Int(animationDisplaySize.height * 2.5), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation.resource.id))) + animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.appearAnimation.resource.id))) animateInAnimationNode.position = animationFrame.center animateInAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) animateInAnimationNode.updateLayout(size: animationFrame.size) } - - /*self.stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.5), height: Int(animationDisplaySize.height * 2.5), playbackMode: .once, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id))) - self.stillAnimationNode.position = animationFrame.center - self.stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) - self.stillAnimationNode.updateLayout(size: animationFrame.size)*/ } } else { transition.updatePosition(node: self.staticAnimationNode, position: animationFrame.center, beginWithCurrentState: true) @@ -216,11 +238,6 @@ final class ReactionNode: ASDisplayNode { transition.updatePosition(node: animateInAnimationNode, position: animationFrame.center, beginWithCurrentState: true) transition.updateTransformScale(node: animateInAnimationNode, scale: animationFrame.size.width / animateInAnimationNode.bounds.width, beginWithCurrentState: true) } - - if let stillAnimationNode = self.stillAnimationNode { - transition.updatePosition(node: stillAnimationNode, position: animationFrame.center, beginWithCurrentState: true) - transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true) - } } } } diff --git a/submodules/SettingsUI/Sources/Reactions/QuickReactionSetupController.swift b/submodules/SettingsUI/Sources/Reactions/QuickReactionSetupController.swift index 808f1d3101..caa56a2271 100644 --- a/submodules/SettingsUI/Sources/Reactions/QuickReactionSetupController.swift +++ b/submodules/SettingsUI/Sources/Reactions/QuickReactionSetupController.swift @@ -193,6 +193,10 @@ private func quickReactionSetupControllerEntries( entries.append(.itemsHeader("QUICK REACTION")) var index = 0 for availableReaction in availableReactions.reactions { + if !availableReaction.isEnabled { + continue + } + entries.append(.item( index: index, value: availableReaction.value, @@ -226,12 +230,8 @@ public func quickReactionSetupController( let arguments = QuickReactionSetupControllerArguments( context: context, - selectItem: { reaction in - let _ = updateReactionSettingsInteractively(postbox: context.account.postbox, { settings in - var settings = settings - settings.quickReaction = reaction - return settings - }).start() + selectItem: { reaction in + let _ = context.engine.stickers.updateQuickReaction(reaction: reaction).start() } ) @@ -252,6 +252,10 @@ public func quickReactionSetupController( if let availableReactions = availableReactions { for availableReaction in availableReactions.reactions { + if !availableReaction.isEnabled { + continue + } + let signal: Signal<(String, UIImage?), NoError> = context.account.postbox.mediaBox.resourceData(availableReaction.staticIcon.resource) |> distinctUntilChanged(isEqual: { lhs, rhs in return lhs.complete == rhs.complete diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 77f5e6bf04..12f45f1055 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -480,6 +480,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1347021750] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantJoinByRequest($0) } dict[-886388890] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionToggleNoForwards($0) } dict[663693416] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionSendMessage($0) } + dict[-1661470870] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeAvailableReactions($0) } dict[-1271602504] = { return Api.auth.ExportedAuthorization.parse_exportedAuthorization($0) } dict[2103482845] = { return Api.SecurePlainData.parse_securePlainPhone($0) } dict[569137759] = { return Api.SecurePlainData.parse_securePlainEmail($0) } @@ -791,7 +792,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[594408994] = { return Api.EmojiKeyword.parse_emojiKeywordDeleted($0) } dict[-290921362] = { return Api.upload.CdnFile.parse_cdnFileReuploadNeeded($0) } dict[-1449145777] = { return Api.upload.CdnFile.parse_cdnFile($0) } - dict[1424116867] = { return Api.AvailableReaction.parse_availableReaction($0) } + dict[35486795] = { return Api.AvailableReaction.parse_availableReaction($0) } dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) } dict[-1826077446] = { return Api.MessageUserReaction.parse_messageUserReaction($0) } dict[1984755728] = { return Api.BotInlineMessage.parse_botInlineMessageMediaAuto($0) } diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index ca5bf77426..c767bf7ff6 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -11695,6 +11695,7 @@ public extension Api { case channelAdminLogEventActionParticipantJoinByRequest(invite: Api.ExportedChatInvite, approvedBy: Int64) case channelAdminLogEventActionToggleNoForwards(newValue: Api.Bool) case channelAdminLogEventActionSendMessage(message: Api.Message) + case channelAdminLogEventActionChangeAvailableReactions(prevValue: [String], newValue: [String]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -11923,6 +11924,21 @@ public extension Api { } message.serialize(buffer, true) break + case .channelAdminLogEventActionChangeAvailableReactions(let prevValue, let newValue): + if boxed { + buffer.appendInt32(-1661470870) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(prevValue.count)) + for item in prevValue { + serializeString(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(newValue.count)) + for item in newValue { + serializeString(item, buffer: buffer, boxed: false) + } + break } } @@ -11998,6 +12014,8 @@ public extension Api { return ("channelAdminLogEventActionToggleNoForwards", [("newValue", newValue)]) case .channelAdminLogEventActionSendMessage(let message): return ("channelAdminLogEventActionSendMessage", [("message", message)]) + case .channelAdminLogEventActionChangeAvailableReactions(let prevValue, let newValue): + return ("channelAdminLogEventActionChangeAvailableReactions", [("prevValue", prevValue), ("newValue", newValue)]) } } @@ -12485,6 +12503,24 @@ public extension Api { return nil } } + public static func parse_channelAdminLogEventActionChangeAvailableReactions(_ reader: BufferReader) -> ChannelAdminLogEventAction? { + var _1: [String]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + var _2: [String]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1255641564, elementType: String.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeAvailableReactions(prevValue: _1!, newValue: _2!) + } + else { + return nil + } + } } public enum SecurePlainData: TypeConstructorDescription { @@ -20200,14 +20236,15 @@ public extension Api { } public enum AvailableReaction: TypeConstructorDescription { - case availableReaction(reaction: String, title: String, staticIcon: Api.Document, appearAnimation: Api.Document, selectAnimation: Api.Document, activateAnimation: Api.Document, effectAnimation: Api.Document) + case availableReaction(flags: Int32, reaction: String, title: String, staticIcon: Api.Document, appearAnimation: Api.Document, selectAnimation: Api.Document, activateAnimation: Api.Document, effectAnimation: Api.Document) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .availableReaction(let reaction, let title, let staticIcon, let appearAnimation, let selectAnimation, let activateAnimation, let effectAnimation): + case .availableReaction(let flags, let reaction, let title, let staticIcon, let appearAnimation, let selectAnimation, let activateAnimation, let effectAnimation): if boxed { - buffer.appendInt32(1424116867) + buffer.appendInt32(35486795) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeString(reaction, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) staticIcon.serialize(buffer, true) @@ -20221,20 +20258,18 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .availableReaction(let reaction, let title, let staticIcon, let appearAnimation, let selectAnimation, let activateAnimation, let effectAnimation): - return ("availableReaction", [("reaction", reaction), ("title", title), ("staticIcon", staticIcon), ("appearAnimation", appearAnimation), ("selectAnimation", selectAnimation), ("activateAnimation", activateAnimation), ("effectAnimation", effectAnimation)]) + case .availableReaction(let flags, let reaction, let title, let staticIcon, let appearAnimation, let selectAnimation, let activateAnimation, let effectAnimation): + return ("availableReaction", [("flags", flags), ("reaction", reaction), ("title", title), ("staticIcon", staticIcon), ("appearAnimation", appearAnimation), ("selectAnimation", selectAnimation), ("activateAnimation", activateAnimation), ("effectAnimation", effectAnimation)]) } } public static func parse_availableReaction(_ reader: BufferReader) -> AvailableReaction? { - var _1: String? - _1 = parseString(reader) + var _1: Int32? + _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: Api.Document? - if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Document - } + var _3: String? + _3 = parseString(reader) var _4: Api.Document? if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Document @@ -20251,6 +20286,10 @@ public extension Api { if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.Document } + var _8: Api.Document? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Document + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -20258,8 +20297,9 @@ public extension Api { let _c5 = _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.AvailableReaction.availableReaction(reaction: _1!, title: _2!, staticIcon: _3!, appearAnimation: _4!, selectAnimation: _5!, activateAnimation: _6!, effectAnimation: _7!) + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.AvailableReaction.availableReaction(flags: _1!, reaction: _2!, title: _3!, staticIcon: _4!, appearAnimation: _5!, selectAnimation: _6!, activateAnimation: _7!, effectAnimation: _8!) } else { return nil diff --git a/submodules/TelegramCore/Sources/Settings/ReactionSettings.swift b/submodules/TelegramCore/Sources/Settings/ReactionSettings.swift index 974e2c8a14..b395e3f43e 100644 --- a/submodules/TelegramCore/Sources/Settings/ReactionSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/ReactionSettings.swift @@ -11,13 +11,17 @@ public struct ReactionSettings: Equatable, Codable { } } +func updateReactionSettings(transaction: Transaction, _ f: (ReactionSettings) -> ReactionSettings) { + transaction.updatePreferencesEntry(key: PreferencesKeys.reactionSettings, { current in + let previous = current?.get(ReactionSettings.self) ?? ReactionSettings.default + let updated = f(previous) + return PreferencesEntry(updated) + }) +} + public func updateReactionSettingsInteractively(postbox: Postbox, _ f: @escaping (ReactionSettings) -> ReactionSettings) -> Signal { return postbox.transaction { transaction -> Void in - transaction.updatePreferencesEntry(key: PreferencesKeys.reactionSettings, { current in - let previous = current?.get(ReactionSettings.self) ?? ReactionSettings.default - let updated = f(previous) - return PreferencesEntry(updated) - }) + updateReactionSettings(transaction: transaction, f) } |> ignoreValues } diff --git a/submodules/TelegramCore/Sources/State/AvailableReactions.swift b/submodules/TelegramCore/Sources/State/AvailableReactions.swift index 56faa4d293..8ffd638bc8 100644 --- a/submodules/TelegramCore/Sources/State/AvailableReactions.swift +++ b/submodules/TelegramCore/Sources/State/AvailableReactions.swift @@ -6,6 +6,7 @@ import SwiftSignalKit public final class AvailableReactions: Equatable, Codable { public final class Reaction: Equatable, Codable { private enum CodingKeys: String, CodingKey { + case isEnabled case value case title case staticIcon @@ -15,6 +16,7 @@ public final class AvailableReactions: Equatable, Codable { case effectAnimation } + public let isEnabled: Bool public let value: String public let title: String public let staticIcon: TelegramMediaFile @@ -24,6 +26,7 @@ public final class AvailableReactions: Equatable, Codable { public let effectAnimation: TelegramMediaFile public init( + isEnabled: Bool, value: String, title: String, staticIcon: TelegramMediaFile, @@ -32,6 +35,7 @@ public final class AvailableReactions: Equatable, Codable { activateAnimation: TelegramMediaFile, effectAnimation: TelegramMediaFile ) { + self.isEnabled = isEnabled self.value = value self.title = title self.staticIcon = staticIcon @@ -42,6 +46,9 @@ public final class AvailableReactions: Equatable, Codable { } public static func ==(lhs: Reaction, rhs: Reaction) -> Bool { + if lhs.isEnabled != rhs.isEnabled { + return false + } if lhs.value != rhs.value { return false } @@ -69,6 +76,8 @@ public final class AvailableReactions: Equatable, Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) + self.isEnabled = try container.decode(Bool.self, forKey: .isEnabled) + self.value = try container.decode(String.self, forKey: .value) self.title = try container.decode(String.self, forKey: .title) @@ -91,6 +100,8 @@ public final class AvailableReactions: Equatable, Codable { 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.value, forKey: .value) try container.encode(self.title, forKey: .title) @@ -146,7 +157,7 @@ public final class AvailableReactions: Equatable, Codable { private extension AvailableReactions.Reaction { convenience init?(apiReaction: Api.AvailableReaction) { switch apiReaction { - case let .availableReaction(reaction, title, staticIcon, appearAnimation, selectAnimation, activateAnimation, effectAnimation): + case let .availableReaction(flags, reaction, title, staticIcon, appearAnimation, selectAnimation, activateAnimation, effectAnimation): guard let staticIconFile = telegramMediaFileFromApiDocument(staticIcon) else { return nil } @@ -162,7 +173,9 @@ private extension AvailableReactions.Reaction { guard let effectAnimationFile = telegramMediaFileFromApiDocument(effectAnimation) else { return nil } + let isEnabled = (flags & (1 << 0)) == 0 self.init( + isEnabled: isEnabled, value: reaction, title: title, staticIcon: staticIconFile, diff --git a/submodules/TelegramCore/Sources/State/ManagedAppConfigurationUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedAppConfigurationUpdates.swift index badf81f425..927d243675 100644 --- a/submodules/TelegramCore/Sources/State/ManagedAppConfigurationUpdates.swift +++ b/submodules/TelegramCore/Sources/State/ManagedAppConfigurationUpdates.swift @@ -17,6 +17,14 @@ func updateAppConfigurationOnce(postbox: Postbox, network: Network) -> Signal Void in if let data = JSON(apiJson: result) { + if let value = data["reactions_default"] as? String { + updateReactionSettings(transaction: transaction, { settings in + var settings = settings + settings.quickReaction = value + return settings + }) + } + updateAppConfiguration(transaction: transaction, { configuration -> AppConfiguration in var configuration = configuration configuration.data = data diff --git a/submodules/TelegramCore/Sources/State/MessageReactions.swift b/submodules/TelegramCore/Sources/State/MessageReactions.swift index 85b5c2f41f..090e510f98 100644 --- a/submodules/TelegramCore/Sources/State/MessageReactions.swift +++ b/submodules/TelegramCore/Sources/State/MessageReactions.swift @@ -499,3 +499,11 @@ func _internal_updatePeerAllowedReactions(account: Account, peerId: PeerId, allo } } } + +func _internal_updateDefaultReaction(account: Account, reaction: String) -> Signal { + return account.network.request(Api.functions.messages.setDefaultReaction(emoji: reaction)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> ignoreValues +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index c8b868d72c..c36a3771cc 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -67,6 +67,7 @@ public enum AdminLogEventAction { case participantJoinByRequest(invitation: ExportedInvitation, approvedBy: PeerId) case toggleCopyProtection(Bool) case sendMessage(Message) + case changeAvailableReactions(previousValue: [String], updatedValue: [String]) } public enum ChannelAdminLogEventError { @@ -262,6 +263,8 @@ func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: PeerId, m if let message = StoreMessage(apiMessage: message), let rendered = locallyRenderedMessage(message: message, peers: peers) { action = .sendMessage(rendered) } + case let .channelAdminLogEventActionChangeAvailableReactions(prevValue, newValue): + action = .changeAvailableReactions(previousValue: prevValue, updatedValue: newValue) } let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) if let action = action { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index ea1f4ea76a..b43bf94077 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -99,5 +99,14 @@ public extension TelegramEngine { public func availableReactions() -> Signal { return _internal_cachedAvailableReactions(postbox: self.account.postbox) } + + public func updateQuickReaction(reaction: String) -> Signal { + let _ = updateReactionSettingsInteractively(postbox: self.account.postbox, { settings in + var settings = settings + settings.quickReaction = reaction + return settings + }).start() + return _internal_updateDefaultReaction(account: self.account, reaction: reaction) + } } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 6cbb39072f..2405ede222 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1005,6 +1005,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if canAddMessageReactions(message: topMessage), let availableReactions = availableReactions, let allowedReactions = allowedReactions { filterReactions: for reaction in availableReactions.reactions { + if !reaction.isEnabled { + continue + } + switch allowedReactions { case let .set(set): if !set.contains(reaction.value) { diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index 8867084c92..cbd9604acf 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -1397,6 +1397,38 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) + case let .changeAvailableReactions(_, updatedValue): + var peers = SimpleDictionary() + var author: Peer? + if let peer = self.entry.peers[self.entry.event.peerId] { + author = peer + peers[peer.id] = peer + } + + var text: String = "" + var entities: [MessageTextEntity] = [] + + let rawText: PresentationStrings.FormattedString + if !updatedValue.isEmpty { + let emojiString = updatedValue.joined(separator: ", ") + rawText = self.presentationData.strings.Channel_AdminLog_AllowedReactionsUpdated(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "", emojiString) + } else { + rawText = self.presentationData.strings.Channel_AdminLog_ReactionsDisabled(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "") + } + + appendAttributedText(text: rawText, generateEntities: { index in + if index == 0, let author = author { + return [.TextMention(peerId: author.id)] + } else if index == 1 { + return [.Bold] + } + return [] + }, to: &text, entities: &entities) + + let action = TelegramMediaActionType.customText(text: text, entities: entities) + let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeTheme(_, updatedValue): diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift index 58cf4520ef..b42992ff22 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenDisclosureItem.swift @@ -193,7 +193,11 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode { self.activateArea.accessibilityValue = item.label.text transition.updateFrame(node: self.labelBadgeNode, frame: labelBadgeNodeFrame) - transition.updateFrame(node: self.labelNode, frame: labelFrame) + if self.labelNode.bounds.size != labelFrame.size { + self.labelNode.frame = labelFrame + } else { + transition.updateFrame(node: self.labelNode, frame: labelFrame) + } transition.updateFrame(node: self.textNode, frame: textFrame) let hasCorners = hasCorners && (topItem == nil || bottomItem == nil)