diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift index c66dec6529..35b4c1f46d 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutControllerNode.swift @@ -1524,7 +1524,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz } switch result { - case let .done(receiptMessageId): + case let .done(receiptMessageId, _): proceedWithCompletion(true, receiptMessageId) case let .externalVerificationRequired(url): strongSelf.updateActionButton() diff --git a/submodules/ComponentFlow/Source/Base/Transition.swift b/submodules/ComponentFlow/Source/Base/Transition.swift index 227a314ad1..aa595927fe 100644 --- a/submodules/ComponentFlow/Source/Base/Transition.swift +++ b/submodules/ComponentFlow/Source/Base/Transition.swift @@ -345,7 +345,7 @@ public struct ComponentTransition { } } - public func setPosition(view: UIView, position: CGPoint, completion: ((Bool) -> Void)? = nil) { + public func setPosition(view: UIView, position: CGPoint, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) { if view.center == position { completion?(true) return @@ -364,7 +364,7 @@ public struct ComponentTransition { } view.center = position - self.animatePosition(view: view, from: previousPosition, to: view.center, completion: completion) + self.animatePosition(view: view, from: previousPosition, to: view.center, delay: delay, completion: completion) } } @@ -803,8 +803,8 @@ public struct ComponentTransition { } } - public func animatePosition(view: UIView, from fromValue: CGPoint, to toValue: CGPoint, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - self.animatePosition(layer: view.layer, from: fromValue, to: toValue, additive: additive, completion: completion) + public func animatePosition(view: UIView, from fromValue: CGPoint, to toValue: CGPoint, delay: Double = 0.0, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + self.animatePosition(layer: view.layer, from: fromValue, to: toValue, delay: delay, additive: additive, completion: completion) } public func animateBounds(view: UIView, from fromValue: CGRect, to toValue: CGRect, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { @@ -819,7 +819,7 @@ public struct ComponentTransition { self.animateBoundsSize(layer: view.layer, from: fromValue, to: toValue, additive: additive, completion: completion) } - public func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + public func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, delay: Double = 0.0, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { switch self.animation { case .none: completion?(true) @@ -829,7 +829,7 @@ public struct ComponentTransition { to: NSValue(cgPoint: toValue), keyPath: "position", duration: duration, - delay: 0.0, + delay: delay, curve: curve, removeOnCompletion: true, additive: additive, diff --git a/submodules/TelegramCore/Sources/State/MessageReactions.swift b/submodules/TelegramCore/Sources/State/MessageReactions.swift index 44613ed604..2ca1488b74 100644 --- a/submodules/TelegramCore/Sources/State/MessageReactions.swift +++ b/submodules/TelegramCore/Sources/State/MessageReactions.swift @@ -198,6 +198,28 @@ public func sendStarsReactionsInteractively(account: Account, messageId: Message |> ignoreValues } +func cancelPendingSendStarsReactionInteractively(account: Account, messageId: MessageId) -> Signal { + return account.postbox.transaction { transaction -> Void in + transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: nil) + transaction.updateMessage(messageId, update: { currentMessage in + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = currentMessage.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags) + } + var attributes = currentMessage.attributes + loop: for j in 0 ..< attributes.count { + if let _ = attributes[j] as? PendingStarsReactionsMessageAttribute { + attributes.remove(at: j) + break loop + } + } + + return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media)) + }) + } + |> ignoreValues +} + private enum RequestUpdateMessageReactionError { case generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index d70f325895..e4bf60422d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -337,6 +337,10 @@ public extension TelegramEngine { public func sendStarsReaction(id: EngineMessage.Id, count: Int) { let _ = sendStarsReactionsInteractively(account: self.account, messageId: id, count: count).startStandalone() } + + public func cancelPendingSendStarsReaction(id: EngineMessage.Id) { + let _ = cancelPendingSendStarsReactionInteractively(account: self.account, messageId: id).startStandalone() + } public func requestChatContextResults(botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal { return _internal_requestChatContextResults(account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults) diff --git a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift index 5c3a6f0546..bb26607690 100644 --- a/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift +++ b/submodules/TelegramUI/Components/AnimatedTextComponent/Sources/AnimatedTextComponent.swift @@ -76,6 +76,8 @@ public final class AnimatedTextComponent: Component { let delayNorm: CGFloat = 0.002 + var firstDelayWidth: CGFloat? + var validKeys: [CharacterKey] = [] for item in component.items { var itemText: [String] = [] @@ -138,20 +140,32 @@ public final class AnimatedTextComponent: Component { if characterTransition.animation.isImmediate { characterComponentView.frame = characterFrame } else { + var delayWidth: Double = 0.0 + if let firstDelayWidth { + delayWidth = size.width - firstDelayWidth + } else { + firstDelayWidth = size.width + } + characterComponentView.bounds = CGRect(origin: CGPoint(), size: characterFrame.size) let deltaPosition = CGPoint(x: characterFrame.midX - characterComponentView.frame.midX, y: characterFrame.midY - characterComponentView.frame.midY) characterComponentView.center = characterFrame.center - characterComponentView.layer.animatePosition(from: CGPoint(x: -deltaPosition.x, y: -deltaPosition.y), to: CGPoint(), duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + characterComponentView.layer.animatePosition(from: CGPoint(x: -deltaPosition.x, y: -deltaPosition.y), to: CGPoint(), duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring, additive: true) } } characterTransition.setFrame(view: characterComponentView, frame: characterFrame) - if animateIn, !transition.animation.isImmediate { - characterComponentView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring) - //characterComponentView.layer.animateSpring(from: (characterSize.height * 0.5) as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", duration: 0.5, additive: true) - characterComponentView.layer.animatePosition(from: CGPoint(x: 0.0, y: characterSize.height * 0.5), to: CGPoint(), duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - characterComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18, delay: delayNorm * size.width) + var delayWidth: Double = 0.0 + if let firstDelayWidth { + delayWidth = size.width - firstDelayWidth + } else { + firstDelayWidth = size.width + } + + characterComponentView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring) + characterComponentView.layer.animatePosition(from: CGPoint(x: 0.0, y: characterSize.height * 0.5), to: CGPoint(), duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + characterComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18, delay: delayNorm * delayWidth) } } @@ -160,6 +174,11 @@ public final class AnimatedTextComponent: Component { } } + let outScaleTransition: ComponentTransition = .spring(duration: 0.4) + let outAlphaTransition: ComponentTransition = .easeInOut(duration: 0.18) + + var outFirstDelayWidth: CGFloat? + var removedKeys: [CharacterKey] = [] for (key, characterView) in self.characters { if !validKeys.contains(key) { @@ -167,9 +186,16 @@ public final class AnimatedTextComponent: Component { if let characterComponentView = characterView.view { if !transition.animation.isImmediate { - characterComponentView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.4, delay: delayNorm * characterComponentView.frame.minX, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - characterComponentView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -characterComponentView.bounds.height * 0.4), duration: 0.4, delay: delayNorm * characterComponentView.frame.minX, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) - characterComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, delay: delayNorm * characterComponentView.frame.minX, removeOnCompletion: false, completion: { [weak characterComponentView] _ in + var delayWidth: Double = 0.0 + if let outFirstDelayWidth { + delayWidth = characterComponentView.frame.minX - outFirstDelayWidth + } else { + outFirstDelayWidth = characterComponentView.frame.minX + } + + outScaleTransition.setScale(view: characterComponentView, scale: 0.01, delay: delayNorm * delayWidth) + outScaleTransition.setPosition(view: characterComponentView, position: CGPoint(x: characterComponentView.center.x, y: characterComponentView.center.y - characterComponentView.bounds.height * 0.4), delay: delayNorm * delayWidth) + outAlphaTransition.setAlpha(view: characterComponentView, alpha: 0.0, delay: delayNorm * delayWidth, completion: { [weak characterComponentView] _ in characterComponentView?.removeFromSuperview() }) } else { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift index c129b2d041..bbce431bf6 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenMessageContextMenu.swift @@ -374,6 +374,7 @@ extension ChatControllerImpl { } self.context.engine.messages.sendStarsReaction(id: message.id, count: 1) + self.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1) } else { let chosenReaction: MessageReaction.Reaction = chosenUpdatedReaction.reaction diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 85014d81f8..c371ebb090 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -613,6 +613,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var messageComposeController: MFMessageComposeViewController? + weak var currentSendStarsUndoController: UndoOverlayController? + var currentSendStarsUndoMessageId: EngineMessage.Id? + var currentSendStarsUndoCount: Int = 0 + public var alwaysShowSearchResultsAsList: Bool = false { didSet { self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) @@ -1709,23 +1713,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.context.engine.messages.sendStarsReaction(id: message.id, count: 1) - - if !"".isEmpty { - let _ = (strongSelf.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId]) - |> deliverOnMainQueue).start(next: { [weak strongSelf, weak itemNode] files in - guard let strongSelf, let file = files[MessageReaction.starsReactionId] else { - return - } - //TODO:localize - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .starsSent(context: strongSelf.context, file: file, amount: 1, title: "Star Sent", text: "Long tap on {star} to select custom quantity of stars."), elevatedLayout: false, action: { _ in - return false - }), in: .current) - - if let itemNode = itemNode, let targetView = itemNode.targetReactionView(value: chosenReaction) { - strongSelf.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: strongSelf.chatDisplayNode.view)) - } - }) - } + strongSelf.displayOrUpdateSendStarsUndo(messageId: message.id, count: 1) }) } else { var removedReaction: MessageReaction.Reaction? diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index b18d4fd139..1ad580542a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -19,6 +19,7 @@ import ChatSendStarsScreen import ChatMessageItemCommon import ChatMessageItemView import ReactionSelectionNode +import AnimatedTextComponent extension ChatControllerImpl { func presentTagPremiumPaywall() { @@ -261,24 +262,7 @@ extension ChatControllerImpl { let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount)) #endif - let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [MessageReaction.starsReactionId]) - |> deliverOnMainQueue).start(next: { [weak self] files in - guard let self, let file = files[MessageReaction.starsReactionId] else { - return - } - - //TODO:localize - let title: String - if amount == 1 { - title = "Star Sent" - } else { - title = "\(amount) Stars Sent" - } - - self.present(UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, file: file, amount: amount, title: title, text: nil), elevatedLayout: false, action: { _ in - return false - }), in: .current) - }) + self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount)) })) }) @@ -480,4 +464,50 @@ extension ChatControllerImpl { }) } } + + func displayOrUpdateSendStarsUndo(messageId: EngineMessage.Id, count: Int) { + if self.currentSendStarsUndoMessageId != messageId { + if let current = self.currentSendStarsUndoController { + self.currentSendStarsUndoController = nil + current.dismiss() + } + } + + if let _ = self.currentSendStarsUndoController { + self.currentSendStarsUndoCount += count + } else { + self.currentSendStarsUndoCount = count + } + + //TODO:localize + let title: String + if self.currentSendStarsUndoCount == 1 { + title = "Star sent!" + } else { + title = "Stars sent!" + } + + var textItems: [AnimatedTextComponent.Item] = [] + textItems.append(AnimatedTextComponent.Item(id: AnyHashable(0), isUnbreakable: true, content: .text("You have reacted with "))) + textItems.append(AnimatedTextComponent.Item(id: AnyHashable(1), content: .number(self.currentSendStarsUndoCount, minDigits: 1))) + textItems.append(AnimatedTextComponent.Item(id: AnyHashable(2), isUnbreakable: true, content: .text(self.currentSendStarsUndoCount == 1 ? " star." : " stars."))) + + self.currentSendStarsUndoMessageId = messageId + //TODO:localize + if let current = self.currentSendStarsUndoController { + current.content = .starsSent(context: self.context, title: title, text: textItems) + } else { + let controller = UndoOverlayController(presentationData: self.presentationData, content: .starsSent(context: self.context, title: title, text: textItems), elevatedLayout: false, position: .top, action: { [weak self] action in + guard let self else { + return false + } + if case .undo = action { + self.context.engine.messages.cancelPendingSendStarsReaction(id: messageId) + } + return false + }) + self.currentSendStarsUndoController = controller + self.present(controller, in: .current) + } + } } diff --git a/submodules/UndoUI/BUILD b/submodules/UndoUI/BUILD index ce70e46883..059920aa56 100644 --- a/submodules/UndoUI/BUILD +++ b/submodules/UndoUI/BUILD @@ -30,6 +30,9 @@ swift_library( "//submodules/AnimatedAvatarSetNode:AnimatedAvatarSetNode", "//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/Components/BundleIconComponent", + "//submodules/TelegramUI/Components/AnimatedTextComponent", + "//submodules/Components/ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index 1172c49219..3aa34a3405 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -5,6 +5,7 @@ import TelegramPresentationData import TelegramCore import AccountContext import ComponentFlow +import AnimatedTextComponent public enum UndoOverlayContent { case removedChat(title: String, text: String?) @@ -39,7 +40,7 @@ public enum UndoOverlayContent { case copy(text: String) case mediaSaved(text: String) case paymentSent(currencyValue: String, itemTitle: String) - case starsSent(context: AccountContext, file: TelegramMediaFile, amount: Int64, title: String, text: String?) + case starsSent(context: AccountContext, title: String, text: [AnimatedTextComponent.Item]) case inviteRequestSent(title: String, text: String) case image(image: UIImage, title: String?, text: String, round: Bool, undoText: String?) case notificationSoundAdded(title: String, text: String, action: (() -> Void)?) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index c20ed6ecee..75770ad4aa 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -20,6 +20,9 @@ import AnimatedAvatarSetNode import ComponentFlow import EmojiStatusComponent import TextNodeWithEntities +import BundleIconComponent +import AnimatedTextComponent +import ComponentDisplayAdapters final class UndoOverlayControllerNode: ViewControllerTracingNode { private let presentationData: PresentationData @@ -42,6 +45,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private var emojiStatus: ComponentView? private let titleNode: ImmediateTextNode private let textNode: ImmediateTextNodeWithEntities + private var textComponent: ComponentView? + private var animatedTextItems: [AnimatedTextComponent.Item]? private let buttonNode: HighlightTrackingButtonNode private let undoButtonTextNode: ImmediateTextNode private let undoButtonNode: HighlightTrackingButtonNode @@ -84,6 +89,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.timerTextNode.displaysAsynchronously = false self.titleNode = ImmediateTextNode() + self.titleNode.layer.anchorPoint = CGPoint() self.titleNode.displaysAsynchronously = false self.titleNode.maximumNumberOfLines = 0 @@ -380,32 +386,23 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.textNode.attributedText = string displayUndo = false self.originalRemainingSeconds = 5 - case let .starsSent(context, file, _, title, text): + case let .starsSent(_, title, textItems): self.avatarNode = nil self.iconNode = nil self.iconCheckNode = nil self.animationNode = nil + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + let imageBoundingSize = CGSize(width: 34.0, height: 34.0) let emojiStatus = ComponentView() self.emojiStatus = emojiStatus let _ = emojiStatus.update( transition: .immediate, - component: AnyComponent(EmojiStatusComponent( - context: context, - animationCache: context.animationCache, - animationRenderer: context.animationRenderer, - content: .animation( - content: .file(file: file), - size: imageBoundingSize, - placeholderColor: UIColor(white: 1.0, alpha: 0.1), - themeColor: .white, - loopMode: .count(1) - ), - isVisibleForAnimations: true, - useSharedAnimation: false, - action: nil + component: AnyComponent(BundleIconComponent( + name: "Premium/Stars/StarLarge", + tintColor: nil )), environment: {}, containerSize: imageBoundingSize @@ -413,34 +410,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.stickerImageSize = imageBoundingSize - if let text { - let formattedString = text - - let string = NSMutableAttributedString(attributedString: NSAttributedString(string: formattedString, font: Font.regular(14.0), textColor: .white)) - let starRange = (string.string as NSString).range(of: "{star}") - if starRange.location != NSNotFound { - string.replaceCharacters(in: starRange, with: "") - string.insert(NSAttributedString(string: ".", attributes: [ - .font: Font.regular(14.0), - ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: MessageReaction.starsReactionId, file: file, custom: nil) - ]), at: starRange.location) - } - - self.textNode.attributedText = string - self.textNode.arguments = TextNodeWithEntities.Arguments( - context: context, - cache: context.animationCache, - renderer: context.animationRenderer, - placeholderColor: UIColor(white: 1.0, alpha: 0.1), - attemptSynchronous: false - ) - self.textNode.visibility = true - } + self.animatedTextItems = textItems - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) - - displayUndo = false - self.originalRemainingSeconds = 3 + displayUndo = true + self.originalRemainingSeconds = 4.5 isUserInteractionEnabled = true case let .messagesUnpinned(title, text, undo, isHidden): self.avatarNode = nil @@ -1485,6 +1458,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { var undoTextColor = self.presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0) + var transition: ContainedViewLayoutTransition = .immediate + switch content { case let .info(title, text, _, _), let .universal(_, _, _, title, text, _, _): let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) @@ -1516,12 +1491,19 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) } self.textNode.attributedText = attributedText + case let .starsSent(_, title, textItems): + self.animatedTextItems = textItems + + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + + self.renewWithCurrentContent() + transition = .animated(duration: 0.1, curve: .easeInOut) default: break } if let validLayout = self.validLayout { - self.containerLayoutUpdated(layout: validLayout, transition: .immediate) + self.containerLayoutUpdated(layout: validLayout, transition: transition) } } @@ -1579,7 +1561,41 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude)) - let textSize = self.textNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude)) + + let maxTextSize = CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude) + + let textSize: CGSize + if let animatedTextItems = self.animatedTextItems { + let textComponent: ComponentView + if let current = self.textComponent { + textComponent = current + } else { + textComponent = ComponentView() + self.textComponent = textComponent + } + textSize = textComponent.update( + transition: ComponentTransition(transition), + component: AnyComponent(AnimatedTextComponent( + font: Font.regular(14.0), + color: .white, + items: animatedTextItems + )), + environment: {}, + containerSize: maxTextSize + ) + if let textComponentView = textComponent.view { + if textComponentView.superview == nil { + textComponentView.layer.anchorPoint = CGPoint() + self.panelWrapperNode.view.addSubview(textComponentView) + } + } + } else { + if let textComponentView = self.textComponent?.view { + self.textComponent = nil + textComponentView.removeFromSuperview() + } + textSize = self.textNode.updateLayout(maxTextSize) + } if !titleSize.width.isZero { contentHeight += titleSize.height + 1.0 @@ -1630,8 +1646,17 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { } let textContentOrigin = floor((contentHeight - textContentHeight) / 2.0) - transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize)) - transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize)) + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize) + transition.updatePosition(node: self.titleNode, position: titleFrame.origin) + self.titleNode.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) + + let textFrame = CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize) + if let textComponentView = self.textComponent?.view { + transition.updatePosition(layer: textComponentView.layer, position: textFrame.origin) + textComponentView.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + } else { + transition.updateFrame(node: self.textNode, frame: textFrame) + } if let iconNode = self.iconNode { let iconSize: CGSize