From 422e3e4e24eb0b5c16c7b255bed7770251369f61 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 29 Jan 2026 21:37:06 +0400 Subject: [PATCH 1/4] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 5 + .../Sources/AccountContext.swift | 2 +- .../Sources/ResizableSheetComponent.swift | 136 ++++++++++++++++-- .../TelegramEngine/Payments/StarGifts.swift | 28 ++-- .../Sources/GiftCraftScreen.swift | 24 ++-- .../Sources/SelectCraftGiftScreen.swift | 9 +- .../Sources/GiftViewScreen.swift | 24 ++-- .../Sources/PeerInfoScreen.swift | 1 + .../Sources/GiftsListView.swift | 1 + .../Sources/SharedAccountContext.swift | 4 +- 10 files changed, 188 insertions(+), 46 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index db3598e081..54b50858b9 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15700,6 +15700,11 @@ Error: %8$@"; "Gift.Craft.Unavailable.Title" = "Try Later"; "Gift.Craft.Unavailable.Text" = "You will be able to craft this gift on %@."; +"Gift.Craft.Select.Title" = "Select Gifts"; +"Gift.Craft.Select.YourGifts" = "Your Gifts"; +"Gift.Craft.Select.SaleGifts" = "Suitable Gifts On Sale"; +"Gift.Craft.Select.NoGiftsFromCollection" = "You don't have other gifts\nfrom this collection"; + "Gift.Attribute.Rare" = "rare"; "Gift.Attribute.Legendary" = "legendary"; "Gift.Attribute.Epic" = "epic"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 1b40090279..fd73c89d38 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1462,7 +1462,7 @@ public protocol SharedAccountContext: AnyObject { func makeGiftOfferScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, crafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController - func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController + func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift, profileGiftsContext: ProfileGiftsContext?) -> ViewController func makeGiftDemoScreen(context: AccountContext) -> ViewController func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController func makeContentReportScreen(context: AccountContext, subject: ReportContentSubject, forceDark: Bool, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void, requestSelectMessages: ((String, Data, String?) -> Void)?) diff --git a/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift b/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift index dbb51b9746..f48f0541a5 100644 --- a/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift +++ b/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift @@ -179,7 +179,7 @@ public final class ResizableSheetComponent Bool { + if gestureRecognizer === self.dismissPanGesture { + let pan = gestureRecognizer as! UIPanGestureRecognizer + let velocity = pan.velocity(in: self) + if abs(velocity.y) <= abs(velocity.x) { + return false + } + } + return true + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer === self.dismissPanGesture { + if otherGestureRecognizer === self.scrollView.panGestureRecognizer { + return true + } + } + return false + } + @objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { self.dismissAnimated() @@ -328,6 +358,79 @@ public final class ResizableSheetComponent 0.0 + if shouldStartDismiss { + if !self.isDismissingInteractively { + self.isDismissingInteractively = true + self.dismissStartTranslation = translation.y + self.scrollView.isScrollEnabled = false + } + + let start = self.dismissStartTranslation ?? translation.y + let dismissOffset = max(0.0, translation.y - start) + self.scrollView.contentOffset = .zero + self.updateDismissTranslation(dismissOffset) + } else if self.isDismissingInteractively { + let start = self.dismissStartTranslation ?? translation.y + let dismissOffset = max(0.0, translation.y - start) + self.updateDismissTranslation(dismissOffset) + } + case .ended, .cancelled: + if self.isDismissingInteractively { + let velocityY = recognizer.velocity(in: self).y + let currentOffset = self.dismissTranslation + let threshold = min(180.0, self.bounds.height * 0.25) + let shouldDismiss = currentOffset > threshold || velocityY > 1000.0 + + self.isDismissingInteractively = false + self.scrollView.isScrollEnabled = !component.isFullscreen + + if shouldDismiss { + let animateOffset = self.bounds.height - self.backgroundLayer.frame.minY + let initialVelocity = animateOffset > 0.0 ? max(0.0, velocityY) / animateOffset : 0.0 + self.animateOut(initialVelocity: initialVelocity, completion: { [weak self] in + self?.environment?.dismiss(false) + }) + } else { + self.resetDismissTranslation(animated: true) + } + } + default: + break + } + } + private func updateScrolling(transition: ComponentTransition) { guard let itemLayout = self.itemLayout, let component = self.component else { return @@ -356,6 +459,7 @@ public final class ResizableSheetComponent Void) { + func animateOut(initialVelocity: CGFloat? = nil, completion: @escaping () -> Void) { let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY - self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) - self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in - completion() - }) - self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) - self.bottomContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.dimView.layer.animateAlpha(from: self.dimView.alpha, to: 0.0, duration: 0.3, removeOnCompletion: false) + if let initialVelocity = initialVelocity { + let transition = ContainedViewLayoutTransition.animated(duration: 0.35, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) + + transition.updatePosition(layer: self.scrollContentClippingView.layer, position: CGPoint(x: self.scrollContentClippingView.layer.position.x, y: self.scrollContentClippingView.layer.position.y + animateOffset), completion: { _ in + completion() + }) + transition.updatePosition(layer: self.backgroundLayer, position: CGPoint(x: self.backgroundLayer.position.x, y: self.backgroundLayer.position.y + animateOffset)) + transition.updatePosition(layer: self.navigationBarContainer.layer, position: CGPoint(x: self.navigationBarContainer.layer.position.x, y: self.navigationBarContainer.layer.position.y + animateOffset)) + transition.updatePosition(layer: self.bottomContainer.layer, position: CGPoint(x: self.bottomContainer.layer.position.x, y: self.bottomContainer.layer.position.y + animateOffset)) + } else { + let duration: Double = 0.25 + self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + self.bottomContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: duration, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) + } } func update(component: ResizableSheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index d8bda4bf32..1579c044f0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -2380,12 +2380,16 @@ private final class ProfileGiftsContextImpl { } func removeStarGifts(references: [StarGiftReference]) { - self.gifts.removeAll(where: { - if let reference = $0.reference { - return references.contains(reference) - } else { - return false + self.gifts.removeAll(where: { gift in + if let reference = gift.reference, references.contains(reference) { + return true } + for reference in references { + if case let .slug(slug) = reference, case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug { + return true + } + } + return false }) self.pushState() @@ -2400,13 +2404,17 @@ private final class ProfileGiftsContextImpl { } else { updatedGifts = [] } - updatedGifts = updatedGifts.filter { gift in - if let reference = gift.reference { - return !references.contains(reference) - } else { + updatedGifts.removeAll(where: { gift in + if let reference = gift.reference, references.contains(reference) { return true } - } + for reference in references { + if case let .slug(slug) = reference, case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug { + return true + } + } + return false + }) updatedCount -= Int32(references.count) if let entry = CodableEntry(CachedProfileGifts(gifts: updatedGifts, count: updatedCount, notificationsEnabled: nil)) { transaction.putItemCacheEntry(id: giftsEntryId(peerId: peerId, collectionId: collectionId), entry: entry) diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift index 8867406dd3..0935742682 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift @@ -270,7 +270,6 @@ private final class CraftGiftPageContent: Component { guard let self else { return } - //let isFirstTime = self.craftState == nil self.craftState = state var items: [GiftItem] = [] @@ -1118,9 +1117,14 @@ private final class CraftGiftPageContent: Component { self.state?.updated(transition: .easeInOut(duration: 0.5)) }, finished: { [weak self] view in - guard let self, let component = self.component, let environment = self.environment, let controller = environment.controller() else { + guard let self, let component = self.component, let environment = self.environment, let controller = environment.controller() as? GiftCraftScreen else { return } + var references: [StarGiftReference] = [] + for gift in selectedGifts.values { + references.append(gift.reference) + } + controller.profileGiftsContext?.removeStarGifts(references: references) if let _ = view { if case let .gift(gift) = component.result { let giftController = GiftViewScreen(context: component.context, subject: .profileGift(component.context.account.peerId, gift)) @@ -1129,12 +1133,12 @@ private final class CraftGiftPageContent: Component { navigationController.view.addSubview(ConfettiView(frame: navigationController.view.bounds)) } + + controller.profileGiftsContext?.insertStarGifts(gifts: [gift]) } controller.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { _ in controller.dismiss() }) - } else { - } } ) @@ -1691,8 +1695,8 @@ private final class SheetContainerComponent: CombinedComponent { selectedGiftIds: Set(), starsTopUpOptions: state.starsTopUpOptionsPromise.get(), selectGift: { item in - let craftController = GiftCraftScreen(context: component.context, gift: item.gift) if let controller = controller() as? GiftCraftScreen, let navigationController = controller.navigationController as? NavigationController { + let craftController = GiftCraftScreen(context: component.context, gift: item.gift, profileGiftsContext: controller.profileGiftsContext) controller.dismissAnimated() navigationController.pushViewController(craftController) } @@ -1770,7 +1774,7 @@ private final class SheetContainerComponent: CombinedComponent { ) ), backgroundColor: .color(backgroundColor), - isFullscreen: false, //state.isCrafting, + isFullscreen: false, animateOut: animateOut ), environment: { @@ -1805,12 +1809,16 @@ private final class SheetContainerComponent: CombinedComponent { public class GiftCraftScreen: ViewControllerComponentContainer { + fileprivate weak var profileGiftsContext: ProfileGiftsContext? + public init( context: AccountContext, - gift: StarGift.UniqueGift + gift: StarGift.UniqueGift, + profileGiftsContext: ProfileGiftsContext? ) { - let craftContext = CraftGiftsContext(account: context.account, giftId: gift.giftId) + self.profileGiftsContext = profileGiftsContext + let craftContext = CraftGiftsContext(account: context.account, giftId: gift.giftId) super.init( context: context, component: SheetContainerComponent( diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/SelectCraftGiftScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/SelectCraftGiftScreen.swift index ed0b4dc0af..250ec068cf 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/SelectCraftGiftScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/SelectCraftGiftScreen.swift @@ -129,7 +129,6 @@ final class SelectGiftPageContent: Component { guard let self else { return } - //let isFirstTime = self.craftState == nil self.craftState = state var items: [GiftItem] = [] @@ -179,7 +178,7 @@ final class SelectGiftPageContent: Component { let myGiftsTitleSize = self.myGiftsTitle.update( transition: transition, component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "Your Gifts".uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_YourGifts.uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor))) ), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) @@ -339,7 +338,7 @@ final class SelectGiftPageContent: Component { transition: .immediate, component: AnyComponent( MultilineTextComponent( - text: .plain(NSAttributedString(string: "You don't have other gifts\nfrom this collection", font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor)), + text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_NoGiftsFromCollection, font: Font.regular(13.0), textColor: environment.theme.list.itemSecondaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 3, lineSpacing: 0.1 @@ -365,7 +364,7 @@ final class SelectGiftPageContent: Component { let storeGiftsTitleSize = self.storeGiftsTitle.update( transition: transition, component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "SUITABLE GIFTS ON SALE".uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_SaleGifts.uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor))) ), environment: {}, containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) @@ -540,7 +539,7 @@ private final class SheetContainerComponent: CombinedComponent { ) ), titleItem: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: "Select Gifts", font: Font.semibold(17.0), textColor: environment.theme.actionSheet.primaryTextColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_Title, font: Font.semibold(17.0), textColor: environment.theme.actionSheet.primaryTextColor))) ), leftItem: AnyComponent( GlassBarButtonComponent( diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 1f921373e4..f2efd7b646 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1134,7 +1134,8 @@ private final class GiftViewSheetContent: CombinedComponent { let craftScreen = self.context.sharedContext.makeGiftCraftScreen( context: self.context, - gift: gift + gift: gift, + profileGiftsContext: controller.profileGiftsContext ) navigationController.pushViewController(craftScreen) } @@ -2554,7 +2555,7 @@ private final class GiftViewSheetContent: CombinedComponent { } if let number = arguments.giftNumber, let title = genericGift?.title { - titleString = "\(title) #\(formatCollectibleNumber(number, dateTimeFormat: environment.dateTimeFormat))" + titleString = "\(title) **#\(formatCollectibleNumber(number, dateTimeFormat: environment.dateTimeFormat))**" } else if isSelfGift { titleString = strings.Gift_View_Self_Title } else { @@ -3252,17 +3253,17 @@ private final class GiftViewSheetContent: CombinedComponent { let titleFont = Font.bold(20.0) let smallTitleFont = Font.bold(15.0) + let numberFont: UIFont + if let number = context.component.subject.arguments?.giftNumber, number < 1000 { + numberFont = titleFont + } else { + numberFont = smallTitleFont + } let titleAttributedString: NSAttributedString - if let uniqueGift { - let numberFont = uniqueGift.number < 1000 ? titleFont : smallTitleFont + if let _ = uniqueGift { titleAttributedString = parseMarkdownIntoAttributedString(titleString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: .white), bold: MarkdownAttributeSet(font: numberFont, textColor: vibrantColor), link: MarkdownAttributeSet(font: titleFont, textColor: .white), linkAttribute: { _ in return nil }), textAlignment: .center) } else { - titleAttributedString = NSAttributedString( - string: titleString, - font: titleFont, - textColor: theme.actionSheet.primaryTextColor, - paragraphAlignment: .center - ) + titleAttributedString = parseMarkdownIntoAttributedString(titleString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), bold: MarkdownAttributeSet(font: numberFont, textColor: theme.actionSheet.secondaryTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: theme.actionSheet.primaryTextColor), linkAttribute: { _ in return nil }), textAlignment: .center) } let title = title.update( @@ -5639,6 +5640,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { fileprivate let balanceOverlay = ComponentView() + fileprivate let profileGiftsContext: ProfileGiftsContext? fileprivate let updateSavedToProfile: ((StarGiftReference, Bool) -> Void)? fileprivate let convertToStars: ((StarGiftReference) -> Void)? fileprivate let dropOriginalDetails: ((StarGiftReference) -> Signal)? @@ -5658,6 +5660,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { allSubjects: [GiftViewScreen.Subject]? = nil, index: Int? = nil, forceDark: Bool = false, + profileGiftsContext: ProfileGiftsContext? = nil, updateSavedToProfile: ((StarGiftReference, Bool) -> Void)? = nil, convertToStars: ((StarGiftReference) -> Void)? = nil, dropOriginalDetails: ((StarGiftReference) -> Signal)? = nil, @@ -5672,6 +5675,7 @@ public class GiftViewScreen: ViewControllerComponentContainer { self.context = context self.subject = subject + self.profileGiftsContext = profileGiftsContext self.updateSavedToProfile = updateSavedToProfile self.convertToStars = convertToStars self.dropOriginalDetails = dropOriginalDetails diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 89dc5d3362..7307b15bc3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -2368,6 +2368,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro let controller = GiftViewScreen( context: self.context, subject: .profileGift(self.peerId, gift), + profileGiftsContext: profileGifts, updateSavedToProfile: { [weak profileGifts] reference, added in guard let profileGifts else { return diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift index 280848e31e..832f899e52 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/GiftsListView.swift @@ -606,6 +606,7 @@ final class GiftsListView: UIView { subject: .profileGift(self.peerId, product), allSubjects: allSubjects, index: index, + profileGiftsContext: self.profileGifts, updateSavedToProfile: { [weak self] reference, added in guard let self else { return diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 7af47de9ab..df4c15bdc1 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3899,8 +3899,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return GiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, attributes: attributes, completion: completion) } - public func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController { - return GiftCraftScreen(context: context, gift: gift) + public func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift, profileGiftsContext: ProfileGiftsContext?) -> ViewController { + return GiftCraftScreen(context: context, gift: gift, profileGiftsContext: profileGiftsContext) } public func makeGiftDemoScreen(context: AccountContext) -> ViewController { From d07d38df251475a9ed96233e319c184a0701b838 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Jan 2026 00:11:52 +0400 Subject: [PATCH 2/4] Various improvements --- .../Sources/ResizableSheetComponent.swift | 7 +- .../TelegramEngine/Payments/StarGifts.swift | 21 +- .../Sources/SelectCraftGiftScreen.swift | 224 ++++++++++-------- .../Sources/GiftViewScreen.swift | 18 +- 4 files changed, 161 insertions(+), 109 deletions(-) diff --git a/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift b/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift index f48f0541a5..6a3f505327 100644 --- a/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift +++ b/submodules/Components/ResizableSheetComponent/Sources/ResizableSheetComponent.swift @@ -19,6 +19,7 @@ public final class ResizableSheetComponentEnvironment: Equatable { public let screenSize: CGSize public let regularMetricsSize: CGSize? public let dismiss: (Bool) -> Void + public let boundsUpdated: ActionSlot public init( theme: PresentationTheme, @@ -30,7 +31,8 @@ public final class ResizableSheetComponentEnvironment: Equatable { isCentered: Bool, screenSize: CGSize, regularMetricsSize: CGSize?, - dismiss: @escaping (Bool) -> Void + dismiss: @escaping (Bool) -> Void, + boundsUpdated: ActionSlot = ActionSlot() ) { self.theme = theme self.statusBarHeight = statusBarHeight @@ -42,6 +44,7 @@ public final class ResizableSheetComponentEnvironment: Equatable { self.screenSize = screenSize self.regularMetricsSize = regularMetricsSize self.dismiss = dismiss + self.boundsUpdated = boundsUpdated } public static func ==(lhs: ResizableSheetComponentEnvironment, rhs: ResizableSheetComponentEnvironment) -> Bool { @@ -469,6 +472,8 @@ public final class ResizableSheetComponent let selectGift: (GiftItem) -> Void let dismiss: () -> Void + let boundsUpdated: ActionSlot init( context: AccountContext, @@ -45,7 +46,8 @@ final class SelectGiftPageContent: Component { selectedGiftIds: Set, starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>, selectGift: @escaping (GiftItem) -> Void, - dismiss: @escaping () -> Void + dismiss: @escaping () -> Void, + boundsUpdated: ActionSlot ) { self.context = context self.craftContext = craftContext @@ -56,6 +58,7 @@ final class SelectGiftPageContent: Component { self.starsTopUpOptions = starsTopUpOptions self.selectGift = selectGift self.dismiss = dismiss + self.boundsUpdated = boundsUpdated } static func ==(lhs: SelectGiftPageContent, rhs: SelectGiftPageContent) -> Bool { @@ -86,6 +89,9 @@ final class SelectGiftPageContent: Component { private var availableGifts: [GiftItem] = [] private var giftMap: [Int64: GiftItem] = [:] + private var availableSize: CGSize? + private var currentBounds: CGRect? + private var component: SelectGiftPageContent? private weak var state: EmptyComponentState? private var environment: ViewControllerComponentContainer.Environment? @@ -107,92 +113,16 @@ final class SelectGiftPageContent: Component { deinit { self.craftStateDisposable?.dispose() } - - func update(component: SelectGiftPageContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - self.isUpdating = true - defer { - self.isUpdating = false + + func updateScrolling(interactive: Bool, transition: ComponentTransition) -> CGFloat { + guard let bounds = self.currentBounds, let availableSize = self.availableSize, let component = self.component, let environment = self.environment else { + return 0.0 } - if self.component == nil { - let initialGiftItem = GiftItem( - gift: component.gift, - reference: .slug(slug: component.gift.slug) - ) - self.availableGifts = [ - initialGiftItem - ] - self.giftMap = [initialGiftItem.gift.id: initialGiftItem] - - self.craftStateDisposable = (component.craftContext.state - |> deliverOnMainQueue).start(next: { [weak self] state in - guard let self else { - return - } - self.craftState = state - - var items: [GiftItem] = [] - var map: [Int64: GiftItem] = [:] - var foundInitial = false - for gift in state.gifts { - guard let reference = gift.reference, case let .unique(uniqueGift) = gift.gift else { - continue - } - let giftItem = GiftItem( - gift: uniqueGift, - reference: reference - ) - map[uniqueGift.id] = giftItem - if uniqueGift.id == component.gift.id { - foundInitial = true - } else { - if component.selectedGiftIds.contains(uniqueGift.id) { - continue - } - items.append(giftItem) - } - } - - if !foundInitial { - map[initialGiftItem.gift.id] = initialGiftItem - } - self.availableGifts = items - self.giftMap = map - - self.state?.updated(transition: .spring(duration: 0.4)) - }) - } + let visibleBounds = bounds.insetBy(dx: 0.0, dy: -10.0) - let environment = environment[ViewControllerComponentContainer.Environment.self].value + var contentHeight: CGFloat = 88.0 + 32.0 - let sideInset: CGFloat = 16.0 + environment.safeInsets.left - - self.component = component - self.state = state - self.environment = environment - - self.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor - - var contentHeight: CGFloat = 88.0 - - let myGiftsTitleSize = self.myGiftsTitle.update( - transition: transition, - component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_YourGifts.uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor))) - ), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) - ) - let myGiftsTitleFrame = CGRect(origin: CGPoint(x: 26.0, y: contentHeight), size: myGiftsTitleSize) - if let myGiftsTitleView = self.myGiftsTitle.view { - if myGiftsTitleView.superview == nil { - self.addSubview(myGiftsTitleView) - } - transition.setFrame(view: myGiftsTitleView, frame: myGiftsTitleFrame) - } - - contentHeight += 32.0 - let itemSpacing: CGFloat = 10.0 let itemSideInset = 16.0 let itemsInRow: Int @@ -207,8 +137,7 @@ final class SelectGiftPageContent: Component { } let itemWidth = (availableSize.width - itemSideInset * 2.0 - itemSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow) let itemSize = CGSize(width: itemWidth, height: itemWidth) - - + var isLoading = false if self.availableGifts.isEmpty, case .loading = (self.craftState?.dataState ?? .loading) { isLoading = true @@ -228,10 +157,10 @@ final class SelectGiftPageContent: Component { var itemsHeight: CGFloat = 0.0 var validIds: [AnyHashable] = [] for gift in self.availableGifts { - let isVisible = "".isEmpty -// if visibleBounds.intersects(itemFrame) { -// isVisible = true -// } + var isVisible = false + if visibleBounds.intersects(itemFrame) { + isVisible = true + } if isVisible { let itemId = AnyHashable(gift.gift.id) validIds.append(itemId) @@ -255,8 +184,6 @@ final class SelectGiftPageContent: Component { } } - let badge: String? = gift.gift.craftChancePermille.flatMap { "+\($0 / 10)%" } - let _ = visibleItem.update( transition: itemTransition, component: AnyComponent( @@ -268,7 +195,7 @@ final class SelectGiftPageContent: Component { peer: nil, subject: .uniqueGift(gift: gift.gift, price: nil), ribbon: GiftItemComponent.Ribbon(text: ribbonText, font: .monospaced, color: ribbonColor, outline: nil), - badge: badge, + badge: gift.gift.craftChancePermille.flatMap { "+\($0 / 10)%" }, resellPrice: nil, isHidden: false, isSelected: false, @@ -361,6 +288,111 @@ final class SelectGiftPageContent: Component { contentHeight += 24.0 } + if let storeGiftsView = self.storeGifts.view as? GiftStoreContentComponent.View { + storeGiftsView.updateScrolling(bounds: bounds.offsetBy(dx: 0.0, dy: -contentHeight), interactive: interactive, transition: .immediate) + } + + return contentHeight + } + + func update(component: SelectGiftPageContent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + self.availableSize = availableSize + if self.component == nil { + self.currentBounds = CGRect(origin: .zero, size: availableSize) + + component.boundsUpdated.connect { [weak self] bounds in + guard let self else { + return + } + self.currentBounds = bounds + let _ = self.updateScrolling(interactive: true, transition: .immediate) + } + + let initialGiftItem = GiftItem( + gift: component.gift, + reference: .slug(slug: component.gift.slug) + ) + self.availableGifts = [ + initialGiftItem + ] + self.giftMap = [initialGiftItem.gift.id: initialGiftItem] + + self.craftStateDisposable = (component.craftContext.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let self else { + return + } + self.craftState = state + + var items: [GiftItem] = [] + var map: [Int64: GiftItem] = [:] + var foundInitial = false + for gift in state.gifts { + guard let reference = gift.reference, case let .unique(uniqueGift) = gift.gift else { + continue + } + let giftItem = GiftItem( + gift: uniqueGift, + reference: reference + ) + map[uniqueGift.id] = giftItem + if uniqueGift.id == component.gift.id { + foundInitial = true + } else { + if component.selectedGiftIds.contains(uniqueGift.id) { + continue + } + items.append(giftItem) + } + } + + if !foundInitial { + map[initialGiftItem.gift.id] = initialGiftItem + } + self.availableGifts = items + self.giftMap = map + + self.state?.updated(transition: .spring(duration: 0.4)) + }) + } + + let environment = environment[ViewControllerComponentContainer.Environment.self].value + + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + + self.component = component + self.state = state + self.environment = environment + + self.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor + + var contentHeight: CGFloat = 88.0 + + let myGiftsTitleSize = self.myGiftsTitle.update( + transition: transition, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.Gift_Craft_Select_YourGifts.uppercased(), font: Font.semibold(14.0), textColor: environment.theme.actionSheet.secondaryTextColor))) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + let myGiftsTitleFrame = CGRect(origin: CGPoint(x: 26.0, y: contentHeight), size: myGiftsTitleSize) + if let myGiftsTitleView = self.myGiftsTitle.view { + if myGiftsTitleView.superview == nil { + self.addSubview(myGiftsTitleView) + } + transition.setFrame(view: myGiftsTitleView, frame: myGiftsTitleFrame) + } + + contentHeight += 32.0 + + contentHeight = self.updateScrolling(interactive: false, transition: transition) + let storeGiftsTitleSize = self.storeGiftsTitle.update( transition: transition, component: AnyComponent( @@ -496,6 +528,8 @@ private final class SheetContainerComponent: CombinedComponent { static var body: Body { let sheet = Child(ResizableSheetComponent.self) let animateOut = StoredActionSlot(Action.self) + + let boundsUpdated = ActionSlot() return { context in let component = context.component @@ -535,7 +569,8 @@ private final class SheetContainerComponent: CombinedComponent { selectGift: component.selectGift, dismiss: { dismiss(true) - } + }, + boundsUpdated: boundsUpdated ) ), titleItem: AnyComponent( @@ -578,7 +613,8 @@ private final class SheetContainerComponent: CombinedComponent { regularMetricsSize: CGSize(width: 430.0, height: 900.0), dismiss: { animated in dismiss(animated) - } + }, + boundsUpdated: boundsUpdated ) }, availableSize: context.availableSize, diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index f2efd7b646..262e1a9b51 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -1511,7 +1511,7 @@ private final class GiftViewSheetContent: CombinedComponent { } if case let .unique(gift) = arguments.gift, let resellAmount = gift.resellAmounts?.first, resellAmount.amount.value > 0 { - if arguments.reference != nil || gift.owner.peerId == context.account.peerId { + if arguments.reference != nil || gift.owner?.peerId == self.context.account.peerId { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_View_Context_ChangePrice, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/PriceTag"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in @@ -2483,6 +2483,7 @@ private final class GiftViewSheetContent: CombinedComponent { var isMyHostedUniqueGift = false var releasedByPeer: EnginePeer? var canGiftUpgrade = false + var isDismantled = false if case let .soldOutGift(gift) = subject { animationFile = gift.file @@ -2571,6 +2572,10 @@ private final class GiftViewSheetContent: CombinedComponent { titleString = "" } + if let uniqueGift, uniqueGift.owner == nil { + isDismantled = true + } + if !canUpgrade, let gift = state.starGiftsMap[giftId], let _ = gift.upgradeStars { canUpgrade = true } @@ -3764,6 +3769,8 @@ private final class GiftViewSheetContent: CombinedComponent { ) ) )) + default: + break } if let peerId = uniqueGift.hostPeerId, let peer = state.peerMap[peerId] { @@ -4332,7 +4339,7 @@ private final class GiftViewSheetContent: CombinedComponent { ) ), at: hasOriginalInfo ? tableItems.count - 1 : tableItems.count) - if let valueAmount = uniqueGift.valueAmount, let valueCurrency = uniqueGift.valueCurrency { + if let valueAmount = uniqueGift.valueAmount, let valueCurrency = uniqueGift.valueCurrency, !isDismantled { tableItems.insert(.init( id: "fiatValue", title: strings.Gift_Unique_Value, @@ -4545,7 +4552,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let controller = controller() as? GiftViewScreen, controller.openChatTheme != nil { isChatTheme = true } - if ((incoming && !converted && !upgraded) || exported || selling || isChatTheme) && (!showUpgradePreview && !showWearPreview) { + if ((incoming && !converted && !upgraded) || exported || selling || isChatTheme) && (!showUpgradePreview && !showWearPreview && !isDismantled) { let textFont = Font.regular(13.0) let textColor = theme.list.itemSecondaryTextColor let linkColor = theme.actionSheet.controlAccentColor @@ -4654,6 +4661,7 @@ private final class GiftViewSheetContent: CombinedComponent { foreground: theme.list.itemCheckColors.foregroundColor, pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) ) + let buttonChild: _UpdatedChildComponent if state.canSkip { buttonChild = button.update( @@ -5027,7 +5035,7 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: buttonSize, transition: context.transition ) - } else if incoming && !converted && !savedToProfile { + } else if incoming && !converted && !savedToProfile && !isDismantled { let buttonTitle = isChannelGift ? strings.Gift_View_Display_Channel : strings.Gift_View_Display buttonChild = button.update( component: ButtonComponent( @@ -5246,7 +5254,7 @@ private final class GiftViewSheetContent: CombinedComponent { )) var rightControlItems: [GlassControlGroupComponent.Item] = [] - if uniqueGift != nil && !showWearPreview { + if uniqueGift != nil && !showWearPreview && !isDismantled { if let _ = component.subject.arguments?.canCraftDate { var canCraft = false if let data = component.context.currentAppConfiguration.with({ $0 }).data { From f0c0f806e66e9de7160695cececce6028b04a899 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Jan 2026 01:45:59 +0400 Subject: [PATCH 3/4] Various improvements --- .../Sources/GiftCraftScreen.swift | 2 +- .../Sources/GiftViewScreen.swift | 9 +++- .../StoryQualityUpgradeSheetScreen/BUILD | 1 + ...yQualityUpgradeSheetContentComponent.swift | 1 - .../StoryQualityUpgradeSheetScreen.swift | 46 +++++++++++-------- 5 files changed, 36 insertions(+), 23 deletions(-) delete mode 100644 submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetContentComponent.swift diff --git a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift index 0935742682..bdcc2ad5dc 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftCraftScreen/Sources/GiftCraftScreen.swift @@ -1245,7 +1245,7 @@ private final class CraftGiftPageContent: Component { } - self.infoBackground.backgroundColor = environment.theme.list.plainBackgroundColor.cgColor + self.infoBackground.backgroundColor = environment.theme.list.modalPlainBackgroundColor.cgColor let infoBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 80.0), size: CGSize(width: availableSize.width, height: 1000.0)) transition.setFrame(layer: self.infoBackground, frame: infoBackgroundFrame) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 262e1a9b51..118b4c5739 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -5606,7 +5606,14 @@ public class GiftViewScreen: ViewControllerComponentContainer { if case let .unique(uniqueGift) = gift.gift { resellAmounts = uniqueGift.resellAmounts } - return (peerId, gift.fromPeer?.id, gift.fromPeer?.debugDisplayTitle, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash, gift.upgradeSeparate, gift.dropOriginalDetailsStars, nil, gift.number, gift.canCraftAt) + + var number: Int32? + if case let .unique(uniqueGift) = gift.gift { + number = uniqueGift.number + } else if let numberValue = gift.number { + number = numberValue + } + return (peerId, gift.fromPeer?.id, gift.fromPeer?.debugDisplayTitle, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash, gift.upgradeSeparate, gift.dropOriginalDetailsStars, nil, number, gift.canCraftAt) case .soldOutGift: return nil case .upgradePreview: diff --git a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD index 316cc15fa8..d0df650baa 100644 --- a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/BUILD @@ -22,6 +22,7 @@ swift_library( "//submodules/Components/SheetComponent", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/GlassBarButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetContentComponent.swift deleted file mode 100644 index 8b13789179..0000000000 --- a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetContentComponent.swift +++ /dev/null @@ -1 +0,0 @@ - diff --git a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift index 024a40960a..bf0ea4525f 100644 --- a/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryQualityUpgradeSheetScreen/Sources/StoryQualityUpgradeSheetScreen.swift @@ -12,6 +12,7 @@ import BalancedTextComponent import Markdown import TelegramStringFormatting import BundleIconComponent +import GlassBarButtonComponent public final class ButtonSubtitleComponent: CombinedComponent { public let title: String @@ -124,23 +125,29 @@ private final class StoryQualityUpgradeSheetContentComponent: Component { } let cancelButtonSize = cancelButton.update( transition: transition, - component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)), - action: { [weak self] in - guard let self, let component = self.component else { - return - } + component: AnyComponent(GlassBarButtonComponent( + size: CGSize(width: 44.0, height: 44.0), + backgroundColor: nil, + isDark: environment.theme.overallDarkAppearance, + state: .glass, + component: AnyComponentWithIdentity(id: "close", component: AnyComponent( + BundleIconComponent( + name: "Navigation/Close", + tintColor: environment.theme.chat.inputPanel.panelControlColor + ) + )), + action: { _ in component.dismiss() } - ).minSize(CGSize(width: 8.0, height: 44.0))), + )), environment: {}, - containerSize: CGSize(width: 200.0, height: 100.0) + containerSize: CGSize(width: 44.0, height: 44.0) ) if let cancelButtonView = cancelButton.view { if cancelButtonView.superview == nil { self.addSubview(cancelButtonView) } - transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 6.0), size: cancelButtonSize)) + transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelButtonSize)) } var contentHeight: CGFloat = 0.0 @@ -152,7 +159,8 @@ private final class StoryQualityUpgradeSheetContentComponent: Component { content: LottieComponent.AppBundleContent(name: "StoryUpgradeSheet"), color: nil, startingPosition: .begin, - size: CGSize(width: 100.0, height: 100.0) + size: CGSize(width: 100.0, height: 100.0), + loop: true )), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0) @@ -217,16 +225,18 @@ private final class StoryQualityUpgradeSheetContentComponent: Component { color: environment.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7) )))) + let buttonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0) let buttonSize = self.button.update( transition: transition, component: AnyComponent(ButtonComponent( background: ButtonComponent.Background( + style: .glass, color: environment.theme.list.itemCheckColors.fillColor, foreground: environment.theme.list.itemCheckColors.foregroundColor, pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) ), content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( - VStack(buttonContents, spacing: 3.0) + VStack(buttonContents, spacing: 1.0) )), isEnabled: true, allowActionWhenDisabled: true, @@ -240,9 +250,9 @@ private final class StoryQualityUpgradeSheetContentComponent: Component { } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 52.0) ) - let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize) + let buttonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: contentHeight), size: buttonSize) if let buttonView = self.button.view { if buttonView.superview == nil { self.addSubview(buttonView) @@ -250,12 +260,7 @@ private final class StoryQualityUpgradeSheetContentComponent: Component { transition.setFrame(view: buttonView, frame: buttonFrame) } contentHeight += buttonSize.height - - if environment.safeInsets.bottom.isZero { - contentHeight += 16.0 - } else { - contentHeight += environment.safeInsets.bottom + 14.0 - } + contentHeight += buttonInsets.bottom return CGSize(width: availableSize.width, height: contentHeight) } @@ -355,7 +360,8 @@ private final class StoryQualityUpgradeSheetScreenComponent: Component { }) } )), - backgroundColor: .color(environment.theme.overallDarkAppearance ? environment.theme.list.itemBlocksBackgroundColor : environment.theme.list.blocksBackgroundColor), + style: .glass, + backgroundColor: .color(environment.theme.list.modalPlainBackgroundColor), animateOut: self.sheetAnimateOut )), environment: { From 90a4dbe855d4d2542a292713ebf919f5846f7705 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Jan 2026 03:21:18 +0400 Subject: [PATCH 4/4] Fix stake dice result message --- .../Sources/ServiceMessageStrings.swift | 22 ++++++++++++++++++- .../StringForMessageTimestampStatus.swift | 3 +-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index d1e8365451..0952a9b132 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1780,7 +1780,27 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, rawString = strings.Conversation_EmojiStake_WonYou(value).string } } else { - let compactPeerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? "" + var compactPeerName = "" + if let authorSignature = message.forwardInfo?.authorSignature { + compactPeerName = authorSignature + } else if let author = message.forwardInfo?.author { + compactPeerName = EnginePeer(author).compactDisplayTitle + } else if let source = message.forwardInfo?.source { + compactPeerName = EnginePeer(source).compactDisplayTitle + } else { + if let author = message.author, case .user = author { + compactPeerName = author.compactDisplayTitle + } else { + for attribute in message.attributes { + if let attribute = attribute as? AuthorSignatureMessageAttribute { + compactPeerName = attribute.signature + } + } + if compactPeerName.isEmpty { + compactPeerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? "" + } + } + } if value == 1, let tonAmount = dice.tonAmount { let value = formatTonAmountText(tonAmount, dateTimeFormat: dateTimeFormat) rawString = strings.Conversation_EmojiStake_Lost(compactPeerName, value).string diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift index 926ccdee44..938c5ad110 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift @@ -149,8 +149,7 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess dayText = strings.Date_ChatDateHeaderYear(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)", "\(1900 + timeinfo.tm_year)").string } dateText = strings.Message_FullDateFormat(dayText, stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)).string - } - else if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) { + } else if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) { dateText = strings.Message_ImportedDateFormat(dateStringForDay(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: forwardInfo.date), stringForMessageTimestamp(timestamp: forwardInfo.date, dateTimeFormat: dateTimeFormat), dateText).string }