From 3bf092aff8131963bfe44ac54e687fbb05a1b96f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 2 Jan 2025 04:03:51 +0400 Subject: [PATCH] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 4 +- .../Sources/Node/ChatListItem.swift | 9 +- .../Sources/ContactsPeerItem.swift | 2 +- .../Sources/ItemListPeerItem.swift | 21 +- .../Sources/NumericFormat.swift | 6 +- .../ChatMessageGiftBubbleContentNode.swift | 22 +- .../ChatTitleView/Sources/ChatTitleView.swift | 16 +- .../Sources/GiftItemComponent.swift | 10 +- .../Sources/GiftOptionsScreen.swift | 14 + .../Sources/ChatGiftPreviewItem.swift | 2 +- .../Sources/GiftSetupScreen.swift | 31 +- .../Sources/GiftViewScreen.swift | 47 +- .../Sources/PeerInfoScreenAvatarSetup.swift | 443 +++++++++--------- .../Sources/PeerInfoGiftsPaneNode.swift | 4 +- .../Resources/Animations/GiftUpgrade.json | 2 +- .../TelegramUI/Sources/ChatController.swift | 6 +- .../Sources/ChatControllerNode.swift | 12 +- ...rollerOpenMessageReactionContextMenu.swift | 2 +- 18 files changed, 361 insertions(+), 292 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 93633ba923..e952f161f6 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13054,6 +13054,7 @@ Sorry for the inconvenience."; "Gift.Options.Gift.Text" = "Give **%@** gifts that can be kept on the profile or converted to Stars. [What are Stars >]()"; "Gift.Options.Gift.Filter.AllGifts" = "All Gifts"; "Gift.Options.Gift.Filter.Limited" = "Limited"; +"Gift.Options.Gift.Filter.InStock" = "In Stock"; "Gift.Options.Gift.Limited" = "limited"; "Gift.Options.Gift.SoldOut" = "sold out"; "Gift.Options.SoldOut.Text" = "Sorry, this gift is sold out."; @@ -13087,6 +13088,7 @@ Sorry for the inconvenience."; "Gift.Send.HideMyName" = "Hide My Name"; "Gift.Send.HideMyName.Info" = "Hide my name and message from visitors to %1$@'s profile. %2$@ will still see your name and message."; "Gift.Send.Send" = "Send a Gift for"; +"Gift.Send.Buy" = "Buy a Gift for"; "Gift.Send.Limited" = "Limited"; "Gift.Send.Remains_1" = "%@ left"; "Gift.Send.Remains_any" = "%@ left"; @@ -13616,7 +13618,7 @@ Sorry for the inconvenience."; "BotVerification.Verify.Channel.Text" = "Do you want to verify this channel with your verification mark and description?"; "BotVerification.Verify.Group.Title" = "Verify Group"; "BotVerification.Verify.Group.Text" = "Do you want to verify this group with your verification mark and description?"; -"BotVerification.Verify.User.Title" = "Verify Bot"; +"BotVerification.Verify.User.Title" = "Verify User"; "BotVerification.Verify.User.Text" = "Do you want to verify this user with your verification mark and description?"; "BotVerification.Verify.Bot.Title" = "Verify Bot"; "BotVerification.Verify.Bot.Text" = "Do you want to verify this bot with your verification mark and description?"; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 1255aaea6c..88eab2bab7 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3149,7 +3149,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var titleLeftOffset: CGFloat = 0.0 if let currentVerifiedIconContent { if titleLeftOffset.isZero, case .animation = currentVerifiedIconContent { - titleLeftOffset += 20.0 + titleLeftOffset += 19.0 } if titleIconsWidth.isZero { @@ -4590,7 +4590,12 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.credibilityIconComponent = credibilityIconComponent let iconOrigin: CGFloat = nextTitleIconOrigin - let containerSize = CGSize(width: 20.0, height: 20.0) + let containerSize: CGSize + if case .verified = currentCredibilityIconContent { + containerSize = CGSize(width: 16.0, height: 16.0) + } else { + containerSize = CGSize(width: 20.0, height: 20.0) + } let iconSize = credibilityIconView.update( transition: .immediate, component: AnyComponent(credibilityIconComponent), diff --git a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift index e67e462239..1bfc445cdc 100644 --- a/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift +++ b/submodules/ContactsPeerItem/Sources/ContactsPeerItem.swift @@ -794,7 +794,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode { var verifiedIcon: EmojiStatusComponent.Content? switch item.peer { case let .peer(peer, _): - if let peer = peer, (peer.id != item.context.account.peerId || item.peerMode == .memberList || item.aliasHandling == .treatSelfAsSaved) { + if let peer = peer, (peer.id != item.context.account.peerId || item.peerMode == .memberList || item.aliasHandling == .standard) { if peer.isScam { credibilityIcon = .text(color: item.presentationData.theme.chat.message.incoming.scamColor, string: item.presentationData.strings.Message_ScamAccount.uppercased()) } else if peer.isFake { diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index 2ad809fcf2..950ef4eaeb 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -1438,7 +1438,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo var titleLeftOffset: CGFloat = 0.0 var nextIconX: CGFloat = titleFrame.maxX - if let verifiedIcon = verifiedIcon { + if let verifiedIcon { let animationCache = item.context.animationCache let animationRenderer = item.context.animationRenderer @@ -1463,15 +1463,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo emojiFileUpdated: nil ) strongSelf.verifiedIconComponent = verifiedIconComponent - - let iconOrigin: CGFloat - if case .animation = verifiedIcon { - iconOrigin = titleFrame.minX - } else { - nextIconX += 4.0 - iconOrigin = nextIconX - } - + let iconSize = verifiedIconView.update( transition: .immediate, component: AnyComponent(verifiedIconComponent), @@ -1479,13 +1471,10 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo containerSize: CGSize(width: 20.0, height: 20.0) ) - transition.updateFrame(view: verifiedIconView, frame: CGRect(origin: CGPoint(x: iconOrigin, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) + transition.updateFrame(view: verifiedIconView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) - if case .animation = verifiedIcon { - titleLeftOffset += iconSize.width + 4.0 - } else { - nextIconX += iconSize.width - } + titleLeftOffset += iconSize.width + 4.0 + nextIconX += iconSize.width + 4.0 } else if let verifiedIconView = strongSelf.verifiedIconView { strongSelf.verifiedIconView = nil verifiedIconView.removeFromSuperview() diff --git a/submodules/TelegramPresentationData/Sources/NumericFormat.swift b/submodules/TelegramPresentationData/Sources/NumericFormat.swift index b3f03bea71..cc556add44 100644 --- a/submodules/TelegramPresentationData/Sources/NumericFormat.swift +++ b/submodules/TelegramPresentationData/Sources/NumericFormat.swift @@ -2,17 +2,17 @@ import Foundation import PresentationStrings import TelegramCore -public func compactNumericCountString(_ count: Int, decimalSeparator: String = ".") -> String { +public func compactNumericCountString(_ count: Int, decimalSeparator: String = ".", showDecimalPart: Bool = true) -> String { if count >= 1000 * 1000 { let remainder = (count % (1000 * 1000)) / (1000 * 100) - if remainder != 0 { + if remainder != 0 && showDecimalPart { return "\(count / (1000 * 1000))\(decimalSeparator)\(remainder)M" } else { return "\(count / (1000 * 1000))M" } } else if count >= 1000 { let remainder = (count % (1000)) / (100) - if remainder != 0 { + if remainder != 0 && showDecimalPart { return "\(count / 1000)\(decimalSeparator)\(remainder)K" } else { return "\(count / 1000)K" diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 90b5fd5d0d..d61054c8b4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -59,6 +59,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private var shimmerEffectNode: ShimmerEffectForegroundNode? private let buttonNode: HighlightTrackingButtonNode private let buttonStarsNode: PremiumStarsNode + private let buttonContentNode: ASDisplayNode private let buttonTitleNode: TextNode private var buttonIconNode: DefaultAnimatedStickerNodeImpl? @@ -162,8 +163,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.buttonStarsNode = PremiumStarsNode() + self.buttonContentNode = ASDisplayNode() + self.buttonContentNode.isUserInteractionEnabled = false + self.buttonTitleNode = TextNode() - self.buttonTitleNode.isUserInteractionEnabled = false self.buttonTitleNode.displaysAsynchronously = false self.ribbonBackgroundNode = ASImageNode() @@ -190,7 +193,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.buttonNode) self.buttonNode.addSubnode(self.buttonStarsNode) - self.buttonNode.addSubnode(self.buttonTitleNode) + self.buttonNode.addSubnode(self.buttonContentNode) + + self.buttonContentNode.addSubnode(self.buttonTitleNode) self.addSubnode(self.ribbonBackgroundNode) self.addSubnode(self.ribbonTextNode) @@ -546,8 +551,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { if case let .unique(uniqueGift) = gift { isStarGift = true let authorName: String - if isUpgrade && item.message.author?.id == item.context.account.peerId { - authorName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" + if isUpgrade { + if item.message.author?.id == item.context.account.peerId { + authorName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" + } else { + authorName = item.associatedData.accountPeer?.compactDisplayTitle ?? "" + } } else { authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" } @@ -747,7 +756,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let overlayColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) - let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 16.0 : 0.0), size: giftSize) + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 12.0 : 0.0), size: giftSize) let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0) var iconSize = CGSize(width: 160.0, height: 160.0) @@ -947,7 +956,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } else { buttonIconNode = DefaultAnimatedStickerNodeImpl() buttonIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: buttonIcon), width: 60, height: 60, playbackMode: .loop, mode: .direct(cachePathPrefix: nil)) - strongSelf.buttonNode.addSubnode(buttonIconNode) + strongSelf.buttonContentNode.addSubnode(buttonIconNode) strongSelf.buttonIconNode = buttonIconNode buttonIconNode.playLoop() } @@ -960,6 +969,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { animation.animator.updateFrame(layer: strongSelf.buttonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: buttonOriginY), size: buttonSize), completion: nil) strongSelf.buttonStarsNode.frame = CGRect(origin: .zero, size: buttonSize) + animation.animator.updateFrame(layer: strongSelf.buttonContentNode.layer, frame: CGRect(origin: .zero, size: buttonSize), completion: nil) if ribbonTextLayout.size.width > 0.0 { if strongSelf.ribbonBackgroundNode.image == nil { diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 00f6f152a7..9e4b73525b 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -950,13 +950,12 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { titleTransition = .immediate } - let iconSpacing: CGFloat = 2.0 let titleSideInset: CGFloat = 6.0 var titleFrame: CGRect if size.height > 40.0 { var titleInsets: UIEdgeInsets = .zero if case .emojiStatus = self.titleVerifiedIcon, verifiedIconWidth > 0.0 { - titleInsets.left = verifiedIconWidth + iconSpacing + titleInsets.left = verifiedIconWidth } var titleSize = self.titleTextNode.updateLayout(size: CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - verifiedIconWidth - statusIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height), insets: titleInsets, animated: titleTransition.isAnimated) @@ -998,18 +997,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var nextIconX: CGFloat = titleFrame.width - var verifiedIconX: CGFloat - if case .emojiStatus = self.titleVerifiedIcon { - verifiedIconX = 0.0 - } else { - verifiedIconX = nextIconX - titleVerifiedSize.width - } - - self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: verifiedIconX, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) - if case .emojiStatus = self.titleVerifiedIcon { - } else { - nextIconX -= titleVerifiedSize.width - } + self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) nextIconX -= titleCredibilitySize.width diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift index 95712eb17f..ddecf2cd1f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -220,7 +220,7 @@ public final class GiftItemComponent: Component { iconSize = CGSize(width: 88.0, height: 88.0) cornerRadius = 10.0 case .profile: - size = CGSize(width: availableSize.width, height: availableSize.width) + size = CGSize(width: availableSize.width, height: min(117 - UIScreenPixel, availableSize.width)) iconSize = CGSize(width: 88.0, height: 88.0) cornerRadius = 10.0 case .thumbnail: @@ -460,11 +460,17 @@ public final class GiftItemComponent: Component { } if let ribbon = component.ribbon { + let ribbonFontSize: CGFloat + if case .profile = component.mode { + ribbonFontSize = 9.0 + } else { + ribbonFontSize = 10.0 + } let ribbonTextSize = self.ribbonText.update( transition: transition, component: AnyComponent( MultilineTextComponent( - text: .plain(NSAttributedString(string: ribbon.text, font: Font.semibold(10.0), textColor: .white)), + text: .plain(NSAttributedString(string: ribbon.text, font: Font.semibold(ribbonFontSize), textColor: .white)), horizontalAlignment: .center ) ), diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 1dbdc4b095..aa61c9ecb8 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -79,6 +79,7 @@ final class GiftOptionsScreenComponent: Component { public enum StarsFilter: Equatable { case all case limited + case inStock case stars(Int64) init(rawValue: Int64) { @@ -87,6 +88,8 @@ final class GiftOptionsScreenComponent: Component { self = .all case -1: self = .limited + case -2: + self = .inStock default: self = .stars(rawValue) } @@ -98,6 +101,8 @@ final class GiftOptionsScreenComponent: Component { return 0 case .limited: return -1 + case .inStock: + return -2 case let .stars(stars): return stars } @@ -159,6 +164,10 @@ final class GiftOptionsScreenComponent: Component { if $0.availability != nil { return true } + case .inStock: + if $0.availability == nil || $0.availability!.remains > 0 { + return true + } case let .stars(stars): if $0.price == stars { return true @@ -901,6 +910,11 @@ final class GiftOptionsScreenComponent: Component { title: strings.Gift_Options_Gift_Filter_Limited )) } + + tabSelectorItems.append(TabSelectorComponent.Item( + id: AnyHashable(StarsFilter.inStock.rawValue), + title: strings.Gift_Options_Gift_Filter_InStock + )) let starsAmounts = Array(starsAmountsSet).sorted() for amount in starsAmounts { diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index fbb41da69f..bc063cf0f4 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -250,7 +250,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { let itemNode = messageNodes[i] items[i].updateNode(async: { $0() }, node: { return itemNode - }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None, completion: { (layout, apply) in + }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .System(duration: 0.2, transition: ControlledTransition(duration: 0.2, curve: .spring, interactive: false)), completion: { (layout, apply) in let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height)) itemNode.contentSize = layout.contentSize diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index b3db1ba090..e0c40a4896 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -417,10 +417,23 @@ final class GiftSetupScreenComponent: Component { options: options ?? [], purpose: .starGift(peerId: component.peerId, requiredStars: starGift.price), completion: { [weak starsContext] stars in - starsContext?.add(balance: StarsAmount(value: stars, nanos: 0)) - Queue.mainQueue().after(2.0) { - proceed() + guard let starsContext else { + return } + starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) + + let _ = (starsContext.state + |> take(until: { value in + if let value { + if !value.flags.contains(.isPendingBalance) { + return SignalTakeAction(passthrough: true, complete: true) + } + } + return SignalTakeAction(passthrough: false, complete: false) + }) + |> deliverOnMainQueue).start(next: { _ in + proceed() + }) } ) controller.push(purchaseController) @@ -464,8 +477,13 @@ final class GiftSetupScreenComponent: Component { } let peerName = self.peerMap[component.peerId]?.compactDisplayTitle ?? "" + let isSelfGift = component.peerId == component.context.account.peerId if self.component == nil { + if isSelfGift { + self.hideName = true + } + let _ = (component.context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: component.peerId), TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId) @@ -615,9 +633,7 @@ final class GiftSetupScreenComponent: Component { } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - - let isSelfGift = component.peerId == component.context.account.peerId - + let navigationTitleSize = self.navigationTitle.update( transition: transition, component: AnyComponent(MultilineTextComponent( @@ -1037,7 +1053,8 @@ final class GiftSetupScreenComponent: Component { finalPrice += upgradePrice } let amountString = presentationStringsFormattedNumber(Int32(finalPrice), presentationData.dateTimeFormat.groupingSeparator) - buttonString = "\(environment.strings.Gift_Send_Send) # \(amountString)" + let buttonTitle = isSelfGift ? environment.strings.Gift_Send_Buy : environment.strings.Gift_Send_Send + buttonString = "\(buttonTitle) # \(amountString)" if let availability = starGift.availability, availability.remains == 0 { buttonIsEnabled = false } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 4cc54532b3..d305f6ece8 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1108,21 +1108,27 @@ private final class GiftViewSheetContent: CombinedComponent { let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSenderWithText(senderName!, recipientName, dateString, "") : strings.Gift_Unique_OriginalInfoWithText(recipientName, dateString, "") let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor) string.replaceCharacters(in: format.ranges[format.ranges.count - 1].range, with: attributedText) - if let _ = senderName { - string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) - string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[1].range) + if let senderPeerId { + string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) + string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention), value: TelegramPeerMention(peerId: senderPeerId, mention: ""), range: format.ranges[0].range) + string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[1].range) + string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention), value: TelegramPeerMention(peerId: recipientPeerId, mention: ""), range: format.ranges[1].range) } else { - string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) + string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) + string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention), value: TelegramPeerMention(peerId: recipientPeerId, mention: ""), range: format.ranges[0].range) } value = string } else { let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSender(senderName!, recipientName, dateString) : strings.Gift_Unique_OriginalInfo(recipientName, dateString) let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor) - if let _ = senderName { - string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) - string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[1].range) + if let senderPeerId { + string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) + string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention), value: TelegramPeerMention(peerId: senderPeerId, mention: ""), range: format.ranges[0].range) + string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[1].range) + string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention), value: TelegramPeerMention(peerId: recipientPeerId, mention: ""), range: format.ranges[1].range) } else { - string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) + string.addAttribute(.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) + string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention), value: TelegramPeerMention(peerId: recipientPeerId, mention: ""), range: format.ranges[0].range) } value = string @@ -1146,7 +1152,26 @@ private final class GiftViewSheetContent: CombinedComponent { horizontalAlignment: .center, maximumNumberOfLines: 0, insets: id == "originalInfo" ? UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0) : .zero, - handleSpoilers: true + highlightColor: tableLinkColor.withAlphaComponent(0.1), + handleSpoilers: true, + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention) + } else { + return nil + } + }, + tapAction: { [weak state] attributes, _ in + guard let state else { + return + } + if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention, let peer = state.peerMap[mention.peerId] { + component.openPeer(peer) + Queue.mainQueue().after(1.0, { + component.cancel(false) + }) + } + } ) ) ) @@ -1178,11 +1203,13 @@ private final class GiftViewSheetContent: CombinedComponent { } } + let issuedString = presentationStringsFormattedNumber(uniqueGift.availability.issued, environment.dateTimeFormat.groupingSeparator) + let totalString = presentationStringsFormattedNumber(uniqueGift.availability.total, environment.dateTimeFormat.groupingSeparator) tableItems.insert(.init( id: "availability", title: strings.Gift_Unique_Availability, component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Unique_Issued("\(uniqueGift.availability.issued)/\(uniqueGift.availability.total)").string, font: tableFont, textColor: tableTextColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Unique_Issued("\(issuedString)/\(totalString)").string, font: tableFont, textColor: tableTextColor))) ) ), at: hasOriginalInfo ? tableItems.count - 1 : tableItems.count) } else { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift index 3b2b7ad43e..729d52e117 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift @@ -16,6 +16,7 @@ import UndoUI import PeerAvatarGalleryUI import PresentationDataUtils import LegacyComponents +import LegacyMediaPickerUI extension PeerInfoScreenImpl { // func newopenAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) { @@ -383,226 +384,226 @@ extension PeerInfoScreenImpl { let photoResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) -// let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false) -// -// var markup: UploadPeerPhotoMarkup? = nil -// if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 { -// if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 { -// markup = .sticker(packReference: .id(id: packId, accessHash: accessHash), fileId: fileId, backgroundColors: backgroundColors) -// } else { -// markup = .emoji(fileId: fileId, backgroundColors: backgroundColors) -// } -// } -// -// var uploadVideo = true -// if let _ = markup { -// if let data = self.context.currentAppConfiguration.with({ $0 }).data, let uploadVideoValue = data["upload_markup_video"] as? Bool, uploadVideoValue { -// uploadVideo = true -// } else { -// uploadVideo = false -// } -// } -// -// if [.suggest, .fallback].contains(mode) { -// } else { -// self.controllerNode.state = self.controllerNode.state.withUpdatingAvatar(.image(representation)) -// if !uploadVideo { -// self.controllerNode.state = self.controllerNode.state.withAvatarUploadProgress(.indefinite) -// } -// } -// -// if let (layout, navigationHeight) = self.controllerNode.validLayout { -// self.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: mode == .custom ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, additive: false) -// } -// self.controllerNode.headerNode.ignoreCollapse = false -// -// var videoStartTimestamp: Double? = nil -// if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { -// videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue -// } -// -// let account = self.context.account -// let context = self.context -// -// let videoResource: Signal -// if uploadVideo { -// videoResource = Signal { [weak self] subscriber in -// let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in -// if let paintingData = adjustments.paintingData, paintingData.hasAnimation { -// return LegacyPaintEntityRenderer(postbox: account.postbox, adjustments: adjustments) -// } else { -// return nil -// } -// } -// -// let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4") -// let uploadInterface = LegacyLiveUploadInterface(context: context) -// let signal: SSignal -// if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer { -// let durationSignal: SSignal = SSignal(generator: { subscriber in -// let disposable = (entityRenderer.duration()).start(next: { duration in -// subscriber.putNext(duration) -// subscriber.putCompletion() -// }) -// -// return SBlockDisposable(block: { -// disposable.dispose() -// }) -// }) -// signal = durationSignal.map(toSignal: { duration -> SSignal in -// if let duration = duration as? Double { -// return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)! -// } else { -// return SSignal.single(nil) -// } -// }) -// } else if let asset = asset as? AVAsset { -// signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, path: tempFile.path, watcher: uploadInterface, entityRenderer: entityRenderer)! -// } else { -// signal = SSignal.complete() -// } -// -// let signalDisposable = signal.start(next: { next in -// if let result = next as? TGMediaVideoConversionResult { -// if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) { -// account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) -// } -// -// if let timestamp = videoStartTimestamp { -// videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05)) -// } -// -// var value = stat() -// if stat(result.fileURL.path, &value) == 0 { -// if let data = try? Data(contentsOf: result.fileURL) { -// let resource: TelegramMediaResource -// if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult { -// resource = LocalFileMediaResource(fileId: liveUploadData.id) -// } else { -// resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) -// } -// account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) -// subscriber.putNext(resource) -// -// EngineTempBox.shared.dispose(tempFile) -// } -// } -// subscriber.putCompletion() -// } else if let strongSelf = self, let progress = next as? NSNumber { -// Queue.mainQueue().async { -// strongSelf.state = strongSelf.state.withAvatarUploadProgress(.value(CGFloat(progress.floatValue * 0.45))) -// if let (layout, navigationHeight) = strongSelf.validLayout { -// strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) -// } -// } -// } -// }, error: { _ in -// }, completed: nil) -// -// let disposable = ActionDisposable { -// signalDisposable?.dispose() -// } -// -// return ActionDisposable { -// disposable.dispose() -// } -// } -// } else { -// videoResource = .single(nil) -// } -// -// var dismissStatus: (() -> Void)? -// if [.suggest, .fallback, .accept].contains(mode) { -// let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { [weak self] in -// self?.controllerNode.updateAvatarDisposable.set(nil) -// dismissStatus?() -// })) -// dismissStatus = { [weak statusController] in -// statusController?.dismiss() -// } -// if let topController = self.navigationController?.topViewController as? ViewController { -// topController.presentInGlobalOverlay(statusController) -// } else if let topController = self.parentController?.topViewController as? ViewController { -// topController.presentInGlobalOverlay(statusController) -// } else { -// self.presentInGlobalOverlay(statusController) -// } -// } -// -// let peerId = self.peerId -// let isSettings = self.isSettings -// let isMyProfile = self.isMyProfile -// self.controllerNode.updateAvatarDisposable.set((videoResource -// |> mapToSignal { videoResource -> Signal in -// if isSettings || isMyProfile { -// if case .fallback = mode { -// return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } else { -// return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } -// } else if case .custom = mode { -// return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } else if case .suggest = mode { -// return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } else { -// return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in -// return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) -// }) -// } -// } -// |> deliverOnMainQueue).startStrict(next: { [weak self] result in -// guard let strongSelf = self else { -// return -// } -// switch result { -// case .complete: -// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil) -// case let .progress(value): -// strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(0.45 + value * 0.55))) -// } -// if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout { -// strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) -// } -// -// if case .complete = result { -// dismissStatus?() -// -// let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) -// |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in -// if let strongSelf = self, let peer { -// switch mode { -// case .fallback: -// (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) -// case .custom: -// strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) -// -// let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).startStandalone() -// case .suggest: -// if let navigationController = (strongSelf.navigationController as? NavigationController) { -// strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in -// })) -// } -// case .accept: -// (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccess, text: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccessText, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in -// if case .info = action { -// self?.parentController?.openSettings() -// } -// return false -// }), in: .current) -// default: -// break -// } -// } -// }) -// } -// })) + let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false) + + var markup: UploadPeerPhotoMarkup? = nil + if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 { + if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 { + markup = .sticker(packReference: .id(id: packId, accessHash: accessHash), fileId: fileId, backgroundColors: backgroundColors) + } else { + markup = .emoji(fileId: fileId, backgroundColors: backgroundColors) + } + } + + var uploadVideo = true + if let _ = markup { + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let uploadVideoValue = data["upload_markup_video"] as? Bool, uploadVideoValue { + uploadVideo = true + } else { + uploadVideo = false + } + } + + if [.suggest, .fallback].contains(mode) { + } else { + self.controllerNode.state = self.controllerNode.state.withUpdatingAvatar(.image(representation)) + if !uploadVideo { + self.controllerNode.state = self.controllerNode.state.withAvatarUploadProgress(.indefinite) + } + } + + if let (layout, navigationHeight) = self.controllerNode.validLayout { + self.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: mode == .custom ? .animated(duration: 0.2, curve: .easeInOut) : .immediate, additive: false) + } + self.controllerNode.headerNode.ignoreCollapse = false + + var videoStartTimestamp: Double? = nil + if let adjustments = adjustments, adjustments.videoStartValue > 0.0 { + videoStartTimestamp = adjustments.videoStartValue - adjustments.trimStartValue + } + + let account = self.context.account + let context = self.context + + let videoResource: Signal + if uploadVideo { + videoResource = Signal { [weak self] subscriber in + let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in + if let paintingData = adjustments.paintingData, paintingData.hasAnimation { + return LegacyPaintEntityRenderer(postbox: account.postbox, adjustments: adjustments) + } else { + return nil + } + } + + let tempFile = EngineTempBox.shared.tempFile(fileName: "video.mp4") + let uploadInterface = LegacyLiveUploadInterface(context: context) + let signal: SSignal + if let url = asset as? URL, url.absoluteString.hasSuffix(".jpg"), let data = try? Data(contentsOf: url, options: [.mappedRead]), let image = UIImage(data: data), let entityRenderer = entityRenderer { + let durationSignal: SSignal = SSignal(generator: { subscriber in + let disposable = (entityRenderer.duration()).start(next: { duration in + subscriber.putNext(duration) + subscriber.putCompletion() + }) + + return SBlockDisposable(block: { + disposable.dispose() + }) + }) + signal = durationSignal.map(toSignal: { duration -> SSignal in + if let duration = duration as? Double { + return TGMediaVideoConverter.renderUIImage(image, duration: duration, adjustments: adjustments, path: tempFile.path, watcher: nil, entityRenderer: entityRenderer)! + } else { + return SSignal.single(nil) + } + }) + } else if let asset = asset as? AVAsset { + signal = TGMediaVideoConverter.convert(asset, adjustments: adjustments, path: tempFile.path, watcher: uploadInterface, entityRenderer: entityRenderer)! + } else { + signal = SSignal.complete() + } + + let signalDisposable = signal.start(next: { next in + if let result = next as? TGMediaVideoConversionResult { + if let image = result.coverImage, let data = image.jpegData(compressionQuality: 0.7) { + account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) + } + + if let timestamp = videoStartTimestamp { + videoStartTimestamp = max(0.0, min(timestamp, result.duration - 0.05)) + } + + var value = stat() + if stat(result.fileURL.path, &value) == 0 { + if let data = try? Data(contentsOf: result.fileURL) { + let resource: TelegramMediaResource + if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult { + resource = LocalFileMediaResource(fileId: liveUploadData.id) + } else { + resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + } + account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + subscriber.putNext(resource) + + EngineTempBox.shared.dispose(tempFile) + } + } + subscriber.putCompletion() + } else if let strongSelf = self, let progress = next as? NSNumber { + Queue.mainQueue().async { + strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(progress.floatValue * 0.45))) + if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout { + strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + } + } + }, error: { _ in + }, completed: nil) + + let disposable = ActionDisposable { + signalDisposable?.dispose() + } + + return ActionDisposable { + disposable.dispose() + } + } + } else { + videoResource = .single(nil) + } + + var dismissStatus: (() -> Void)? + if [.suggest, .fallback, .accept].contains(mode) { + let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: { [weak self] in + self?.controllerNode.updateAvatarDisposable.set(nil) + dismissStatus?() + })) + dismissStatus = { [weak statusController] in + statusController?.dismiss() + } + if let topController = self.navigationController?.topViewController as? ViewController { + topController.presentInGlobalOverlay(statusController) + } else if let topController = self.parentController?.topViewController as? ViewController { + topController.presentInGlobalOverlay(statusController) + } else { + self.presentInGlobalOverlay(statusController) + } + } + + let peerId = self.peerId + let isSettings = self.isSettings + let isMyProfile = self.isMyProfile + self.controllerNode.updateAvatarDisposable.set((videoResource + |> mapToSignal { videoResource -> Signal in + if isSettings || isMyProfile { + if case .fallback = mode { + return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } else { + return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } + } else if case .custom = mode { + return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .custom, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } else if case .suggest = mode { + return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mode: .suggest, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } else { + return context.engine.peers.updatePeerPhoto(peerId: peerId, photo: context.engine.peers.uploadedPeerPhoto(resource: photoResource), video: videoResource.flatMap { context.engine.peers.uploadedPeerVideo(resource: $0) |> map(Optional.init) }, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in + return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) + }) + } + } + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let strongSelf = self else { + return + } + switch result { + case .complete: + strongSelf.controllerNode.state = strongSelf.controllerNode.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil) + case let .progress(value): + strongSelf.controllerNode.state = strongSelf.controllerNode.state.withAvatarUploadProgress(.value(CGFloat(0.45 + value * 0.55))) + } + if let (layout, navigationHeight) = strongSelf.controllerNode.validLayout { + strongSelf.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + + if case .complete = result { + dismissStatus?() + + let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in + if let strongSelf = self, let peer { + switch mode { + case .fallback: + (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + case .custom: + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + + let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).startStandalone() + case .suggest: + if let navigationController = (strongSelf.navigationController as? NavigationController) { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), keepStack: .default, completion: { _ in + })) + } + case .accept: + (strongSelf.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccess, text: strongSelf.presentationData.strings.Conversation_SuggestedVideoSuccessText, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] action in + if case .info = action { + self?.parentController?.openSettings() + } + return false + }), in: .current) + default: + break + } + } + }) + } + })) } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index a781386708..f4f3290cd6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -176,12 +176,12 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr switch product.gift { case let .generic(gift): if let availability = gift.availability { - ribbonText = params.presentationData.strings.PeerInfo_Gifts_OneOf(compactNumericCountString(Int(availability.total))).string + ribbonText = params.presentationData.strings.PeerInfo_Gifts_OneOf(compactNumericCountString(Int(availability.total), decimalSeparator: params.presentationData.dateTimeFormat.decimalSeparator)).string } else { ribbonText = nil } case let .unique(gift): - ribbonText = params.presentationData.strings.PeerInfo_Gifts_OneOf(compactNumericCountString(Int(gift.availability.total))).string + ribbonText = params.presentationData.strings.PeerInfo_Gifts_OneOf(compactNumericCountString(Int(gift.availability.total), decimalSeparator: params.presentationData.dateTimeFormat.decimalSeparator)).string for attribute in gift.attributes { if case let .backdrop(_, innerColor, outerColor, _, _, _) = attribute { ribbonColor = .custom(outerColor, innerColor) diff --git a/submodules/TelegramUI/Resources/Animations/GiftUpgrade.json b/submodules/TelegramUI/Resources/Animations/GiftUpgrade.json index 2ec9f56482..44aa6bc3bf 100644 --- a/submodules/TelegramUI/Resources/Animations/GiftUpgrade.json +++ b/submodules/TelegramUI/Resources/Animations/GiftUpgrade.json @@ -1 +1 @@ -{"v":"5.12.1","fr":60,"ip":0,"op":91,"w":90,"h":90,"nm":"upgrade","ddd":0,"assets":[{"id":"comp_0","nm":"arrows","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 203","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":45,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[32.25]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.66],"y":[0]},"t":20,"s":[27.25]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[34.25]},{"t":40,"s":[32.25]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,1.75],[0,-1.75],[-4,1.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 203","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector 202","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":45,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":5,"s":[44.25]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.66],"y":[0]},"t":15,"s":[39.25]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":25,"s":[46.25]},{"t":35,"s":[44.25]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,1.75],[0,-1.75],[-4,1.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 202","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Vector 201","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":45,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[56.25]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.66],"y":[0]},"t":10,"s":[51.25]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[58.25]},{"t":30,"s":[56.25]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,1.75],[0,-1.75],[-4,1.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 201","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"arrows","td":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45,45,0],"ix":2,"l":2},"a":{"a":0,"k":[45,45,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":90,"h":90,"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Rectangle 329","tt":2,"tp":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":45,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[45]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[40.5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[47.5]},{"t":40,"s":[45]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":0,"s":[20,20]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":20,"s":[19,22]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"t":30,"s":[21,19]},{"t":40,"s":[20,20]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":30,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 329","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}} \ No newline at end of file +{"v":"5.12.1","fr":60,"ip":0,"op":30,"w":90,"h":90,"nm":"upgrade 2","ddd":0,"assets":[{"id":"comp_0","nm":"arrows 2","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Vector 209","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":45,"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[15]},{"t":30,"s":[0.75]}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Vector 208","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":6,"s":[100]},{"t":26,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":14.5,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[100,100,100]},{"t":26,"s":[50,50,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,1.75],[0,-1.75],[-4,1.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 202","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Vector 202","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":43.5,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,1.75],[0,-1.75],[-4,1.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 202","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Vector 207","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":29,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,1.75],[0,-1.75],[-4,1.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 201","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Vector 204","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":87,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,1.75],[0,-1.75],[-4,1.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 201","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Vector 201","parent":1,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":6,"s":[0]},{"t":26,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":0,"ix":3},"y":{"a":0,"k":58,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":6,"s":[30,30,100]},{"t":26,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4,1.75],[0,-1.75],[-4,1.75]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 201","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"arrows 2","td":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[45,45,0],"ix":2,"l":2},"a":{"a":0,"k":[45,45,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":90,"h":90,"ip":0,"op":31,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Rectangle 329","tt":2,"tp":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":0,"k":45,"ix":3},"y":{"a":0,"k":45,"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[20,20],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":30,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 329","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"ct":1,"bm":0}],"markers":[],"props":{}} \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index d826577770..4d7ab0dd13 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3206,7 +3206,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.selectPollOptionFeedback?.success() - strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected() + strongSelf.chatDisplayNode.playConfettiAnimation() } else { var found = false strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in @@ -3608,7 +3608,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, displayDiceTooltip: { [weak self] dice in self?.displayDiceTooltip(dice: dice) }, animateDiceSuccess: { [weak self] haptic, confetti in - guard let strongSelf = self else { + guard let strongSelf = self, strongSelf.isNodeLoaded else { return } if strongSelf.selectPollOptionFeedback == nil { @@ -3618,7 +3618,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.selectPollOptionFeedback?.success() } if confetti { - strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected() + strongSelf.chatDisplayNode.playConfettiAnimation() } }, displayPremiumStickerTooltip: { [weak self] file, message in self?.displayPremiumStickerTooltip(file: file, message: message) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 531a03d4aa..a2cdfdb1c9 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -4297,7 +4297,17 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } } - func animateQuizCorrectOptionSelected() { + private var previousConfettiAnimationTimestamp: Double? + func playConfettiAnimation() { + guard self.view.bounds.width > 0.0 else { + return + } + let currentTime = CACurrentMediaTime() + if let previousConfettiAnimationTimestamp = self.previousConfettiAnimationTimestamp, abs(currentTime - previousConfettiAnimationTimestamp) < 0.1 { + return + } + self.previousConfettiAnimationTimestamp = currentTime + self.view.insertSubview(ConfettiView(frame: self.view.bounds), aboveSubview: self.historyNode.view) } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index ee04acb0df..3bf0cf4150 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -471,7 +471,7 @@ extension ChatControllerImpl { } if isBecomingTop { - self.chatDisplayNode.animateQuizCorrectOptionSelected() + self.chatDisplayNode.playConfettiAnimation() } if let itemNode, let targetView = itemNode.targetReactionView(value: .stars), self.context.sharedContext.energyUsageSettings.fullTranslucency {