From cbe98a3fc6fed3956ee055781ee1b0d6421d1807 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 31 Jan 2026 07:08:06 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 10 +- ...stControllerLeaveChannelConfirmation.swift | 23 +- .../ChannelMembersSearchController.swift | 3 +- .../Sources/State/Serialization.swift | 2 +- .../AlertComponent/Sources/AlertContent.swift | 11 +- .../ChatMessageGiftBubbleContentNode.swift | 6 +- .../Components/Gifts/GiftCraftScreen/BUILD | 1 - .../Sources/DialIndicatorComponent.swift | 71 +- .../Sources/GiftCraftScreen.swift | 745 ++++++++++-------- .../Sources/SelectCraftGiftScreen.swift | 2 + .../Sources/GiftItemComponent.swift | 8 +- .../Sources/GiftStoreScreen.swift | 7 + .../Sources/GiftUpgradeVariantsScreen.swift | 3 +- .../DialColorMask.imageset}/Contents.json | 0 .../Craft/DialColorMask.imageset}/bg.pdf | Bin 15 files changed, 528 insertions(+), 364 deletions(-) rename submodules/TelegramUI/Images.xcassets/{Components/ColorMask.imageset => Premium/Craft/DialColorMask.imageset}/Contents.json (100%) rename submodules/TelegramUI/Images.xcassets/{Components/ColorMask.imageset => Premium/Craft/DialColorMask.imageset}/bg.pdf (100%) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a626c8e4fc..92c555190c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15672,7 +15672,7 @@ Error: %8$@"; "Appearance.SendWithCmdEnter" = "Send Messages with ⌘+Enter"; -"Notification.StarsGift.Crafted" = "You crafted a new unique collectible"; +"Notification.StarsGift.Crafted" = "You crafted a new gift"; "Gift.Craft.Title" = "Craft Gift"; "Gift.Craft.Description" = "Add up to **4 gifts** to craft new\n**$ %@**.\n\nIf crafting fails, all used gifts\nwill be lost."; @@ -15757,3 +15757,11 @@ Error: %8$@"; "Notification.GroupCreatorChangeApplied" = "%1$@ has transferred ownership of the group to %2$@."; "Notification.StarGift.Crafted.Title" = "Crafted Gift"; +"Notification.StarGift.Upgraded.Title" = "Upgraded Gift"; +"Notification.StarGift.Purchased.Title" = "Purchased Gift"; + +"LeaveGroup.Title" = "Leave %@"; +"LeaveGroup.Text" = "If you leave, **%1$@** will become the owner of **%2$@** in **1 week**."; +"LeaveGroup.AppointAnotherOwner" = "Appoint Another Owner"; +"LeaveGroup.Proceed" = "Leave Group"; +"AppointAnotherOwner.Title" = "Appoint Another Owner"; diff --git a/submodules/ChatListUI/Sources/ChatListControllerLeaveChannelConfirmation.swift b/submodules/ChatListUI/Sources/ChatListControllerLeaveChannelConfirmation.swift index 8175a859b8..239b73584f 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerLeaveChannelConfirmation.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerLeaveChannelConfirmation.swift @@ -51,17 +51,16 @@ extension ChatListControllerImpl { ) ) )) - //TODO:localize content.append(AnyComponentWithIdentity( id: "title", component: AnyComponent( - AlertTitleComponent(title: "Leave \(peer.compactDisplayTitle)") + AlertTitleComponent(title: self.presentationData.strings.LeaveGroup_Title(peer.compactDisplayTitle).string) ) )) content.append(AnyComponentWithIdentity( id: "text", component: AnyComponent( - AlertTextComponent(content: .plain("If you leave, **\(nextCreator.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder))** will become the owner of **\(peer.compactDisplayTitle)** in **1 week**.")) + AlertTextComponent(content: .plain(self.presentationData.strings.LeaveGroup_Text(nextCreator.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder), peer.compactDisplayTitle).string)) ) )) @@ -70,16 +69,18 @@ extension ChatListControllerImpl { configuration: .init(actionAlignment: .vertical), content: content, actions: [ - .init(title: "Appoint Another Owner", action: { [weak self] in + .init(title: self.presentationData.strings.LeaveGroup_AppointAnotherOwner, action: { [weak self] in guard let self else { return } - self.presentOwnershipTransfer(chatPeer: peer) + self.presentOwnershipTransfer(chatPeer: peer, leaveGroup: { + completion(true) + }) }), - .init(title: "Cancel", action: { + .init(title: self.presentationData.strings.Common_Cancel, action: { completion(false) }), - .init(title: "Leave Group", type: .destructive, action: { + .init(title: self.presentationData.strings.LeaveGroup_Proceed, type: .destructive, action: { completion(true) }) ] @@ -90,7 +91,7 @@ extension ChatListControllerImpl { } } - func presentOwnershipTransfer(chatPeer: EnginePeer) { + func presentOwnershipTransfer(chatPeer: EnginePeer, leaveGroup: @escaping () -> Void) { let presentController: (ViewController) -> Void = { [weak self] c in if let topController = self?.navigationController?.topViewController as? ViewController { topController.present(c, in: .window(.root)) @@ -121,7 +122,9 @@ extension ChatListControllerImpl { case .creator: return case let .member(_, _, adminInfo, _, _, _): - let _ = adminInfo + if adminInfo == nil { + let _ = self.context.engine.peers.updateChannelAdminRights(peerId: chatPeer.id, adminId: peer.id, rights: TelegramChatAdminRights(rights: .all), rank: nil).start() + } let _ = (self.context.engine.peers.checkOwnershipTranfserAvailability(memberId: peer.id) |> deliverOnMainQueue).start(error: { [weak self] error in guard let self, case let .user(user) = peer else { @@ -141,6 +144,8 @@ extension ChatListControllerImpl { }, completion: { _ in dismissController?() + + leaveGroup() } ) presentController(controller) diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift index 03f7048781..91230aad26 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersSearchController.swift @@ -53,8 +53,7 @@ public final class ChannelMembersSearchControllerImpl: ViewController, ChannelMe let title: String switch params.mode { case .ownershipTransfer: - //TODO:localize - title = "Appoint Another Owner" + title = self.presentationData.strings.AppointAnotherOwner_Title let titleView = CounterControllerTitleView(theme: self.presentationData.theme) titleView.title = CounterControllerTitle(title: title, counter: " ") self.navigationItem.titleView = titleView diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index dc7b329822..e1003fb62f 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 224 + return 222 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift index 8e68f3a1e6..4f2312a393 100644 --- a/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift +++ b/submodules/TelegramUI/Components/AlertComponent/Sources/AlertContent.swift @@ -65,6 +65,8 @@ public final class AlertTitleComponent: Component { let environment = environment[AlertComponentEnvironment.self] + let inset: CGFloat = -6.0 + let titleConstrainedSize = CGSize(width: availableSize.width - inset * 2.0, height: availableSize.height) let titleSize = self.title.update( transition: transition, component: AnyComponent(MultilineTextComponent( @@ -77,13 +79,13 @@ public final class AlertTitleComponent: Component { maximumNumberOfLines: 0 )), environment: {}, - containerSize: availableSize + containerSize: titleConstrainedSize ) let titleOriginX: CGFloat switch component.alignment { case .default: - titleOriginX = 0.0 + titleOriginX = inset case .center: titleOriginX = floorToScreenPixels((availableSize.width - titleSize.width) / 2.0) } @@ -280,7 +282,8 @@ public final class AlertTextComponent: Component { break } - let textConstrainedSize = CGSize(width: availableSize.width, height: availableSize.height) + let inset: CGFloat = -6.0 + let textConstrainedSize = CGSize(width: availableSize.width - inset * 2.0, height: availableSize.height) let textSize = self.text.update( transition: transition, @@ -314,7 +317,7 @@ public final class AlertTextComponent: Component { containerSize: textConstrainedSize ) - var textOffset = CGPoint() + var textOffset = CGPoint(x: inset, y: 0.0) if hasCenterAlignment { textOffset.x = floorToScreenPixels((availableSize.width - textSize.width) / 2.0) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index be144adffd..f1263d1588 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -581,7 +581,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let isChannelGift = item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel || channelPeerId != nil if isSelfGift { - title = item.presentationData.strings.Notification_StarGift_Self_Title + title = item.presentationData.strings.Notification_StarGift_Purchased_Title } else { if isPrepaidUpgrade && senderPeerId == channelPeerId { title = item.presentationData.strings.Gift_View_Unknown_Title @@ -714,8 +714,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } else if isSelfGift { if isCrafted { title = item.presentationData.strings.Notification_StarGift_Crafted_Title + } else if isUpgrade { + title = item.presentationData.strings.Notification_StarGift_Upgraded_Title } else { - title = item.presentationData.strings.Notification_StarGift_Self_Title + title = item.presentationData.strings.Notification_StarGift_Purchased_Title } } else if item.message.id.peerId.isTelegramNotifications { title = item.presentationData.strings.Notification_StarGift_TitleShort diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/BUILD index 7262f33a94..d0a949560d 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/BUILD @@ -46,7 +46,6 @@ swift_library( "//submodules/TooltipUI", "//submodules/TextFormat", "//submodules/TelegramUI/Components/Gifts/GiftStoreScreen", - "//submodules/TelegramUI/Components/SpaceWarpView", "//submodules/ConfettiEffect", "//submodules/TelegramNotices", "//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView", diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/DialIndicatorComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/DialIndicatorComponent.swift index 468c44773f..c581d4d104 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/DialIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/DialIndicatorComponent.swift @@ -34,7 +34,7 @@ final class DialIndicatorComponent: Component { value: Int, suffix: String, isVisible: Bool = true, - isFlipped: Bool = false, + isFlipped: Bool = false ) { self.content = content self.backgroundColor = backgroundColor @@ -224,3 +224,72 @@ final class DialIndicatorComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + + +final class ColorSwatchComponent: Component { + let innerColor: UIColor + let outerColor: UIColor + + public init( + innerColor: UIColor, + outerColor: UIColor + ) { + self.innerColor = innerColor + self.outerColor = outerColor + } + + public static func ==(lhs: ColorSwatchComponent, rhs: ColorSwatchComponent) -> Bool { + if lhs.innerColor != rhs.innerColor { + return false + } + if lhs.outerColor != rhs.outerColor { + return false + } + return true + } + + public final class View: UIImageView { + private var component: ColorSwatchComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ColorSwatchComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component + self.component = component + self.state = state + + if previousComponent?.innerColor != component.innerColor || previousComponent?.outerColor != component.outerColor { + self.image = generateImage(availableSize, contextGenerator: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + if let image = UIImage(bundleImageName: "Premium/Craft/DialColorMask"), let cgImage = image.cgImage { + context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage) + } + var locations: [CGFloat] = [1.0, 0.0] + let colors: [CGColor] = [component.innerColor.cgColor, component.outerColor.cgColor] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + }) + } + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift index d622e15465..2e4247c661 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift @@ -31,13 +31,9 @@ import LottieComponent import TooltipUI import TextFormat import GlassBackgroundComponent -import SpaceWarpView import ConfettiEffect import TelegramNotices -private let backdropButtonTag = GenericComponentViewTag() -private let symbolButtonTag = GenericComponentViewTag() - private final class CraftGiftPageContent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -145,9 +141,9 @@ private final class CraftGiftPageContent: Component { private var craftTable = ComponentView() - private var backdropDial = ComponentView() - private var symbolDial = ComponentView() - private var variantsButton = ComponentView() + private var attributeDials: [AnyHashable: ComponentView] = [:] + private var attributeDialTags: [AnyHashable: GenericComponentViewTag] = [:] + private var variantsButton: ComponentView? private var variantsButtonMeasure = ComponentView() private let craftingTitle = ComponentView() @@ -461,282 +457,8 @@ private final class CraftGiftPageContent: Component { containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) ) craftContentHeight += 291.0 - craftContentHeight += descriptionTextSize.height - craftContentHeight += 25.0 - var attributes: [ResaleGiftsContext.Attribute: StarGift.UniqueGift.Attribute] = [:] - var backdropAttributeCount: [ResaleGiftsContext.Attribute: Int32] = [:] - var patternAttributeCount: [ResaleGiftsContext.Attribute: Int32] = [:] - for gift in selectedGifts.values { - for attribute in gift.gift.attributes { - switch attribute { - case let .backdrop(_, id, _, _, _, _, _): - let attributeId: ResaleGiftsContext.Attribute = .backdrop(id) - attributes[attributeId] = attribute - if let count = backdropAttributeCount[attributeId] { - backdropAttributeCount[attributeId] = count + 1 - } else { - backdropAttributeCount[attributeId] = 1 - } - case let .pattern(_, file, _): - let attributeId: ResaleGiftsContext.Attribute = .pattern(file.fileId.id) - attributes[attributeId] = attribute - if let count = patternAttributeCount[attributeId] { - patternAttributeCount[attributeId] = count + 1 - } else { - patternAttributeCount[attributeId] = 1 - } - default: - break - } - } - } - - func mostFrequentAttribute(from counts: [ResaleGiftsContext.Attribute: Int32]) -> (attribute: StarGift.UniqueGift.Attribute, count: Int32)? { - guard let (id, count) = counts.max(by: { $0.value < $1.value }), - count > 1, - let attribute = attributes[id] else { - return nil - } - return (attribute, count) - } - - var possibleBackdrop: (StarGift.UniqueGift.Attribute, Int32)? - var possiblePattern: (StarGift.UniqueGift.Attribute, Int32)? - - var backdropColor: UIColor = .white - var backdropPermille: Int = 0 - var backdropName = "" - var symbolFile: TelegramMediaFile? - var symbolPermille: Int = 0 - var symbolName = "" - - for attribute in component.gift.attributes { - switch attribute { - case .backdrop: - possibleBackdrop = (attribute, 1) - case .pattern: - possiblePattern = (attribute, 1) - default: - break - } - } - - if let betterBackdrop = mostFrequentAttribute(from: backdropAttributeCount) { - possibleBackdrop = betterBackdrop - } - - if let betterPattern = mostFrequentAttribute(from: patternAttributeCount) { - possiblePattern = betterPattern - } - - let appConfiguration = component.context.currentAppConfiguration.with { $0 } - let giftCraftConfiguration = GiftCraftConfiguration.with(appConfiguration: appConfiguration) - - if case let .backdrop(name, _, innerColor, _, _, _, _) = possibleBackdrop?.0 { - backdropColor = UIColor(rgb: UInt32(bitPattern: innerColor)) - backdropName = name - } - if let possibleBackdrop { - backdropPermille = Int(giftCraftConfiguration.craftAttributePermilles[Int(possibleBackdrop.1 - 1)]) - } - - if case let .pattern(name, file, _) = possiblePattern?.0 { - symbolFile = file - symbolName = name - } - if let possiblePattern { - symbolPermille = Int(giftCraftConfiguration.craftAttributePermilles[Int(possiblePattern.1 - 1)]) - } - - let backdropDialSize = self.backdropDial.update( - transition: transition, - component: AnyComponent( - PlainButtonComponent( - content: AnyComponent( - DialIndicatorComponent( - content: AnyComponentWithIdentity( - id: "color", - component: AnyComponent( - BundleIconComponent(name: "Components/ColorMask", tintColor: backdropColor) - ) - ), - backgroundColor: .white.withAlphaComponent(0.1), - foregroundColor: .white, - diameter: 48.0, - lineWidth: 4.0, - fontSize: 10.0, - progress: CGFloat(backdropPermille) / 10.0 / 100.0, - value: backdropPermille / 10, - suffix: "%" - ) - ), - action: { [weak self] in - guard let self else { - return - } - #if DEBUG - self.component?.externalState.testFailOrSuccess = true - #endif - self.showAttributeInfo(tag: backdropButtonTag, text: environment.strings.Gift_Craft_BackdropTooltip("\(backdropPermille / 10)", backdropName).string) - }, - tag: backdropButtonTag - ) - ), - environment: {}, - containerSize: availableSize - ) - - let symbolDialSize = self.symbolDial.update( - transition: transition, - component: AnyComponent( - PlainButtonComponent( - content: AnyComponent( - DialIndicatorComponent( - content: symbolFile.flatMap { AnyComponentWithIdentity( - id: "symbol", - component: AnyComponent( - LottieComponent( - content: LottieComponent.ResourceContent( - context: component.context, - file: $0, - attemptSynchronously: true, - providesPlaceholder: true - ), - color: .white, - size: CGSize(width: 32.0, height: 32.0) - ) - ) - ) } ?? AnyComponentWithIdentity( - id: "empty", component: AnyComponent(Rectangle(color: .clear))), - backgroundColor: .white.withAlphaComponent(0.1), - foregroundColor: .white, - diameter: 48.0, - contentSize: CGSize(width: 28.0, height: 28.0), - lineWidth: 4.0, - fontSize: 10.0, - progress: CGFloat(symbolPermille) / 10.0 / 100.0, - value: symbolPermille / 10, - suffix: "%" - ) - ), - action: { [weak self] in - guard let self else { - return - } - #if DEBUG - self.component?.externalState.testFailOrSuccess = false - #endif - self.showAttributeInfo(tag: symbolButtonTag, text: environment.strings.Gift_Craft_SymbolTooltip("\(symbolPermille / 10)", symbolName).string) - }, - tag: symbolButtonTag - ) - ), - environment: {}, - containerSize: availableSize - ) - craftContentHeight += backdropDialSize.height - craftContentHeight += 15.0 - - let variantsString = environment.strings.Gift_Craft_ViewVariants - let variantsButtonMeasure = self.variantsButtonMeasure.update( - transition: .immediate, - component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: variantsString, font: Font.semibold(13.0), textColor: .clear)))), - environment: {}, - containerSize: availableSize - ) - - let variantsButtonSize = CGSize(width: variantsButtonMeasure.width + 87.0, height: 24.0) - if let gift = self.starGiftsMap[component.gift.giftId] { - var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "") - var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "") - var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "") - - if let upgradePreview = self.upgradePreview { - var i = 0 - for attribute in upgradePreview { - if case .model = attribute { - switch i { - case 0: - variant1 = .preview(attributes: [attribute], rarity: nil) - case 1: - variant2 = .preview(attributes: [attribute], rarity: nil) - case 2: - variant3 = .preview(attributes: [attribute], rarity: nil) - default: - break - } - i += 1 - } - } - } - - let _ = self.variantsButton.update( - transition: transition, - component: AnyComponent( - GlassBarButtonComponent( - size: variantsButtonSize, - backgroundColor: component.colors.3, - isDark: true, - state: .tintedGlass, - component: AnyComponentWithIdentity(id: "content", component: AnyComponent(HStack([ - AnyComponentWithIdentity(id: "icon1", component: AnyComponent( - GiftItemComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - peer: nil, - subject: variant1, - isPlaceholder: false, - mode: .tableIcon - ) - )), - AnyComponentWithIdentity(id: "icon2", component: AnyComponent( - GiftItemComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - peer: nil, - subject: variant2, - isPlaceholder: false, - mode: .tableIcon - ) - )), - AnyComponentWithIdentity(id: "icon3", component: AnyComponent( - GiftItemComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - peer: nil, - subject: variant3, - isPlaceholder: false, - mode: .tableIcon - ) - )), - AnyComponentWithIdentity(id: "text", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: variantsString, font: Font.semibold(13.0), textColor: .white))) - )), - AnyComponentWithIdentity(id: "arrow", component: AnyComponent( - BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: .white) - )) - ], spacing: 3.0))), - action: { [weak self] _ in - self?.openUpgradeVariants() - } - ) - ), - environment: {}, - containerSize: availableSize - ) - } - craftContentHeight += 160.0 - - let originalCraftContentHeight = craftContentHeight - if !"".isEmpty, isCrafting { - craftContentHeight = component.screenSize.height - } - - let descriptionTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - descriptionTextSize.width) * 0.5), y: craftContentHeight - 145.0 - 78.0 - 115.0), size: descriptionTextSize) + let descriptionTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - descriptionTextSize.width) * 0.5), y: craftContentHeight), size: descriptionTextSize) if let descriptionTextView = self.descriptionText.view { if descriptionTextView.superview == nil { self.addSubview(descriptionTextView) @@ -745,52 +467,395 @@ private final class CraftGiftPageContent: Component { transition.setAlpha(view: descriptionTextView, alpha: isCrafting ? 0.0 : 1.0) transition.setBlur(layer: descriptionTextView.layer, radius: isCrafting ? 10.0 : 0.0) } + craftContentHeight += descriptionTextSize.height + craftContentHeight += 16.0 - let backdropDialFrame = CGRect(origin: CGPoint(x: availableSize.width * 0.5 - 9.0 - backdropDialSize.width, y: craftContentHeight - 145.0 - 78.0), size: backdropDialSize) - if let backdropDialView = self.backdropDial.view { - if backdropDialView.superview == nil { - self.addSubview(backdropDialView) - } - transition.setFrame(view: backdropDialView, frame: backdropDialFrame) - transition.setAlpha(view: backdropDialView, alpha: isCrafting ? 0.0 : 1.0) - transition.setBlur(layer: backdropDialView.layer, radius: isCrafting ? 10.0 : 0.0) - } - - let symbolDialFrame = CGRect(origin: CGPoint(x: availableSize.width * 0.5 + 9.0, y: craftContentHeight - 145.0 - 78.0), size: symbolDialSize) - if let symbolDialView = self.symbolDial.view { - if symbolDialView.superview == nil { - self.addSubview(symbolDialView) - } - transition.setFrame(view: symbolDialView, frame: symbolDialFrame) - transition.setAlpha(view: symbolDialView, alpha: isCrafting ? 0.0 : 1.0) - transition.setBlur(layer: symbolDialView.layer, radius: isCrafting ? 10.0 : 0.0) - } - - let variantsButtonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - variantsButtonSize.width) / 2.0), y: craftContentHeight - 145.0), size: variantsButtonSize) - var varitantsButtonTransition = transition - if let variantsButtonView = self.variantsButton.view { - if variantsButtonView.superview == nil && component.displayState == .default { - varitantsButtonTransition = .immediate - if let symbolDialView = self.symbolDial.view { - self.insertSubview(variantsButtonView, aboveSubview: symbolDialView) - } else { - self.addSubview(variantsButtonView) + var attributes: [ResaleGiftsContext.Attribute: StarGift.UniqueGift.Attribute] = [:] + var backdropAttributeCount: [ResaleGiftsContext.Attribute: (Int32, Int)] = [:] + var patternAttributeCount: [ResaleGiftsContext.Attribute: (Int32, Int)] = [:] + for (index, gift) in selectedGifts { + for attribute in gift.gift.attributes { + switch attribute { + case let .backdrop(_, id, _, _, _, _, _): + let attributeId: ResaleGiftsContext.Attribute = .backdrop(id) + attributes[attributeId] = attribute + if let (minPosition, count) = backdropAttributeCount[attributeId] { + backdropAttributeCount[attributeId] = (min(index, minPosition), count + 1) + } else { + backdropAttributeCount[attributeId] = (index, 1) + } + case let .pattern(_, file, _): + let attributeId: ResaleGiftsContext.Attribute = .pattern(file.fileId.id) + attributes[attributeId] = attribute + if let (minPosition, count) = patternAttributeCount[attributeId] { + patternAttributeCount[attributeId] = (min(index, minPosition), count + 1) + } else { + patternAttributeCount[attributeId] = (index, 1) + } + default: + break } } - varitantsButtonTransition.setFrame(view: variantsButtonView, frame: variantsButtonFrame) - varitantsButtonTransition.setBlur(layer: variantsButtonView.layer, radius: isCrafting ? 10.0 : 0.0) - if component.displayState == .crafting { + } + var attributesCount: [ResaleGiftsContext.Attribute: (Int32, Int)] = [:] + for (attributeId, value) in backdropAttributeCount { + attributesCount[attributeId] = value + } + for (attributeId, value) in patternAttributeCount { + attributesCount[attributeId] = value + } + + var backdropAttributes: [(ResaleGiftsContext.Attribute, Int)] = [] + for (attributeId, count) in backdropAttributeCount { + backdropAttributes.append((attributeId, count.1)) + } + backdropAttributes = backdropAttributes.sorted(by: { lhs, rhs in + if lhs.1 != rhs.1 { + return lhs.1 > rhs.1 + } else { + return attributesCount[lhs.0]!.0 < attributesCount[rhs.0]!.0 + } + }) + var patternAttributes: [(ResaleGiftsContext.Attribute, Int)] = [] + for (attributeId, count) in patternAttributeCount { + patternAttributes.append((attributeId, count.1)) + } + patternAttributes = patternAttributes.sorted(by: { lhs, rhs in + if lhs.1 != rhs.1 { + return lhs.1 > rhs.1 + } else { + return attributesCount[lhs.0]!.0 < attributesCount[rhs.0]!.0 + } + }) + + var combinedAttributes: [ResaleGiftsContext.Attribute] = [] + for (attributeId, _) in backdropAttributes { + combinedAttributes.append(attributeId) + } + for (attributeId, _) in patternAttributes { + combinedAttributes.append(attributeId) + } + + let appConfiguration = component.context.currentAppConfiguration.with { $0 } + let giftCraftConfiguration = GiftCraftConfiguration.with(appConfiguration: appConfiguration) + + var firstRowCount = 0 + var secondRowCount = 0 + switch combinedAttributes.count { + case 0, 1, 2, 3, 4: + firstRowCount = combinedAttributes.count + case 5: + firstRowCount = 2 + secondRowCount = 3 + case 6: + firstRowCount = 3 + secondRowCount = 3 + case 7: + firstRowCount = 3 + secondRowCount = 4 + case 8: + firstRowCount = 4 + secondRowCount = 4 + default: + break + } + + let attributeDialSpacing: CGFloat = 18.0 + let attributeDialSize = CGSize(width: 48.0, height: 48.0) + + let attributeFirstRowTotalWidth = CGFloat(firstRowCount) * attributeDialSize.width + CGFloat(firstRowCount - 1) * attributeDialSpacing + let attributeSecondRowTotalWidth = CGFloat(secondRowCount) * attributeDialSize.width + CGFloat(secondRowCount - 1) * attributeDialSpacing + var attributeDialFrame: CGRect = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - attributeFirstRowTotalWidth) / 2.0), y: craftContentHeight), size: attributeDialSize) + + craftContentHeight += attributeDialSize.height + craftContentHeight += 39.0 + + var validIds: [AnyHashable] = [] + var attributeDialIndex = 0 + for attribute in combinedAttributes { + let itemId = AnyHashable(attribute) + validIds.append(itemId) + + var itemTransition = transition + let visibleItem: ComponentView + if let current = self.attributeDials[itemId] { + visibleItem = current + } else { + visibleItem = ComponentView() + self.attributeDials[itemId] = visibleItem + itemTransition = .immediate + } + + let tag: GenericComponentViewTag + if let current = self.attributeDialTags[itemId] { + tag = current + } else { + tag = GenericComponentViewTag() + self.attributeDialTags[itemId] = tag + } + + let attributeCount = attributesCount[attribute]?.1 ?? 0 + let permille = Int(giftCraftConfiguration.craftAttributePermilles[selectedGifts.count - 1][attributeCount - 1]) + + let dialContent: AnyComponentWithIdentity + var dialContentSize: CGSize? + let tooltipText: String + switch attribute { + case .backdrop: + guard case let .backdrop(name, _, innerColor, outerColor, _, _, _) = attributes[attribute] else { + continue + } + dialContent = AnyComponentWithIdentity( + id: "color", + component: AnyComponent( + ColorSwatchComponent( + innerColor: UIColor(rgb: UInt32(bitPattern: innerColor)), + outerColor: UIColor(rgb: UInt32(bitPattern: outerColor)) + ) + ) + ) + tooltipText = environment.strings.Gift_Craft_BackdropTooltip("\(permille / 10)", name).string + case .pattern: + guard case let .pattern(name, file, _) = attributes[attribute] else { + continue + } + dialContent = AnyComponentWithIdentity( + id: "symbol", + component: AnyComponent( + LottieComponent( + content: LottieComponent.ResourceContent( + context: component.context, + file: file, + attemptSynchronously: true, + providesPlaceholder: true + ), + color: .white, + size: CGSize(width: 32.0, height: 32.0) + ) + ) + ) + dialContentSize = CGSize(width: 30.0, height: 30.0) + tooltipText = environment.strings.Gift_Craft_SymbolTooltip("\(permille / 10)", name).string + default: + continue + } + + let _ = visibleItem.update( + transition: itemTransition, + component: AnyComponent( + PlainButtonComponent( + content: AnyComponent( + DialIndicatorComponent( + content: dialContent, + backgroundColor: .white.withAlphaComponent(0.1), + foregroundColor: .white, + diameter: 48.0, + contentSize: dialContentSize, + lineWidth: 4.0, + fontSize: 10.0, + progress: CGFloat(permille) / 10.0 / 100.0, + value: permille / 10, + suffix: "%" + ) + ), + action: { [weak self] in + guard let self else { + return + } + HapticFeedback().impact(.light) + + #if DEBUG + switch attribute { + case .backdrop: + self.component?.externalState.testFailOrSuccess = true + case .pattern: + self.component?.externalState.testFailOrSuccess = false + default: + break + } + #endif + self.showAttributeInfo(tag: tag, text: tooltipText) + }, + tag: tag + ) + ), + environment: {}, + containerSize: availableSize + ) + if let itemView = visibleItem.view { + if itemView.superview == nil { + self.addSubview(itemView) + + if !transition.animation.isImmediate { + itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } + } + itemTransition.setFrame(view: itemView, frame: attributeDialFrame) + transition.setAlpha(view: itemView, alpha: isCrafting ? 0.0 : 1.0) + transition.setBlur(layer: itemView.layer, radius: isCrafting ? 10.0 : 0.0) + } + + attributeDialFrame.origin.x += attributeDialSize.width + attributeDialSpacing + attributeDialIndex += 1 + + if attributeDialIndex == firstRowCount { + attributeDialFrame.origin.x = floorToScreenPixels((availableSize.width - attributeSecondRowTotalWidth) / 2.0) + attributeDialFrame.origin.y += 66.0 + } + } + + var removeIds: [AnyHashable] = [] + for (id, item) in self.attributeDials { + if !validIds.contains(id) { + removeIds.append(id) + if let itemView = item.view { + if !transition.animation.isImmediate { + itemView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false) + itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + itemView.removeFromSuperview() + }) + } else { + itemView.removeFromSuperview() + } + } + } + } + for id in removeIds { + self.attributeDials.removeValue(forKey: id) + } + + + if secondRowCount == 0, case .default = component.displayState { + let variantsString = environment.strings.Gift_Craft_ViewVariants + let variantsButtonMeasure = self.variantsButtonMeasure.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: variantsString, font: Font.semibold(13.0), textColor: .clear)))), + environment: {}, + containerSize: availableSize + ) + + let variantsButton: ComponentView + if let current = self.variantsButton { + variantsButton = current + } else { + variantsButton = ComponentView() + self.variantsButton = variantsButton + } + + let variantsButtonSize = CGSize(width: variantsButtonMeasure.width + 87.0, height: 24.0) + if let gift = self.starGiftsMap[component.gift.giftId] { + var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "") + var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "") + var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "") + + if let upgradePreview = self.upgradePreview { + var i = 0 + for attribute in upgradePreview { + if case .model = attribute { + switch i { + case 0: + variant1 = .preview(attributes: [attribute], rarity: nil) + case 1: + variant2 = .preview(attributes: [attribute], rarity: nil) + case 2: + variant3 = .preview(attributes: [attribute], rarity: nil) + default: + break + } + i += 1 + } + } + } + + let _ = variantsButton.update( + transition: transition, + component: AnyComponent( + GlassBarButtonComponent( + size: variantsButtonSize, + backgroundColor: component.colors.3, + isDark: true, + state: .tintedGlass, + component: AnyComponentWithIdentity(id: "content", component: AnyComponent(HStack([ + AnyComponentWithIdentity(id: "icon1", component: AnyComponent( + GiftItemComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + peer: nil, + subject: variant1, + isPlaceholder: false, + mode: .tableIcon + ) + )), + AnyComponentWithIdentity(id: "icon2", component: AnyComponent( + GiftItemComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + peer: nil, + subject: variant2, + isPlaceholder: false, + mode: .tableIcon + ) + )), + AnyComponentWithIdentity(id: "icon3", component: AnyComponent( + GiftItemComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + peer: nil, + subject: variant3, + isPlaceholder: false, + mode: .tableIcon + ) + )), + AnyComponentWithIdentity(id: "text", component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: variantsString, font: Font.semibold(13.0), textColor: .white))) + )), + AnyComponentWithIdentity(id: "arrow", component: AnyComponent( + BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: .white) + )) + ], spacing: 3.0))), + action: { [weak self] _ in + HapticFeedback().impact(.light) + + self?.openUpgradeVariants() + } + ) + ), + environment: {}, + containerSize: availableSize + ) + } + let variantsButtonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - variantsButtonSize.width) / 2.0), y: craftContentHeight), size: variantsButtonSize) + var varitantsButtonTransition = transition + if let variantsButtonView = variantsButton.view { + if variantsButtonView.superview == nil { + varitantsButtonTransition = .immediate + if let descriptionView = self.descriptionText.view { + self.insertSubview(variantsButtonView, aboveSubview: descriptionView) + } else { + self.addSubview(variantsButtonView) + } + } + varitantsButtonTransition.setFrame(view: variantsButtonView, frame: variantsButtonFrame) + } + } else if let variantsButton = self.variantsButton { + self.variantsButton = nil + if let variantsButtonView = variantsButton.view { + transition.setBlur(layer: variantsButtonView.layer, radius: isCrafting ? 10.0 : 0.0) variantsButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in variantsButtonView.removeFromSuperview() }) } } + craftContentHeight += 145.0 + let permilleValue = selectedGifts.reduce(0, { $0 + Int($1.value.gift.craftChancePermille ?? 0) }) if component.displayState == .crafting { - //var craftingOriginY = craftContentHeight * 0.5 + 160.0 var craftingOriginY = craftContentHeight * 0.5 - 16.0 - let offset = -(craftContentHeight - originalCraftContentHeight) + let offset: CGFloat = 0.0 let craftingTitleSize = self.craftingTitle.update( transition: transition, @@ -935,7 +1000,7 @@ private final class CraftGiftPageContent: Component { if component.displayState == .failure { var failureOriginY = craftContentHeight * 0.5 - 16.0 - let offset = -(craftContentHeight - originalCraftContentHeight) + let offset: CGFloat = 0.0 let failureTitleSize = self.failureTitle.update( transition: transition, @@ -1081,6 +1146,9 @@ private final class CraftGiftPageContent: Component { guard let self, let component = self.component, let environment = self.environment, let genericGift = self.starGiftsMap[component.gift.giftId], let resaleContext = component.resaleContext() else { return } + + HapticFeedback().impact(.light) + let selectController = SelectCraftGiftScreen( context: component.context, craftContext: component.craftContext, @@ -1105,6 +1173,8 @@ private final class CraftGiftPageContent: Component { guard let self else { return } + HapticFeedback().impact(.light) + self.component?.removeGift(index) }, willFinish: { [weak self] success in @@ -1142,6 +1212,10 @@ private final class CraftGiftPageContent: Component { controller.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { _ in controller.dismiss() }) + + HapticFeedback().success() + } else { + HapticFeedback().error() } } ) @@ -1482,7 +1556,7 @@ private final class SheetContainerComponent: CombinedComponent { permilleValue += gift.gift.craftChancePermille ?? 0 } } - if permilleValue > 900 { + if permilleValue >= 950 { colors.0 = UIColor(rgb: 0x1b3b3d) colors.1 = UIColor(rgb: 0x1a2f38) colors.2 = UIColor(rgb: 0x22464a) @@ -1689,6 +1763,8 @@ private final class SheetContainerComponent: CombinedComponent { state.displayInfo = false state.updated(transition: .spring(duration: 0.3)) } else if state.displayFailure, let genericGift = externalState.starGiftsMap[component.gift.giftId] { + HapticFeedback().impact(.light) + let selectController = SelectCraftGiftScreen( context: component.context, craftContext: component.craftContext, @@ -1707,6 +1783,8 @@ private final class SheetContainerComponent: CombinedComponent { ) environment.controller()?.push(selectController) } else { + HapticFeedback().impact(.medium) + state.inProgress = true state.updated(transition: .spring(duration: 0.3)) @@ -1876,23 +1954,25 @@ public class GiftCraftScreen: ViewControllerComponentContainer { private struct GiftCraftConfiguration { static var defaultValue: GiftCraftConfiguration { return GiftCraftConfiguration( - craftAttributePermilles: [60, 180, 450, 1000] + craftAttributePermilles: [[90], [80, 200], [70, 190, 460], [60, 180, 450, 1000]] ) } - let craftAttributePermilles: [Int32] + let craftAttributePermilles: [[Int32]] fileprivate init( - craftAttributePermilles: [Int32] + craftAttributePermilles: [[Int32]] ) { self.craftAttributePermilles = craftAttributePermilles } static func with(appConfiguration: AppConfiguration) -> GiftCraftConfiguration { if let data = appConfiguration.data { - var craftAttributePermilles: [Int32] = [] - if let value = data["stargifts_craft_attribute_permilles"] as? [Double] { - craftAttributePermilles = value.map { Int32($0) } + var craftAttributePermilles: [[Int32]] = [] + if let value = data["stargifts_craft_attribute_permilles"] as? [[Double]] { + craftAttributePermilles = value.map { innerArray in + innerArray.map { Int32($0) } + } } else { craftAttributePermilles = GiftCraftConfiguration.defaultValue.craftAttributePermilles } @@ -1905,20 +1985,3 @@ private struct GiftCraftConfiguration { } } } - -private func animateRipple(parentView: UIView, screenCornerRadius: CGFloat, location: CGPoint) { - if let snapshotView = parentView.snapshotView(afterScreenUpdates: false) { - let wrappingNode = SpaceWarpNodeImpl() - wrappingNode.isUserInteractionEnabled = false - wrappingNode.frame = CGRect(origin: .zero, size: parentView.bounds.size) - wrappingNode.update(size: parentView.bounds.size, cornerRadius: screenCornerRadius, transition: .immediate) - parentView.addSubview(wrappingNode.view) - wrappingNode.contentNode.view.addSubview(snapshotView) - - wrappingNode.triggerRipple(at: location) - - Queue.mainQueue().after(0.7, { - wrappingNode.view.removeFromSuperview() - }) - } -} diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/SelectCraftGiftScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/SelectCraftGiftScreen.swift index 9d93239554..93ec2b2d5d 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/SelectCraftGiftScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/SelectCraftGiftScreen.swift @@ -206,6 +206,8 @@ final class SelectGiftPageContent: Component { guard let self, let component = self.component else { return } + HapticFeedback().impact(.light) + component.selectGift(gift) component.dismiss() }, diff --git a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift index 07ede21c03..840df9d0fb 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftItemComponent/Sources/GiftItemComponent.swift @@ -172,6 +172,7 @@ public final class GiftItemComponent: Component { let animateChanges: Bool let mode: Mode let cornerRadius: CGFloat? + let allowAnimations: Bool let action: (() -> Void)? let contextAction: ((UIView, ContextGesture) -> Void)? @@ -200,6 +201,7 @@ public final class GiftItemComponent: Component { animateChanges: Bool = false, mode: Mode = .generic, cornerRadius: CGFloat? = nil, + allowAnimations: Bool = true, action: (() -> Void)? = nil, contextAction: ((UIView, ContextGesture) -> Void)? = nil ) { @@ -227,6 +229,7 @@ public final class GiftItemComponent: Component { self.animateChanges = animateChanges self.mode = mode self.cornerRadius = cornerRadius + self.allowAnimations = allowAnimations self.action = action self.contextAction = contextAction } @@ -304,6 +307,9 @@ public final class GiftItemComponent: Component { if lhs.cornerRadius != rhs.cornerRadius { return false } + if lhs.allowAnimations != rhs.allowAnimations { + return false + } if (lhs.contextAction == nil) != (rhs.contextAction == nil) { return false } @@ -713,7 +719,7 @@ public final class GiftItemComponent: Component { pointSize: CGSize(width: iconSize.width * 2.0, height: iconSize.height * 2.0), loopCount: 1 ) - animationLayer.isVisibleForAnimations = true + animationLayer.isVisibleForAnimations = component.allowAnimations self.animationLayer = animationLayer if let patternView = self.patternView.view { diff --git a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift index a27c6aa641..0a58d55d85 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftStoreScreen/Sources/GiftStoreScreen.swift @@ -882,6 +882,8 @@ public final class GiftStoreContentComponent: Component { } let previousFilterAttributes = self.starGiftsState?.filterAttributes let previousSorting = self.starGiftsState?.sorting + let previousDataState = self.starGiftsState?.dataState + let previousItems = self.starGiftsState?.gifts self.starGiftsState = state var transition: ComponentTransition = .immediate @@ -890,6 +892,11 @@ public final class GiftStoreContentComponent: Component { } else if let previousSorting, previousSorting != state.sorting { transition = .easeInOut(duration: 0.25) } + + if previousItems?.isEmpty == true, case .loading = previousDataState, component.confirmPurchaseImmediately { + transition = .spring(duration: 0.3) + } + if !self.isUpdating { self.state?.updated(transition: transition) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeVariantsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeVariantsScreen.swift index dfb46929db..f16c63f1fa 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeVariantsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftUpgradeVariantsScreen.swift @@ -519,7 +519,8 @@ private final class GiftUpgradeVariantsScreenComponent: Component { title: title, ribbon: nil, isSelected: isSelected, - mode: .upgradePreview + mode: .upgradePreview, + allowAnimations: self.selectedSection != .backdrops ) ), effectAlignment: .center, diff --git a/submodules/TelegramUI/Images.xcassets/Components/ColorMask.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Craft/DialColorMask.imageset/Contents.json similarity index 100% rename from submodules/TelegramUI/Images.xcassets/Components/ColorMask.imageset/Contents.json rename to submodules/TelegramUI/Images.xcassets/Premium/Craft/DialColorMask.imageset/Contents.json diff --git a/submodules/TelegramUI/Images.xcassets/Components/ColorMask.imageset/bg.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Craft/DialColorMask.imageset/bg.pdf similarity index 100% rename from submodules/TelegramUI/Images.xcassets/Components/ColorMask.imageset/bg.pdf rename to submodules/TelegramUI/Images.xcassets/Premium/Craft/DialColorMask.imageset/bg.pdf