Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-09-06 17:30:58 +02:00
commit 5259ccb0c7
25 changed files with 560 additions and 174 deletions

View File

@ -7237,6 +7237,13 @@ Sorry for the inconvenience.";
"PeerInfo.AllowedReactions.AllowAllGroupInfo" = "Allow subscribers to react to group messages.";
"PeerInfo.AllowedReactions.AllowAllChannelInfo" = "Allow subscribers to react to channel posts.";
"PeerInfo.AllowedReactions.ReactionListHeader" = "AVAILABLE REACTIONS";
"PeerInfo.AllowedReactions.OptionAllReactions" = "All Reactions";
"PeerInfo.AllowedReactions.OptionSomeReactions" = "Some Reactions";
"PeerInfo.AllowedReactions.OptionNoReactions" = "No Reactions";
"PeerInfo.AllowedReactions.GroupOptionAllInfo" = "Members of this group can use any emoji as reactions to messages.";
"PeerInfo.AllowedReactions.GroupOptionSomeInfo" = "Members of the group can use only some allowed emoji as reactions to messages.";
"PeerInfo.AllowedReactions.GroupOptionNoInfo" = "Members of the group can't add any reactions to messages.";
"PeerInfo.Reactions" = "Reactions";
"PeerInfo.ReactionsDisabled" = "Disabled";
@ -7249,6 +7256,9 @@ Sorry for the inconvenience.";
"Settings.QuickReactionSetup.DemoMessageAuthor" = "Dino";
"Settings.QuickReactionSetup.DemoMessageText" = "I hope you're enjoying your day as much as I am.";
"Settings.QuickReactionSetup.ChooseQuickReaction" = "Choose Your Quick Reaction";
"Settings.QuickReactionSetup.ChooseQuickReactionInfo" = "You can set any emoji as your quick reaction.";
"Chat.ContextReactionCount_1" = "1 reaction";
"Chat.ContextReactionCount_any" = "%@ reactions";
"Chat.OutgoingContextReactionCount_1" = "1 reacted";
@ -8046,3 +8056,22 @@ Sorry for the inconvenience.";
"Premium.EmojiStatus" = "Emoji Status";
"Premium.EmojiStatusInfo" = "Add any of thousands emojis next to your name to display current activity.";
"PeerStatusSetup.NoTimerTitle" = "Long tap to set a timer";
"Chat.PremiumReactionToastTitle" = "Subscribe to **Telegram Premium** to unlock this reaction.";
"Chat.PremiumReactionToastAction" = "More";
"Chat.ClearReactionsAlertText" = "Do you want to clear your recent reaction emoji from suggestions?";
"Chat.ClearReactionsAlertAction" = "Clear Recent Emoji";
"EmojiStatusSetup.SetUntil" = "Set Until";
"EmojiStatusSetup.TimerOther" = "Other";
"Chat.ReactionSection.Popular" = "Popular";
"Chat.ReactionSection.Recent" = "Recently Used";
"PeerInfo.SetEmojiStatus" = "Set Emoji Status";
"PeerInfo.ChangeEmojiStatus" = "Change Emoji Status";
"PeerInfo.LabelAllReactions" = "All Reactions";

View File

@ -852,8 +852,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private func openStatusSetup(sourceView: UIView) {
self.emojiStatusSelectionController?.dismiss()
var selectedItems = Set<MediaId>()
//TODO:localize
var topStatusTitle = "Long tap to set a timer"
var topStatusTitle = self.presentationData.strings.PeerStatusSetup_NoTimerTitle
if let peerStatus = self.titleView.title.peerStatus, case let .emoji(emojiStatus) = peerStatus {
selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId))

View File

@ -568,9 +568,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
animateInAsReplacement = true
}
//TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: "Subscribe to **Telegram Premium** to unlock this reaction.", undoText: "More", customAction: { [weak controller] in
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: context, file: file, title: nil, text: presentationData.strings.Chat_PremiumReactionToastTitle, undoText: presentationData.strings.Chat_PremiumReactionToastAction, customAction: { [weak controller] in
controller?.premiumReactionsSelected?()
}), elevatedLayout: false, position: position, animateInAsReplacement: animateInAsReplacement, action: { _ in true })
strongSelf.currentUndoController = undoController

View File

@ -278,8 +278,7 @@ private func peerAllowedReactionListControllerEntries(
if let peer = peer, let availableReactions = availableReactions, let allowedReactions = state.updatedAllowedReactions, let mode = state.updatedMode {
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
//TODO:localize
entries.append(.allowSwitch(text: "Allow Reactions", value: mode != .empty))
entries.append(.allowSwitch(text: presentationData.strings.PeerInfo_AllowedReactions_AllowAllText, value: mode != .empty))
entries.append(.itemsHeader(presentationData.strings.PeerInfo_AllowedReactions_ReactionListHeader))
var index = 0
@ -291,32 +290,30 @@ private func peerAllowedReactionListControllerEntries(
index += 1
}
} else {
//TODO:localize
entries.append(.allowAllHeader("AVAILABLE REACTIONS"))
entries.append(.allowAllHeader(presentationData.strings.PeerInfo_AllowedReactions_ReactionListHeader))
//TODO:localize
entries.append(.allowAll(text: "All Reactions", isEnabled: mode == .all))
entries.append(.allowSome(text: "Some Reactions", isEnabled: mode == .some))
entries.append(.allowNone(text: "No Reactions", isEnabled: mode == .empty))
entries.append(.allowAll(text: presentationData.strings.PeerInfo_AllowedReactions_OptionAllReactions, isEnabled: mode == .all))
entries.append(.allowSome(text: presentationData.strings.PeerInfo_AllowedReactions_OptionSomeReactions, isEnabled: mode == .some))
entries.append(.allowNone(text: presentationData.strings.PeerInfo_AllowedReactions_OptionNoReactions, isEnabled: mode == .empty))
let allInfoText: String
if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
switch mode {
case .all:
allInfoText = "Subscribers of this channel can use any emoji as reactions to messages."
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionAllInfo
case .some:
allInfoText = "You can select emoji that will allow subscribers of your channel to react to messages."
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionSomeInfo
case .empty:
allInfoText = "Subscribers of the channel can't add any reactions to messages."
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionNoInfo
}
} else {
switch mode {
case .all:
allInfoText = "Members of this group can use any emoji as reactions to messages."
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionAllInfo
case .some:
allInfoText = "Members of the group can use only some allowed emoji as reactions to messages."
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionSomeInfo
case .empty:
allInfoText = "Members of the group can't add any reactions to messages."
allInfoText = presentationData.strings.PeerInfo_AllowedReactions_GroupOptionNoInfo
}
}

View File

@ -798,7 +798,7 @@ private final class DemoSheetContent: CombinedComponent {
context: component.context,
position: .top,
videoFile: configuration.videos["infinite_reactions"],
decoration: .badgeStars
decoration: .swirlStars
)),
title: strings.Premium_InfiniteReactions,
text: strings.Premium_InfiniteReactionsInfo,
@ -834,7 +834,7 @@ private final class DemoSheetContent: CombinedComponent {
context: component.context,
position: .top,
videoFile: configuration.videos["emoji_status"],
decoration: .swirlStars
decoration: .badgeStars
)),
title: strings.Premium_EmojiStatus,
text: strings.Premium_EmojiStatusInfo,

View File

@ -323,6 +323,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
let gradientColors: [UIColor] = [
UIColor(rgb: 0xF27C30),
UIColor(rgb: 0xE36850),
UIColor(rgb: 0xda5d63),
UIColor(rgb: 0xD15078),
UIColor(rgb: 0xC14998),
UIColor(rgb: 0xB24CB5),
@ -332,7 +333,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x5A6EEE),
UIColor(rgb: 0x548DFF),
UIColor(rgb: 0x54A3FF),
UIColor(rgb: 0x54A3FF)
UIColor(rgb: 0x54bdff)
]
i = 0

View File

@ -260,7 +260,7 @@ enum PremiumPerk: CaseIterable {
case .animatedEmoji:
return "Premium/Perk/Emoji"
case .emojiStatus:
return "Premium/Perk/Emoji"
return "Premium/Perk/Status"
}
}
}
@ -1261,6 +1261,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let gradientColors: [UIColor] = [
UIColor(rgb: 0xF27C30),
UIColor(rgb: 0xE36850),
UIColor(rgb: 0xda5d63),
UIColor(rgb: 0xD15078),
UIColor(rgb: 0xC14998),
UIColor(rgb: 0xB24CB5),
@ -1270,7 +1271,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x5A6EEE),
UIColor(rgb: 0x548DFF),
UIColor(rgb: 0x54A3FF),
UIColor(rgb: 0x54A3FF)
UIColor(rgb: 0x54bdff)
]
let accountContext = context.component.context

View File

@ -33,6 +33,7 @@ swift_library(
"//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters",
"//submodules/TextFormat:TextFormat",
"//submodules/GZip:GZip",
"//submodules/ShimmerEffect:ShimmerEffect",
],
visibility = [
"//visibility:public",

View File

@ -1145,9 +1145,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
var items: [ActionSheetItem] = []
let context = strongSelf.context
//TODO:localize
items.append(ActionSheetTextItem(title: "Do you want to clear your recent reaction emoji from suggestions?", parseMarkdown: true))
items.append(ActionSheetButtonItem(title: presentationData.strings.Emoji_ClearRecent, color: .destructive, action: { [weak actionSheet] in
items.append(ActionSheetTextItem(title: presentationData.strings.Chat_ClearReactionsAlertText, parseMarkdown: true))
items.append(ActionSheetButtonItem(title: presentationData.strings.Chat_ClearReactionsAlertAction, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
guard let strongSelf = self else {
return
@ -1180,6 +1179,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
externalBackground: EmojiPagerContentComponent.ExternalBackground(
effectContainerView: self.backgroundNode.vibrancyEffectView?.contentView
),
externalExpansionView: self.view,
useOpaqueTheme: false
)
}
@ -1423,13 +1423,16 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
let expandedFrame = CGRect(origin: CGPoint(x: selfTargetRect.midX - expandedSize.width / 2.0, y: selfTargetRect.midY - expandedSize.height / 2.0), size: expandedSize)
let effectFrame: CGRect
var effectFrame: CGRect
let incomingMessage: Bool = expandedFrame.midX < self.bounds.width / 2.0
if self.didTriggerExpandedReaction {
let expandFactor: CGFloat = 0.5
effectFrame = expandedFrame.insetBy(dx: -expandedFrame.width * expandFactor, dy: -expandedFrame.height * expandFactor).offsetBy(dx: incomingMessage ? (expandedFrame.width - 50.0) : (-expandedFrame.width + 50.0), dy: 0.0)
} else {
effectFrame = expandedFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
if itemNode.item.isCustom {
effectFrame = effectFrame.insetBy(dx: -expandedSize.width, dy: -expandedSize.height)
}
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
@ -1442,11 +1445,28 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
let additionalAnimationNode: DefaultAnimatedStickerNodeImpl?
var genericAnimationView: AnimationView?
let additionalAnimation: TelegramMediaFile?
var additionalAnimation: TelegramMediaFile?
if self.didTriggerExpandedReaction {
additionalAnimation = itemNode.item.largeApplicationAnimation
} else {
additionalAnimation = itemNode.item.applicationAnimation
if additionalAnimation == nil && itemNode.item.isCustom {
outer: for attribute in itemNode.item.stillAnimation.attributes {
if case let .CustomEmoji(_, alt, _) = attribute {
if let availableReactions = self.availableReactions {
for availableReaction in availableReactions.reactions {
if availableReaction.value == .builtin(alt) {
additionalAnimation = availableReaction.aroundAnimation
break outer
}
}
}
break
}
}
}
}
if let additionalAnimation = additionalAnimation {
@ -1676,6 +1696,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
recognizer.state = .cancelled
return
}
if !itemNode.isAnimationLoaded {
recognizer.state = .cancelled
return
}
self.highlightedReaction = itemNode.item.reaction
if #available(iOS 13.0, *) {
@ -1871,6 +1895,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
public func reaction(at point: CGPoint) -> ReactionContextItem? {
let itemNode = self.reactionItemNode(at: point)
if let itemNode = itemNode as? ReactionNode {
if !itemNode.isAnimationLoaded {
return nil
}
return .reaction(itemNode.item)
} else if let _ = itemNode as? PremiumReactionsNode {
return .premium

View File

@ -12,6 +12,7 @@ import StickerResources
import AccountContext
import AnimationCache
import MultiAnimationRenderer
import ShimmerEffect
private func generateBubbleImage(foreground: UIColor, diameter: CGFloat, shadowBlur: CGFloat) -> UIImage? {
return generateImage(CGSize(width: diameter + shadowBlur * 2.0, height: diameter + shadowBlur * 2.0), rotatedContext: { size, context in
@ -48,6 +49,7 @@ protocol ReactionItemNode: ASDisplayNode {
public final class ReactionNode: ASDisplayNode, ReactionItemNode {
let context: AccountContext
let theme: PresentationTheme
let item: ReactionItem
private let loopIdle: Bool
private let hasAppearAnimation: Bool
@ -57,6 +59,7 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
let selectionView: UIView
private var animateInAnimationNode: AnimatedStickerNode?
private var staticAnimationPlaceholderView: UIImageView?
private let staticAnimationNode: AnimatedStickerNode
private var stillAnimationNode: AnimatedStickerNode?
private var customContentsNode: ASDisplayNode?
@ -83,8 +86,13 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
return self.staticAnimationNode.currentFrameImage
}
var isAnimationLoaded: Bool {
return self.staticAnimationNode.currentFrameImage != nil
}
public init(context: AccountContext, theme: PresentationTheme, item: ReactionItem, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, loopIdle: Bool, hasAppearAnimation: Bool = true, useDirectRendering: Bool = false) {
self.context = context
self.theme = theme
self.item = item
self.loopIdle = loopIdle
self.hasAppearAnimation = hasAppearAnimation
@ -361,6 +369,33 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
if self.animationNode == nil {
self.didSetupStillAnimation = true
let staticFile: TelegramMediaFile
if !self.hasAppearAnimation {
staticFile = self.item.largeListAnimation
} else {
staticFile = self.item.stillAnimation
}
if self.staticAnimationPlaceholderView == nil, let immediateThumbnailData = staticFile.immediateThumbnailData {
let staticAnimationPlaceholderView = UIImageView()
self.view.addSubview(staticAnimationPlaceholderView)
self.staticAnimationPlaceholderView = staticAnimationPlaceholderView
if let image = generateStickerPlaceholderImage(data: immediateThumbnailData, size: animationDisplaySize, scale: min(2.0, UIScreenScale), imageSize: staticFile.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: self.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1)) {
staticAnimationPlaceholderView.image = image
}
}
self.staticAnimationNode.started = { [weak self] in
guard let strongSelf = self else {
return
}
if let staticAnimationPlaceholderView = strongSelf.staticAnimationPlaceholderView {
strongSelf.staticAnimationPlaceholderView = nil
staticAnimationPlaceholderView.removeFromSuperview()
}
}
self.staticAnimationNode.automaticallyLoadFirstFrame = true
if !self.hasAppearAnimation {
self.staticAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.largeListAnimation.resource, isVideo: self.item.largeListAnimation.isVideoEmoji || self.item.largeListAnimation.isVideoSticker || self.item.largeListAnimation.isStaticSticker || self.item.largeListAnimation.isStaticEmoji), width: Int(expandedAnimationFrame.width * 2.0), height: Int(expandedAnimationFrame.height * 2.0), playbackMode: .still(.start), mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.largeListAnimation.resource.id)))
@ -372,6 +407,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
self.staticAnimationNode.updateLayout(size: animationFrame.size)
self.staticAnimationNode.visibility = true
if let staticAnimationPlaceholderView = self.staticAnimationPlaceholderView {
staticAnimationPlaceholderView.center = animationFrame.center
staticAnimationPlaceholderView.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
}
if let animateInAnimationNode = self.animateInAnimationNode {
animateInAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.appearAnimation.resource, isVideo: self.item.appearAnimation.isVideoEmoji || self.item.appearAnimation.isVideoSticker || self.item.appearAnimation.isStaticSticker || self.item.appearAnimation.isStaticEmoji), 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
@ -383,6 +423,11 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
transition.updatePosition(node: self.staticAnimationNode, position: animationFrame.center, beginWithCurrentState: true)
transition.updateTransformScale(node: self.staticAnimationNode, scale: animationFrame.size.width / self.staticAnimationNode.bounds.width, beginWithCurrentState: true)
if let staticAnimationPlaceholderView = self.staticAnimationPlaceholderView {
transition.updatePosition(layer: staticAnimationPlaceholderView.layer, position: animationFrame.center)
transition.updateTransformScale(layer: staticAnimationPlaceholderView.layer, scale: animationFrame.size.width / self.staticAnimationNode.bounds.width)
}
if let animateInAnimationNode = self.animateInAnimationNode {
transition.updatePosition(node: animateInAnimationNode, position: animationFrame.center, beginWithCurrentState: true)
transition.updateTransformScale(node: animateInAnimationNode, scale: animationFrame.size.width / animateInAnimationNode.bounds.width, beginWithCurrentState: true)

View File

@ -189,11 +189,9 @@ private func quickReactionSetupControllerEntries(
))
entries.append(.demoDescription(presentationData.strings.Settings_QuickReactionSetup_DemoInfo))
//TODO:localize
entries.append(.quickReaction("Choose Your Quick Reaction", reactionSettings.quickReaction, availableReactions))
entries.append(.quickReaction(presentationData.strings.Settings_QuickReactionSetup_ChooseQuickReaction, reactionSettings.quickReaction, availableReactions))
//TODO:localize
entries.append(.quickReactionDescription("You can set any emoji as your quick reaction."))
entries.append(.quickReactionDescription(presentationData.strings.Settings_QuickReactionSetup_ChooseQuickReactionInfo))
}
return entries

View File

@ -66,12 +66,17 @@ public func mergedMessageReactionsAndPeers(accountPeer: EnginePeer?, message: Me
if message.id.peerId.namespace == Namespaces.Peer.CloudUser {
for reaction in attribute.reactions {
var selfCount: Int32 = 0
if reaction.isSelected {
selfCount += 1
if let accountPeer = accountPeer {
recentPeers.append((reaction.value, accountPeer))
}
} else if let peer = message.peers[message.id.peerId] {
recentPeers.append((reaction.value, EnginePeer(peer)))
}
if reaction.count > selfCount + 1 {
if let peer = message.peers[message.id.peerId] {
recentPeers.append((reaction.value, EnginePeer(peer)))
}
}
}
} else {

View File

@ -1629,6 +1629,15 @@ func extractEmojiFileIds(message: StoreMessage, fileIds: inout Set<Int64>) {
break
}
}
} else if let attribute = attribute as? ReactionsMessageAttribute {
for reaction in attribute.reactions {
switch reaction.value {
case let .custom(fileId):
fileIds.insert(fileId)
default:
break
}
}
}
}
}
@ -1648,10 +1657,22 @@ private func messagesFromOperations(state: AccountMutableState) -> [StoreMessage
return messages
}
private func reactionsFromState(_ state: AccountMutableState) -> [MessageReaction.Reaction] {
var result: [MessageReaction.Reaction] = []
for operation in state.operations {
if case let .UpdateMessageReactions(_, reactions, _) = operation {
for reaction in ReactionsMessageAttribute(apiReactions: reactions).reactions {
result.append(reaction.value)
}
}
}
return result
}
private func resolveAssociatedMessages(postbox: Postbox, network: Network, state: AccountMutableState) -> Signal<AccountMutableState, NoError> {
let missingMessageIds = state.referencedMessageIds.subtracting(state.storedMessages)
if missingMessageIds.isEmpty {
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), result: state)
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: state), reactions: reactionsFromState(state), result: state)
} else {
var missingPeers = false
let _ = missingPeers
@ -1713,7 +1734,7 @@ private func resolveAssociatedMessages(postbox: Postbox, network: Network, state
return updatedState
}
|> mapToSignal { updatedState -> Signal<AccountMutableState, NoError> in
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: updatedState), result: updatedState)
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: messagesFromOperations(state: updatedState), reactions: reactionsFromState(updatedState), result: updatedState)
}
}
}

View File

@ -390,7 +390,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
folderSummaries: folderSummaries,
peerGroupIds: peerGroupIds
)
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: storeMessages, result: result)
return resolveUnknownEmojiFiles(postbox: postbox, source: .network(network), messages: storeMessages, reactions: [], result: result)
}
}
}

View File

@ -43,13 +43,19 @@ enum FetchMessageHistoryHoleSource {
}
}
func resolveUnknownEmojiFiles<T>(postbox: Postbox, source: FetchMessageHistoryHoleSource, messages: [StoreMessage], result: T) -> Signal<T, NoError> {
func resolveUnknownEmojiFiles<T>(postbox: Postbox, source: FetchMessageHistoryHoleSource, messages: [StoreMessage], reactions: [MessageReaction.Reaction], result: T) -> Signal<T, NoError> {
var fileIds = Set<Int64>()
for message in messages {
extractEmojiFileIds(message: message, fileIds: &fileIds)
}
for reaction in reactions {
if case let .custom(fileId) = reaction {
fileIds.insert(fileId)
}
}
if fileIds.isEmpty {
return .single(result)
} else {
@ -111,7 +117,7 @@ private func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMe
referencedIds.subtract(transaction.filterStoredMessageIds(referencedIds))
if referencedIds.isEmpty {
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, result: Void())
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages, reactions: [], result: Void())
|> mapToSignal { _ -> Signal<T, NoError> in
return postbox.transaction { transaction -> T in
return f(transaction, [], [])
@ -174,7 +180,7 @@ private func withResolvedAssociatedMessages<T>(postbox: Postbox, source: FetchMe
}
}
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, result: Void())
return resolveUnknownEmojiFiles(postbox: postbox, source: source, messages: storeMessages + additionalMessages, reactions: [], result: Void())
|> mapToSignal { _ -> Signal<T, NoError> in
return postbox.transaction { transaction -> T in
return f(transaction, additionalPeers, additionalMessages)

View File

@ -1390,6 +1390,12 @@ private func findHigherResolutionFileForAdaptation(itemDirectoryPath: String, ba
public final class AnimationCacheImpl: AnimationCache {
private final class Impl {
private struct ItemKey: Hashable {
var id: String
var width: Int
var height: Int
}
private final class ItemContext {
let subscribers = Bag<(AnimationCacheItemResult) -> Void>()
let disposable = MetaDisposable()
@ -1406,7 +1412,7 @@ public final class AnimationCacheImpl: AnimationCache {
private let fetchQueues: [Queue]
private var nextFetchQueueIndex: Int = 0
private var itemContexts: [String: ItemContext] = [:]
private var itemContexts: [ItemKey: ItemContext] = [:]
init(queue: Queue, basePath: String, allocateTempFile: @escaping () -> String) {
self.queue = queue
@ -1437,14 +1443,15 @@ public final class AnimationCacheImpl: AnimationCache {
return EmptyDisposable
}
let key = ItemKey(id: sourceId, width: Int(size.width), height: Int(size.height))
let itemContext: ItemContext
var beginFetch = false
if let current = self.itemContexts[sourceId] {
if let current = self.itemContexts[key] {
itemContext = current
} else {
itemContext = ItemContext()
self.itemContexts[sourceId] = itemContext
self.itemContexts[key] = itemContext
beginFetch = true
}
@ -1459,11 +1466,11 @@ public final class AnimationCacheImpl: AnimationCache {
let allocateTempFile = self.allocateTempFile
guard let writer = AnimationCacheItemWriterImpl(queue: self.fetchQueues[fetchQueueIndex % self.fetchQueues.count], allocateTempFile: self.allocateTempFile, completion: { [weak self, weak itemContext] result in
queue.async {
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[sourceId] else {
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[key] else {
return
}
strongSelf.itemContexts.removeValue(forKey: sourceId)
strongSelf.itemContexts.removeValue(forKey: key)
guard let result = result else {
return
@ -1503,13 +1510,13 @@ public final class AnimationCacheImpl: AnimationCache {
return ActionDisposable { [weak self, weak itemContext] in
queue.async {
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[sourceId] else {
guard let strongSelf = self, let itemContext = itemContext, itemContext === strongSelf.itemContexts[key] else {
return
}
itemContext.subscribers.remove(index)
if itemContext.subscribers.isEmpty {
itemContext.disposable.dispose()
strongSelf.itemContexts.removeValue(forKey: sourceId)
strongSelf.itemContexts.removeValue(forKey: key)
}
}
}

View File

@ -437,10 +437,9 @@ private final class TimeSelectionControlComponent: Component {
let pickerSize = CGSize(width: availableSize.width, height: 216.0)
let pickerFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + pickerSpacing), size: pickerSize)
//TODO:localize
let titleSize = self.titleView.update(
transition: transition,
component: AnyComponent(Text(text: "Set Until", font: Font.semibold(17.0), color: component.theme.list.itemPrimaryTextColor)),
component: AnyComponent(Text(text: component.strings.EmojiStatusSetup_SetUntil, font: Font.semibold(17.0), color: component.theme.list.itemPrimaryTextColor)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
)
@ -473,11 +472,10 @@ private final class TimeSelectionControlComponent: Component {
transition.setFrame(view: leftButtonComponentView, frame: CGRect(origin: CGPoint(x: 16.0, y: floor((topPanelHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize))
}
//TODO:localize
let actionButtonSize = self.actionButtonView.update(
transition: transition,
component: AnyComponent(SolidRoundedButtonComponent(
title: "Set Until",
title: component.strings.EmojiStatusSetup_SetUntil,
icon: nil,
theme: SolidRoundedButtonComponent.Theme(theme: component.theme),
font: .bold,
@ -674,9 +672,8 @@ final class EmojiStatusPreviewScreenComponent: Component {
}
))))
}
//TODO:localize
menuItems.append(AnyComponentWithIdentity(id: "Other", component: AnyComponent(ContextMenuActionItem(
title: "Other",
title: component.strings.EmojiStatusSetup_TimerOther,
action: { [weak self] in
self?.toggleState()
return .clearHighlight

View File

@ -345,6 +345,7 @@ public final class EmojiStatusSelectionController: ViewController {
peekBehavior: nil,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
useOpaqueTheme: true
)

View File

@ -1581,6 +1581,7 @@ public final class EmojiPagerContentComponent: Component {
public let peekBehavior: EmojiContentPeekBehavior?
public let customLayout: CustomLayout?
public let externalBackground: ExternalBackground?
public let externalExpansionView: UIView?
public let useOpaqueTheme: Bool
public init(
@ -1599,6 +1600,7 @@ public final class EmojiPagerContentComponent: Component {
peekBehavior: EmojiContentPeekBehavior?,
customLayout: CustomLayout?,
externalBackground: ExternalBackground?,
externalExpansionView: UIView?,
useOpaqueTheme: Bool
) {
self.performItemAction = performItemAction
@ -1616,6 +1618,7 @@ public final class EmojiPagerContentComponent: Component {
self.peekBehavior = peekBehavior
self.customLayout = customLayout
self.externalBackground = externalBackground
self.externalExpansionView = externalExpansionView
self.useOpaqueTheme = useOpaqueTheme
}
}
@ -2201,6 +2204,9 @@ public final class EmojiPagerContentComponent: Component {
}
}
final class CloneItemLayer: SimpleLayer {
}
public final class ItemLayer: MultiAnimationRenderTarget {
public struct Key: Hashable {
var groupId: AnyHashable
@ -2244,6 +2250,22 @@ public final class EmojiPagerContentComponent: Component {
}
public private(set) var displayPlaceholder: Bool = false
public let onUpdateDisplayPlaceholder: (Bool, Double) -> Void
weak var cloneLayer: CloneItemLayer? {
didSet {
if let cloneLayer = self.cloneLayer {
cloneLayer.contents = self.contents
}
}
}
override public var contents: Any? {
didSet {
if let cloneLayer = self.cloneLayer {
cloneLayer.contents = self.contents
}
}
}
public init(
item: Item,
@ -3374,6 +3396,7 @@ public final class EmojiPagerContentComponent: Component {
private let longPressDuration: Double = 0.5
private var longPressItem: EmojiPagerContentComponent.View.ItemLayer.Key?
private var currentLongPressLayer: CloneItemLayer?
private var hapticFeedback: HapticFeedback?
private var continuousHaptic: AnyObject?
private var longPressTimer: SwiftSignalKit.Timer?
@ -3402,9 +3425,23 @@ public final class EmojiPagerContentComponent: Component {
self.hapticFeedback = HapticFeedback()
}
let _ = itemLayer
//let transition = Transition(animation: .curve(duration: longPressDuration, curve: .easeInOut))
//transition.setScale(layer: itemLayer, scale: 1.3)
if let externalExpansionView = self.component?.inputInteractionHolder.inputInteraction?.externalExpansionView {
if let currentLongPressLayer = self.currentLongPressLayer {
self.currentLongPressLayer = nil
currentLongPressLayer.removeFromSuperlayer()
}
let currentLongPressLayer = CloneItemLayer()
currentLongPressLayer.position = self.scrollView.layer.convert(itemLayer.position, to: externalExpansionView.layer)
currentLongPressLayer.bounds = itemLayer.convert(itemLayer.bounds, to: externalExpansionView.layer)
currentLongPressLayer.transform = itemLayer.transform
externalExpansionView.layer.addSublayer(currentLongPressLayer)
self.currentLongPressLayer = currentLongPressLayer
itemLayer.cloneLayer = currentLongPressLayer
itemLayer.isHidden = true
let transition = Transition(animation: .curve(duration: longPressDuration, curve: .easeInOut))
transition.setScale(layer: currentLongPressLayer, scale: 1.85)
}
self.longPressTimer?.invalidate()
self.longPressTimer = SwiftSignalKit.Timer(timeout: longPressDuration, repeat: false, completion: { [weak self] in
@ -3431,7 +3468,23 @@ public final class EmojiPagerContentComponent: Component {
if let itemLayer = self.visibleItemLayers[itemKey] {
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transition.setScale(layer: itemLayer, scale: 1.0)
if let currentLongPressLayer = self.currentLongPressLayer {
self.currentLongPressLayer = nil
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transition.setScale(layer: currentLongPressLayer, scale: 1.0, completion: { [weak itemLayer, weak currentLongPressLayer] _ in
itemLayer?.isHidden = false
currentLongPressLayer?.removeFromSuperlayer()
})
}
} else if let currentLongPressLayer = self.currentLongPressLayer {
self.currentLongPressLayer = nil
currentLongPressLayer.removeFromSuperlayer()
}
} else if let currentLongPressLayer = self.currentLongPressLayer {
self.currentLongPressLayer = nil
currentLongPressLayer.removeFromSuperlayer()
}
case .ended:
self.longPressTimer?.invalidate()
@ -3441,11 +3494,32 @@ public final class EmojiPagerContentComponent: Component {
self.longPressItem = nil
if let component = self.component, let itemLayer = self.visibleItemLayers[itemKey] {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, itemLayer.item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, true)
if let externalExpansionView = self.component?.inputInteractionHolder.inputInteraction?.externalExpansionView, let currentLongPressLayer = self.currentLongPressLayer {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, itemLayer.item, externalExpansionView, currentLongPressLayer.frame, currentLongPressLayer, true)
} else {
component.inputInteractionHolder.inputInteraction?.performItemAction(itemKey.groupId, itemLayer.item, self, self.scrollView.convert(itemLayer.frame, to: self), itemLayer, true)
}
} else {
if let itemLayer = self.visibleItemLayers[itemKey] {
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transition.setScale(layer: itemLayer, scale: 1.0)
if let currentLongPressLayer = self.currentLongPressLayer {
self.currentLongPressLayer = nil
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transition.setScale(layer: currentLongPressLayer, scale: 1.0, completion: { [weak itemLayer, weak currentLongPressLayer] _ in
itemLayer?.isHidden = false
currentLongPressLayer?.removeFromSuperlayer()
})
}
} else if let currentLongPressLayer = self.currentLongPressLayer {
self.currentLongPressLayer = nil
let transition = Transition(animation: .curve(duration: 0.3, curve: .spring))
transition.setScale(layer: currentLongPressLayer, scale: 1.0, completion: { [weak currentLongPressLayer] _ in
currentLongPressLayer?.removeFromSuperlayer()
})
}
}
}
@ -4911,7 +4985,6 @@ public final class EmojiPagerContentComponent: Component {
itemGroups[groupIndex].items.append(resultItem)
} else {
itemGroupIndexById[groupId] = itemGroups.count
//TODO:localize
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: topStatusTitle?.uppercased(), subtitle: nil, isPremiumLocked: false, isFeatured: false, collapsedLineCount: 5, isClearable: false, headerItem: nil, items: [resultItem]))
}
@ -5121,8 +5194,7 @@ public final class EmojiPagerContentComponent: Component {
maxRecentLineCount = 5
}
//TODO:localize
let popularTitle = hasRecent ? "Recently Used" : "Popular"
let popularTitle = hasRecent ? strings.Chat_ReactionSection_Recent : strings.Chat_ReactionSection_Popular
if let availableReactions = availableReactions {
for reactionItem in availableReactions.reactions {

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "status.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,98 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 4.320007 2.144306 cm
1.000000 1.000000 1.000000 scn
10.373089 2.859669 m
8.050100 4.488478 6.380870 6.558828 3.543569 9.138160 c
2.650327 9.950203 1.641354 10.812672 0.459726 11.727699 c
-0.718856 12.606317 0.605010 14.381629 1.780190 13.505348 c
4.284061 11.489586 l
6.457295 9.740021 l
6.992327 9.341235 7.617038 10.095894 7.164679 10.554753 c
4.466011 13.444919 l
1.629065 16.483160 l
0.624206 17.494774 2.149370 19.019176 3.158343 18.003448 c
5.809479 15.159142 l
8.512616 12.259024 l
8.970663 11.794324 9.717808 12.423758 9.318109 12.953712 c
6.570284 16.767281 l
3.620909 20.860554 l
2.785100 22.005569 4.518925 23.287643 5.364432 22.129528 c
8.200413 18.189003 l
10.914721 14.417480 l
11.343112 13.849086 12.225386 14.414383 11.897745 15.040059 c
10.236182 18.412340 l
8.358698 22.222813 l
7.680108 23.518496 9.637980 24.552961 10.320377 23.250320 c
11.154460 21.568140 11.866667 20.137379 12.507627 18.893036 c
13.583073 16.805317 14.458187 15.242372 15.372757 13.896872 c
13.810370 12.772676 13.260306 10.794797 13.271935 9.282023 c
13.275491 8.803411 13.960783 8.714442 14.085299 9.177669 c
14.549135 10.905348 15.014800 11.856633 16.730038 12.322754 c
17.129433 14.205773 17.395172 15.580981 17.659184 16.554203 c
17.974028 17.714911 19.216745 18.364452 20.352518 17.951246 c
20.763287 17.801796 20.978804 17.334660 20.833061 16.909876 c
19.358932 12.613934 19.128233 10.638695 19.061661 6.794147 c
18.995644 2.557825 13.450433 0.701872 10.373089 2.859669 c
h
f
n
Q
endstream
endobj
3 0 obj
1544
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001634 00000 n
0000001657 00000 n
0000001830 00000 n
0000001904 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1963
%%EOF

View File

@ -6718,6 +6718,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch result {
case let .result(messageId):
if let messageId = messageId {
strongSelf.chatDisplayNode.historyNode.suspendReadingReactions = true
strongSelf.navigateToMessage(from: nil, to: .id(messageId, nil), scrollPosition: .center(.top), completion: {
guard let strongSelf = self else {
return
@ -6743,34 +6744,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
guard let availableReactions = item.associatedData.availableReactions else {
return
}
var avatarPeers: [EnginePeer] = []
if item.message.id.peerId.namespace != Namespaces.Peer.CloudUser, let updatedReactionPeer = updatedReactionPeer {
avatarPeers.append(updatedReactionPeer)
}
guard let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) else {
return
}
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if reaction.value == updatedReaction {
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: strongSelf.chatDisplayNode.historyNode.takeGenericReactionEffect())
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
standaloneReactionAnimation.animateReactionSelection(
context: strongSelf.context,
theme: strongSelf.presentationData.theme,
animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
reaction: ReactionItem(
var reactionItem: ReactionItem?
switch updatedReaction {
case .builtin:
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if reaction.value == updatedReaction {
reactionItem = ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
@ -6779,28 +6774,60 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation,
isCustom: false
),
avatarPeers: avatarPeers,
playHaptic: true,
isLarge: updatedReactionIsLarge,
targetView: targetView,
addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
},
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
break
}
}
case let .custom(fileId):
if let itemFile = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
reactionItem = ReactionItem(
reaction: ReactionItem.Reaction(rawValue: updatedReaction),
appearAnimation: itemFile,
stillAnimation: itemFile,
listAnimation: itemFile,
largeListAnimation: itemFile,
applicationAnimation: nil,
largeApplicationAnimation: nil,
isCustom: true
)
break
}
}
guard let targetView = itemNode.targetReactionView(value: updatedReaction) else {
return
}
if let reactionItem = reactionItem {
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: strongSelf.chatDisplayNode.historyNode.takeGenericReactionEffect())
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
standaloneReactionAnimation.animateReactionSelection(
context: strongSelf.context,
theme: strongSelf.presentationData.theme,
animationCache: strongSelf.controllerInteraction!.presentationContext.animationCache,
reaction: reactionItem,
avatarPeers: avatarPeers,
playHaptic: true,
isLarge: updatedReactionIsLarge,
targetView: targetView,
addStandaloneReactionAnimation: { standaloneReactionAnimation in
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = strongSelf.chatDisplayNode.bounds
strongSelf.chatDisplayNode.addSubnode(standaloneReactionAnimation)
},
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
}
strongSelf.chatDisplayNode.historyNode.suspendReadingReactions = false
})
}
case .loading:
@ -17031,6 +17058,10 @@ enum AllowedReactions {
}
func peerMessageAllowedReactions(context: AccountContext, message: Message) -> Signal<AllowedReactions?, NoError> {
if message.containsSecretMedia {
return .single(AllowedReactions.set(Set()))
}
return combineLatest(
context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId),
@ -17045,20 +17076,26 @@ func peerMessageAllowedReactions(context: AccountContext, message: Message) -> S
return .set(Set(effectiveReactions.map(\.value)))
}
if case let .channel(channel) = peer, case .broadcast = channel.info {
if let availableReactions = availableReactions {
return .set(Set(availableReactions.reactions.map(\.value)))
} else {
return .set(Set())
}
}
switch allowedReactions {
case .unknown:
if case let .channel(channel) = peer, case .broadcast = channel.info {
if let availableReactions = availableReactions {
return .set(Set(availableReactions.reactions.map(\.value)))
} else {
return .set(Set())
}
}
return .all
case let .known(value):
switch value {
case .all:
if case let .channel(channel) = peer, case .broadcast = channel.info {
if let availableReactions = availableReactions {
return .set(Set(availableReactions.reactions.map(\.value)))
} else {
return .set(Set())
}
}
return .all
case let .limited(reactions):
return .set(Set(reactions))

View File

@ -1105,6 +1105,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
peekBehavior: nil,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
useOpaqueTheme: false
)
@ -1310,6 +1311,7 @@ final class ChatEntityKeyboardInputNode: ChatInputNode {
peekBehavior: stickerPeekBehavior,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
useOpaqueTheme: false
)
@ -2020,6 +2022,7 @@ final class EntityInputView: UIView, AttachmentTextInputPanelInputView, UIInputV
peekBehavior: nil,
customLayout: nil,
externalBackground: nil,
externalExpansionView: nil,
useOpaqueTheme: false
)

View File

@ -451,6 +451,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
let canReadHistory = Promise<Bool>()
private var canReadHistoryValue: Bool = false
private var canReadHistoryDisposable: Disposable?
var suspendReadingReactions: Bool = false {
didSet {
if self.suspendReadingReactions != oldValue {
if !self.suspendReadingReactions {
self.attemptReadingReactions()
}
}
}
}
private var messageIdsScheduledForMarkAsSeen = Set<MessageId>()
private var messageIdsWithReactionsScheduledForMarkAsSeen = Set<MessageId>()
@ -728,7 +738,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
guard let strongSelf = self else {
return
}
if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
if strongSelf.canReadHistoryValue && !strongSelf.suspendReadingReactions && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory {
strongSelf.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
} else {
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.formUnion(messageIds)
@ -1365,20 +1375,13 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
strongSelf.canReadHistoryValue = value
strongSelf.updateReadHistoryActions()
if strongSelf.canReadHistoryValue && !strongSelf.messageIdsScheduledForMarkAsSeen.isEmpty {
if strongSelf.canReadHistoryValue && !strongSelf.suspendReadingReactions && !strongSelf.messageIdsScheduledForMarkAsSeen.isEmpty {
let messageIds = strongSelf.messageIdsScheduledForMarkAsSeen
strongSelf.messageIdsScheduledForMarkAsSeen.removeAll()
context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds)
}
if strongSelf.canReadHistoryValue && !strongSelf.context.sharedContext.immediateExperimentalUISettings.skipReadHistory && !strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty {
let messageIds = strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen
let _ = strongSelf.displayUnseenReactionAnimations(messageIds: Array(messageIds))
strongSelf.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll()
context?.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
}
strongSelf.attemptReadingReactions()
}
}
})
@ -1598,6 +1601,17 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
self.genericReactionEffectDisposable?.dispose()
}
private func attemptReadingReactions() {
if self.canReadHistoryValue && !self.suspendReadingReactions && !self.context.sharedContext.immediateExperimentalUISettings.skipReadHistory && !self.messageIdsWithReactionsScheduledForMarkAsSeen.isEmpty {
let messageIds = self.messageIdsWithReactionsScheduledForMarkAsSeen
let _ = self.displayUnseenReactionAnimations(messageIds: Array(messageIds))
self.messageIdsWithReactionsScheduledForMarkAsSeen.removeAll()
self.context.account.viewTracker.updateMarkReactionsSeenForMessageIds(messageIds: messageIds)
}
}
func takeGenericReactionEffect() -> String? {
let result = self.genericReactionEffect
self.loadNextGenericReactionEffect(context: self.context)
@ -2754,32 +2768,20 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
visibleNewIncomingReactionMessageIds.append(item.content.firstMessage.id)
if let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) {
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if reaction.value == updatedReaction {
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.genericReactionEffect)
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
var avatarPeers: [EnginePeer] = []
if item.message.id.peerId.namespace != Namespaces.Peer.CloudUser, let updateReactionPeer = updateReactionPeer {
avatarPeers = [updateReactionPeer]
var reactionItem: ReactionItem?
switch updatedReaction {
case .builtin:
if let availableReactions = item.associatedData.availableReactions {
for reaction in availableReactions.reactions {
guard let centerAnimation = reaction.centerAnimation else {
continue
}
chatDisplayNode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = chatDisplayNode.bounds
standaloneReactionAnimation.animateReactionSelection(
context: self.context,
theme: item.presentationData.theme.theme,
animationCache: self.controllerInteraction.presentationContext.animationCache,
reaction: ReactionItem(
guard let aroundAnimation = reaction.aroundAnimation else {
continue
}
if reaction.value == updatedReaction {
reactionItem = ReactionItem(
reaction: ReactionItem.Reaction(rawValue: reaction.value),
appearAnimation: reaction.appearAnimation,
stillAnimation: reaction.selectAnimation,
@ -2788,25 +2790,59 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
applicationAnimation: aroundAnimation,
largeApplicationAnimation: reaction.effectAnimation,
isCustom: false
),
avatarPeers: avatarPeers,
playHaptic: true,
isLarge: updatedReactionIsLarge,
targetView: targetView,
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
guard let strongSelf = self, let chatDisplayNode = strongSelf.controllerInteraction.chatControllerNode() as? ChatControllerNode else {
return
}
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = chatDisplayNode.bounds
chatDisplayNode.addSubnode(standaloneReactionAnimation)
},
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
)
break
}
}
}
case let .custom(fileId):
if let itemFile = item.message.associatedMedia[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] as? TelegramMediaFile {
reactionItem = ReactionItem(
reaction: ReactionItem.Reaction(rawValue: updatedReaction),
appearAnimation: itemFile,
stillAnimation: itemFile,
listAnimation: itemFile,
largeListAnimation: itemFile,
applicationAnimation: nil,
largeApplicationAnimation: nil,
isCustom: true
)
}
}
if let reactionItem = reactionItem, let targetView = itemNode.targetReactionView(value: updatedReaction) {
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.genericReactionEffect)
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
var avatarPeers: [EnginePeer] = []
if item.message.id.peerId.namespace != Namespaces.Peer.CloudUser, let updateReactionPeer = updateReactionPeer {
avatarPeers = [updateReactionPeer]
}
chatDisplayNode.addSubnode(standaloneReactionAnimation)
standaloneReactionAnimation.frame = chatDisplayNode.bounds
standaloneReactionAnimation.animateReactionSelection(
context: self.context,
theme: item.presentationData.theme.theme,
animationCache: self.controllerInteraction.presentationContext.animationCache,
reaction: reactionItem,
avatarPeers: avatarPeers,
playHaptic: true,
isLarge: updatedReactionIsLarge,
targetView: targetView,
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
guard let strongSelf = self, let chatDisplayNode = strongSelf.controllerInteraction.chatControllerNode() as? ChatControllerNode else {
return
}
chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
standaloneReactionAnimation.frame = chatDisplayNode.bounds
chatDisplayNode.addSubnode(standaloneReactionAnimation)
},
completion: { [weak standaloneReactionAnimation] in
standaloneReactionAnimation?.removeFromSupernode()
}
)
}
}
return visibleNewIncomingReactionMessageIds

View File

@ -633,11 +633,9 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
if let peer = data.peer as? TelegramUser, peer.isPremium {
if peer.emojiStatus != nil {
hasEmojiStatus = true
//TODO:localize
setStatusTitle = "Change Emoji Status"
setStatusTitle = presentationData.strings.PeerInfo_ChangeEmojiStatus
} else {
//TODO:localize
setStatusTitle = "Set Emoji Status"
setStatusTitle = presentationData.strings.PeerInfo_SetEmojiStatus
}
displaySetStatus = true
} else {
@ -1301,8 +1299,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
if let cachedData = data.cachedData as? CachedChannelData, case let .known(allowedReactions) = cachedData.allowedReactions {
switch allowedReactions {
case .all:
//TODO:localize
label = "All Reactions"
label = presentationData.strings.PeerInfo_LabelAllReactions
case .empty:
label = presentationData.strings.PeerInfo_ReactionsDisabled
case let .limited(reactions):
@ -1467,8 +1464,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
if let cachedData = data.cachedData as? CachedChannelData, case let .known(allowedReactions) = cachedData.allowedReactions {
switch allowedReactions {
case .all:
//TODO:localize
label = "All Reactions"
label = presentationData.strings.PeerInfo_LabelAllReactions
case .empty:
label = presentationData.strings.PeerInfo_ReactionsDisabled
case let .limited(reactions):
@ -1493,8 +1489,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
if let cachedData = data.cachedData as? CachedChannelData, case let .known(allowedReactions) = cachedData.allowedReactions {
switch allowedReactions {
case .all:
//TODO:localize
label = "All Reactions"
label = presentationData.strings.PeerInfo_LabelAllReactions
case .empty:
label = presentationData.strings.PeerInfo_ReactionsDisabled
case let .limited(reactions):
@ -1607,8 +1602,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
if let cachedData = data.cachedData as? CachedGroupData, case let .known(allowedReactions) = cachedData.allowedReactions {
switch allowedReactions {
case .all:
//TODO:localize
label = "All Reactions"
label = presentationData.strings.PeerInfo_LabelAllReactions
case .empty:
label = presentationData.strings.PeerInfo_ReactionsDisabled
case let .limited(reactions):
@ -3119,7 +3113,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
}
//TODO:localize
let emojiStatusSelectionController = EmojiStatusSelectionController(
context: strongSelf.context,
mode: .statusSelection,