From ac28ed9d743c1cf36bff61b4185afa42e0fab6e4 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Nov 2025 17:47:34 +0400 Subject: [PATCH 1/4] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 31 ++++++++- .../Sources/AccountContext.swift | 2 +- .../Payments/StarGiftsAuctions.swift | 23 +++++-- .../Sources/GiftOptionsScreen.swift | 22 ++++++- .../Sources/GiftAuctionBidScreen.swift | 36 +++++++--- .../Sources/GiftAuctionInfoScreen.swift | 66 +++++++++++++------ .../Sources/GiftAuctionViewScreen.swift | 34 ++++++---- .../Sources/SharedAccountContext.swift | 4 +- 8 files changed, 163 insertions(+), 55 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index f3c6b86322..e25e896157 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15180,7 +15180,7 @@ Error: %8$@"; "Notification.GiftAuction.Acquired" = "You've successfully bought a gift in the auction for %@."; "Gift.Auction.Auction" = "auction"; -"Gift.Auction.Description" = "Top %@ bidders will get a **%@** this round. [Learn More >]()"; +"Gift.Auction.Description" = "Top %@ bidders will get **%@** gifts this round. [Learn More >]()"; "Gift.Auction.Ended" = "Auction ended"; "Gift.Auction.FirstSale" = "First Sale"; "Gift.Auction.LastSale" = "Last Sale"; @@ -15213,6 +15213,14 @@ Error: %8$@"; "Gift.AuctionBid.TopWinners" = "Top 3 Winners"; "Gift.AuctionBid.PlaceBid" = "Place a %@ Bid"; "Gift.AuctionBid.AddToBid" = "Add %@ to Your Bid"; +"Gift.AuctionBid.Placed.Title" = "Your bid has been placed"; +"Gift.AuctionBid.Placed.Text" = "If you fall below the top %@, your bid will roll over to the next drop."; +"Gift.AuctionBid.Increased.Title" = "Your bid has been increased"; +"Gift.AuctionBid.Increased.Text" = "If you fall below the top %@, your bid will roll over to the next drop."; +"Gift.AuctionBid.AddMoreStars" = "Add at least %@ to increase your bid."; +"Gift.AuctionBid.AddMoreStars.Stars_1" = "%@ Star"; +"Gift.AuctionBid.AddMoreStars.Stars_any" = "%@ Stars"; +"Gift.AuctionBid.Top" = "TOP %@"; "Gift.Auction.Context.About" = "About"; "Gift.Auction.Context.CopyLink" = "Copy Link"; @@ -15264,3 +15272,24 @@ Error: %8$@"; "Gift.Auction.Ongoing.Text" = "You’ve already bid in this gift auction for **%@**."; "Gift.Auction.Ongoing.TextYourself" = "You’ve already bid in this gift auction for yourself."; "Gift.Auction.Ongoing.View" = "View"; + +"Gift.Auction.Info.Title" = "Auction"; +"Gift.Auction.Info.Description" = "Join the battle for exclusive gifts."; + +"Gift.Auction.Info.TopBidders.Title_1" = "Top %@ Bidder"; +"Gift.Auction.Info.TopBidders.Title_any" = "Top %@ Bidders"; +"Gift.Auction.Info.TopBidders.Text" = "%@ are dropped in %@ to the top %@ by bid amount."; +"Gift.Auction.Info.TopBidders.Gifts_1" = "%@ gift"; +"Gift.Auction.Info.TopBidders.Gifts_any" = "%@ gifts"; +"Gift.Auction.Info.TopBidders.Rounds_1" = "%@ round"; +"Gift.Auction.Info.TopBidders.Rounds_any" = "%@ rounds"; +"Gift.Auction.Info.TopBidders.Bidders_1" = "%@ bidder"; +"Gift.Auction.Info.TopBidders.Bidders_any" = "%@ bidders"; + +"Gift.Auction.Info.Carryover.Title" = "Bid Carryover"; +"Gift.Auction.Info.Carryover.Text" = "If your bid leaves the top %@, it will automatically join the next drop."; + +"Gift.Auction.Info.Missed.Title" = "Missed Bidders"; +"Gift.Auction.Info.Missed.Text" = "If your bid doesn't win after the final drop, your Stars will be fully refunded."; + +"Gift.Auction.Info.Understood" = "Understood"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index d6a58698a8..aec951be6f 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1422,7 +1422,7 @@ public protocol SharedAccountContext: AnyObject { func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: ((StarGift.UniqueGift) -> Void)?) -> ViewController func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, openChatTheme: (() -> Void)?, dismissed: (() -> Void)?) -> ViewController func makeGiftWearPreviewScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController - func makeGiftAuctionInfoScreen(context: AccountContext, gift: StarGift, completion: (() -> Void)?) -> ViewController + func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController func makeGiftAuctionBidScreen(context: AccountContext, auctionContext: GiftAuctionContext) -> ViewController func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext) -> ViewController func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift index e61519538c..1a7ffbdee5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift @@ -91,7 +91,7 @@ public final class GiftAuctionContext { } public var currentBidPeerId: EnginePeer.Id? { - if self.myState?.bidAmount != nil { + if self.myState?.bidAmount != nil, case .ongoing = self.auctionState { return self.myState?.bidPeerId } else { return nil @@ -145,10 +145,19 @@ public final class GiftAuctionContext { self.myState = myState self.timeout = timeout + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + var effectiveTimeout = timeout + if case let .ongoing(_, _, _, _, _, _, nextRoundDate, _, _, _) = auctionState { + let delta = nextRoundDate - currentTime + if delta > 0 && delta < timeout { + effectiveTimeout = delta + } + } + self.pushState() self.updateTimer?.invalidate() - self.updateTimer = SwiftSignalKit.Timer(timeout: Double(timeout), repeat: false, completion: { [weak self] _ in + self.updateTimer = SwiftSignalKit.Timer(timeout: Double(effectiveTimeout), repeat: false, completion: { [weak self] _ in guard let self else { return } @@ -385,11 +394,11 @@ public class GiftAuctionsManager { guard let self, let activeAuctions else { return } - var auctionContexts: [Int64 : GiftAuctionContext] = [:] for auction in activeAuctions { - auctionContexts[auction.gift.giftId] = auction + if self.auctionContexts[auction.gift.giftId] == nil { + self.auctionContexts[auction.gift.giftId] = auction + } } - self.auctionContexts = auctionContexts self.updateState() })) } @@ -417,7 +426,7 @@ public class GiftAuctionsManager { } } - func storeAuctionContext(auctionContext: GiftAuctionContext) { + public func storeAuctionContext(auctionContext: GiftAuctionContext) { self.auctionContexts[auctionContext.gift.giftId] = auctionContext self.updateState() } @@ -427,7 +436,7 @@ public class GiftAuctionsManager { for auction in self.auctionContexts.values.sorted(by: { $0.gift.giftId < $1.gift.giftId }) { signals.append(auction.state |> mapToSignal { state in - if let state, state.myState.bidAmount != nil { + if let state, state.myState.bidAmount != nil, case .ongoing = state.auctionState { return .single(state) } else { return .complete() diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 5223111f19..f7f256ca9f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -314,7 +314,7 @@ final class GiftOptionsScreenComponent: Component { } let _ = (component.context.engine.payments.checkCanSendStarGift(giftId: gift.id) - |> deliverOnMainQueue).start(next: { [weak self, weak controller] result in + |> deliverOnMainQueue).start(next: { [weak self, weak controller] result in guard let self, let controller else { return } @@ -369,11 +369,22 @@ final class GiftOptionsScreenComponent: Component { guard let giftAuctionsManager = component.context.giftAuctionsManager else { return } + + self.loadingGiftId = gift.id + Queue.mainQueue().after(0.25) { + if self.loadingGiftId != nil { + self.state?.updated() + } + } + self.auctionDisposable.set((giftAuctionsManager.auctionContext(for: .giftId(gift.id)) |> deliverOnMainQueue).start(next: { [weak self, weak mainController] auctionContext in - guard let auctionContext, let component = self?.component, let mainController else { + guard let self, let auctionContext, let component = self.component, let mainController else { return } + self.loadingGiftId = nil + self.state?.updated() + if let currentBidPeerId = auctionContext.currentBidPeerId { if currentBidPeerId == component.peerId { let giftController = component.context.sharedContext.makeGiftAuctionBidScreen( @@ -1317,6 +1328,13 @@ final class GiftOptionsScreenComponent: Component { let hasAnyGifts = hasGenericGifts || hasTransferGifts if isSelfGift || isChannelGift || isPremiumDisabled { + if !self.premiumItems.isEmpty { + for (_, itemView) in self.premiumItems { + itemView.view?.removeFromSuperview() + } + self.premiumItems.removeAll() + } + contentHeight += 6.0 } else { if let premiumProducts = state.premiumProducts { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift index 74e42f0449..fb4109cb2f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift @@ -810,11 +810,10 @@ private final class SliderBackgroundComponent: Component { topLineFrameTransition.setFrame(layer: self.topBackgroundLine, frame: topLineFrame) topLineAlphaTransition.setAlpha(layer: self.topBackgroundLine, alpha: topLineAlpha) - //TODO:localize let topTextSize = self.topForegroundText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "TOP \(component.giftsPerRound)", font: Font.semibold(15.0), textColor: UIColor(white: 1.0, alpha: 0.4))) + text: .plain(NSAttributedString(string: component.strings.Gift_AuctionBid_Top("\(component.giftsPerRound)").string, font: Font.semibold(15.0), textColor: UIColor(white: 1.0, alpha: 0.4))) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) @@ -822,7 +821,7 @@ private final class SliderBackgroundComponent: Component { let _ = self.topBackgroundText.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "TOP \(component.giftsPerRound)", font: Font.semibold(15.0), textColor: component.theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.22) : UIColor(white: 0.0, alpha: 0.2))) + text: .plain(NSAttributedString(string: component.strings.Gift_AuctionBid_Top("\(component.giftsPerRound)").string, font: Font.semibold(15.0), textColor: component.theme.overallDarkAppearance ? UIColor(white: 1.0, alpha: 0.22) : UIColor(white: 0.0, alpha: 0.2))) )), environment: {}, containerSize: CGSize(width: availableSize.width, height: 100.0) @@ -1364,21 +1363,30 @@ private final class GiftAuctionBidScreenComponent: Component { return } + var isUpdate = false let value = Int64(self.amount.realValue) if let myBidAmount = self.giftAuctionState?.myState.bidAmount { + isUpdate = true if value == myBidAmount { controller.dismiss() return } } + let giftsPerRounds = gift.auctionGiftsPerRound ?? 50 + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } if let myBidAmount = self.giftAuctionState?.myState.bidAmount, let myMinBidAmount = self.giftAuctionState?.myState.minBidAmount, value < myMinBidAmount { HapticFeedback().error() - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } controller.present( UndoOverlayController( presentationData: presentationData, - content: .info(title: nil, text: "Add at least \(myMinBidAmount - myBidAmount) Stars to increase your bid.", timeout: nil, customUndoText: nil), + content: .info( + title: nil, + text: presentationData.strings.Gift_AuctionBid_AddMoreStars(presentationData.strings.Gift_AuctionBid_AddMoreStars_Stars(Int32(clamping: myMinBidAmount - myBidAmount))).string, + timeout: nil, + customUndoText: nil + ), position: .bottom, action: { _ in return true } ), @@ -1431,13 +1439,20 @@ private final class GiftAuctionBidScreenComponent: Component { self.state?.updated() self.amount = self.amount.withMinAllowedRealValue(Int(value)) + + let title = isUpdate ? presentationData.strings.Gift_AuctionBid_Increased_Title : presentationData.strings.Gift_AuctionBid_Placed_Title + let text = isUpdate ? presentationData.strings.Gift_AuctionBid_Increased_Text("\(giftsPerRounds)").string : presentationData.strings.Gift_AuctionBid_Placed_Text("\(giftsPerRounds)").string - //TODO:localize let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } controller?.present( UndoOverlayController( presentationData: presentationData, - content: .actionSucceeded(title: "Your bid has been placed", text: "If you fall below the top 50, your bid will roll over to the next drop.", cancel: nil, destructive: false), + content: .actionSucceeded( + title: title, + text: text, + cancel: nil, + destructive: false + ), position: .bottom, action: { _ in return true } ), @@ -1571,6 +1586,7 @@ private final class GiftAuctionBidScreenComponent: Component { let context = component.context let gift = component.auctionContext.gift + let auctionContext = component.auctionContext let presentationData = context.sharedContext.currentPresentationData.with { $0 } var link = "" @@ -1583,7 +1599,7 @@ private final class GiftAuctionBidScreenComponent: Component { items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_Auction_Context_About, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, f in f(.default) - let infoController = context.sharedContext.makeGiftAuctionInfoScreen(context: context, gift: gift, completion: nil) + let infoController = context.sharedContext.makeGiftAuctionInfoScreen(context: context, auctionContext: auctionContext, completion: nil) controller?.push(infoController) }))) @@ -1973,7 +1989,7 @@ private final class GiftAuctionBidScreenComponent: Component { var dropsLeftAnimatedItems: [AnimatedTextComponent.Item] = [] if let auctionState = self.giftAuctionState?.auctionState { - if case let .ongoing(_, _, _, minBidAmount, _, _, nextDropDate, _, dropsLeft, _) = auctionState { + if case let .ongoing(_, _, _, minBidAmount, _, _, nextDropDate, dropsLeft, _, _) = auctionState { var minBidAmount = minBidAmount if let myMinBidAmmount = self.giftAuctionState?.myState.minBidAmount { minBidAmount = myMinBidAmmount @@ -2040,7 +2056,7 @@ private final class GiftAuctionBidScreenComponent: Component { } let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) - let dropTimeout = nextDropDate - currentTime + let dropTimeout = max(0, nextDropDate - currentTime) let minutes = Int(dropTimeout / 60) let seconds = Int(dropTimeout % 60) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift index fa198c9135..da94c92f4e 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionInfoScreen.swift @@ -26,17 +26,20 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { let context: AccountContext let gift: StarGift + let auctionContext: GiftAuctionContext let animateOut: ActionSlot> let getController: () -> ViewController? init( context: AccountContext, gift: StarGift, + auctionContext: GiftAuctionContext, animateOut: ActionSlot>, getController: @escaping () -> ViewController? ) { self.context = context self.gift = gift + self.auctionContext = auctionContext self.animateOut = animateOut self.getController = getController } @@ -53,19 +56,42 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { final class State: ComponentState { private let context: AccountContext + private let auctionContext: GiftAuctionContext private let animateOut: ActionSlot> private let getController: () -> ViewController? + + private(set) var rounds: Int32 = 50 + + fileprivate let playButtonAnimation = ActionSlot() + private var didPlayAnimation = false init( context: AccountContext, + auctionContext: GiftAuctionContext, animateOut: ActionSlot>, getController: @escaping () -> ViewController? ) { self.context = context + self.auctionContext = auctionContext self.animateOut = animateOut self.getController = getController super.init() + + let _ = (self.auctionContext.state + |> deliverOnMainQueue).startStandalone(next: { [weak self] state in + if let self, case let .ongoing(_, _, _, _, _, _, _, _, _, totalRounds) = state?.auctionState { + self.rounds = totalRounds + self.updated() + } + }) + } + + func playAnimationIfNeeded() { + if !self.didPlayAnimation { + self.didPlayAnimation = true + self.playButtonAnimation.invoke(Void()) + } } func dismiss(animated: Bool) { @@ -83,7 +109,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, animateOut: self.animateOut, getController: self.getController) + return State(context: self.context, auctionContext: self.auctionContext, animateOut: self.animateOut, getController: self.getController) } static var body: Body { @@ -101,7 +127,6 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { let theme = environment.theme let strings = environment.strings - let _ = strings let sideInset: CGFloat = 30.0 + environment.safeInsets.left let textSideInset: CGFloat = 30.0 + environment.safeInsets.left @@ -137,7 +162,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { let title = title.update( component: BalancedTextComponent( - text: .plain(NSAttributedString(string: "Auction", font: titleFont, textColor: textColor)), + text: .plain(NSAttributedString(string: strings.Gift_Auction_Info_Title, font: titleFont, textColor: textColor)), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.1 @@ -153,7 +178,7 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { let text = text.update( component: BalancedTextComponent( - text: .plain(NSAttributedString(string: "Join the battle for exclusive gifts.", font: textFont, textColor: secondaryTextColor)), + text: .plain(NSAttributedString(string: strings.Gift_Auction_Info_Description, font: textFont, textColor: secondaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.2 @@ -167,15 +192,14 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { contentSize.height += text.size.height contentSize.height += spacing + 9.0 - //TODO:localize var items: [AnyComponentWithIdentity] = [] items.append( AnyComponentWithIdentity( id: "top", component: AnyComponent(ParagraphComponent( - title: "Top \(auctionGiftsPerRound) Bidders", + title: strings.Gift_Auction_Info_TopBidders_Title(auctionGiftsPerRound), titleColor: textColor, - text: "\(auctionGiftsPerRound) gifts are dropped in 10 rounds to the top \(auctionGiftsPerRound) bidders by bid amount.", + text: strings.Gift_Auction_Info_TopBidders_Text(strings.Gift_Auction_Info_TopBidders_Gifts(auctionGiftsPerRound), strings.Gift_Auction_Info_TopBidders_Rounds(state.rounds), strings.Gift_Auction_Info_TopBidders_Bidders(auctionGiftsPerRound)).string, textColor: secondaryTextColor, accentColor: linkColor, iconName: "Premium/Auction/Drop", @@ -187,9 +211,9 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { AnyComponentWithIdentity( id: "carryover", component: AnyComponent(ParagraphComponent( - title: "Bid Carryover", + title: strings.Gift_Auction_Info_Carryover_Title, titleColor: textColor, - text: "If your bid leaves the top \(auctionGiftsPerRound), it will automatically join the next drop.", + text: strings.Gift_Auction_Info_Carryover_Text("\(auctionGiftsPerRound)").string, textColor: secondaryTextColor, accentColor: linkColor, iconName: "Premium/Auction/NextDrop", @@ -201,9 +225,9 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { AnyComponentWithIdentity( id: "missed", component: AnyComponent(ParagraphComponent( - title: "Missed Bidders", + title: strings.Gift_Auction_Info_Missed_Title, titleColor: textColor, - text: "If your bid doesn't win after the final drop, your Stars will be fully refunded.", + text: strings.Gift_Auction_Info_Missed_Text, textColor: secondaryTextColor, accentColor: linkColor, iconName: "Premium/Auction/Refund", @@ -251,16 +275,15 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { var buttonTitle: [AnyComponentWithIdentity] = [] - let playButtonAnimation = ActionSlot() buttonTitle.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(LottieComponent( content: LottieComponent.AppBundleContent(name: "anim_ok"), color: theme.list.itemCheckColors.foregroundColor, startingPosition: .begin, size: CGSize(width: 28.0, height: 28.0), - playOnce: playButtonAnimation + playOnce: state.playButtonAnimation )))) buttonTitle.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(ButtonTextContentComponent( - text: "Understood", + text: strings.Gift_Auction_Info_Understood, badge: 0, textColor: theme.list.itemCheckColors.foregroundColor, badgeBackground: theme.list.itemCheckColors.foregroundColor, @@ -303,6 +326,8 @@ private final class GiftAuctionInfoSheetContent: CombinedComponent { contentSize.height += 30.0 + state.playAnimationIfNeeded() + return contentSize } } @@ -313,13 +338,16 @@ final class GiftAuctionInfoSheetComponent: CombinedComponent { let context: AccountContext let gift: StarGift + let auctionContext: GiftAuctionContext init( context: AccountContext, - gift: StarGift + gift: StarGift, + auctionContext: GiftAuctionContext ) { self.context = context self.gift = gift + self.auctionContext = auctionContext } static func ==(lhs: GiftAuctionInfoSheetComponent, rhs: GiftAuctionInfoSheetComponent) -> Bool { @@ -347,6 +375,7 @@ final class GiftAuctionInfoSheetComponent: CombinedComponent { content: AnyComponent(GiftAuctionInfoSheetContent( context: context.component.context, gift: context.component.gift, + auctionContext: context.component.auctionContext, animateOut: animateOut, getController: controller )), @@ -422,23 +451,22 @@ final class GiftAuctionInfoSheetComponent: CombinedComponent { public final class GiftAuctionInfoScreen: ViewControllerComponentContainer { private let context: AccountContext - private let gift: StarGift fileprivate let completion: (() -> Void)? public init( context: AccountContext, - gift: StarGift, + auctionContext: GiftAuctionContext, completion: (() -> Void)? ) { self.context = context - self.gift = gift self.completion = completion super.init( context: context, component: GiftAuctionInfoSheetComponent( context: context, - gift: gift + gift: auctionContext.gift, + auctionContext: auctionContext ), navigationBarAppearance: .none, statusBarStyle: .ignore, diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift index 87bdc12d95..9eba93798c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift @@ -291,6 +291,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { let context = self.context let gift = self.auctionContext.gift + let auctionContext = self.auctionContext let presentationData = context.sharedContext.currentPresentationData.with { $0 } var link = "" @@ -300,12 +301,14 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_Auction_Context_About, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, f in - f(.default) - - let infoController = context.sharedContext.makeGiftAuctionInfoScreen(context: context, gift: gift, completion: nil) - controller?.push(infoController) - }))) + if let auctionState = self.auctionState, case .ongoing = auctionState.auctionState { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_Auction_Context_About, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, f in + f(.default) + + let infoController = context.sharedContext.makeGiftAuctionInfoScreen(context: context, auctionContext: auctionContext, completion: nil) + controller?.push(infoController) + }))) + } items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_Auction_Context_CopyLink, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { [weak controller] c, f in f(.default) @@ -599,7 +602,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { let controller = component.context.sharedContext.makeGiftAuctionInfoScreen( context: component.context, - gift: component.auctionContext.gift, + auctionContext: component.auctionContext, completion: nil ) environment.controller()?.push(controller) @@ -636,10 +639,15 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { if state.giftAuctionAcquiredGifts.count > 0, case let .generic(gift) = component.auctionContext.gift { originY += 5.0 - let text = strings.Gift_Auction_ItemsBought(Int32(state.giftAuctionAcquiredGifts.count)) let acquiredButton = acquiredButton.update( component: PlainButtonComponent(content: AnyComponent( HStack([ + AnyComponentWithIdentity(id: "count", component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(Int32(state.giftAuctionAcquiredGifts.count), dateTimeFormat.groupingSeparator), font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor))) + )), + AnyComponentWithIdentity(id: "spacing", component: AnyComponent( + Rectangle(color: .clear, width: 8.0, height: 1.0) + )), AnyComponentWithIdentity(id: "icon", component: AnyComponent( GiftItemComponent( context: component.context, @@ -647,16 +655,16 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { strings: strings, peer: nil, subject: .starGift(gift: gift, price: ""), - mode: .tableIcon + mode: .buttonIcon ) )), AnyComponentWithIdentity(id: "text", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: " \(strings.Gift_Auction_ItemsBought(Int32(state.giftAuctionAcquiredGifts.count)))", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor))) )), - AnyComponentWithIdentity(id: "chevron", component: AnyComponent( - BundleIconComponent(name: "Settings/TextArrowRight", tintColor: environment.theme.actionSheet.controlAccentColor) + AnyComponentWithIdentity(id: "arrow", component: AnyComponent( + BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor) )) - ], spacing: 6.0) + ], spacing: 0.0) ), action: { [weak state] in guard let state else { return diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 9589404cbf..a3ba527161 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3842,8 +3842,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return controller } - public func makeGiftAuctionInfoScreen(context: AccountContext, gift: StarGift, completion: (() -> Void)?) -> ViewController { - return GiftAuctionInfoScreen(context: context, gift: gift, completion: completion) + public func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController { + return GiftAuctionInfoScreen(context: context, auctionContext: auctionContext, completion: completion) } public func makeGiftAuctionBidScreen(context: AccountContext, auctionContext: GiftAuctionContext) -> ViewController { From bdcc80e649f1a2af118c81390ca02ee82e315f62 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Nov 2025 18:20:38 +0400 Subject: [PATCH 2/4] Various improvements --- .../Sources/TelegramBaseController.swift | 2 +- .../Payments/StarGiftsAuctions.swift | 57 ++++++++++++++++--- .../Sources/GiftAuctionBidScreen.swift | 14 ++--- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 5c7dbc2da9..4e5390fe96 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -553,7 +553,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { guard let self else { return } - if !"".isEmpty, self.giftAuctionStates.count == 1, let gift = self.giftAuctionStates.first?.gift, case let .generic(gift) = gift { + if self.giftAuctionStates.count == 1, let gift = self.giftAuctionStates.first?.gift, case let .generic(gift) = gift { if let giftAuctionsManager = self.context.giftAuctionsManager { let _ = (giftAuctionsManager.auctionContext(for: .giftId(gift.id)) |> deliverOnMainQueue).start(next: { [weak self] auction in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift index 1a7ffbdee5..a032c64bc5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift @@ -448,17 +448,60 @@ public class GiftAuctionsManager { } public extension GiftAuctionContext.State { - var place: Int32? { - guard case let .ongoing(_, _, _, _, bidLevels, _, _, _, _, _) = self.auctionState, let myBid = self.myState.bidAmount, let myBidDate = self.myState.bidDate else { + func getPlace(myBid: Int64?, myBidDate: Int32?) -> Int32? { + guard case let .ongoing(_, _, _, _, bidLevels, _, _, _, _, _) = self.auctionState else { return nil } - var place: Int32 = 1 - for level in bidLevels { - if myBid < level.amount || (myBid == level.amount && myBidDate > level.date) { - place = level.position + 1 + guard let myBid = myBid ?? self.myState.bidAmount else { + return nil + } + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + let myBidDate = self.myState.bidDate ?? currentTime + + let levels = bidLevels + guard !levels.isEmpty else { + return 1 + } + + func isWorse(than level: GiftAuctionContext.State.BidLevel) -> Bool { + if myBid < level.amount { + return true + } + if myBid == level.amount, myBidDate > level.date { + return true + } + return false + } + + var lowerIndex: Int = -1 + for (i, level) in levels.enumerated() { + if isWorse(than: level) { + lowerIndex = i + } else { + break } } - return place + if lowerIndex == -1 { + return 1 + } + + let lowerPosition = levels[lowerIndex].position + let nextPosition: Int32 + let nextIndex = lowerIndex + 1 + if nextIndex < levels.count { + nextPosition = levels[nextIndex].position + } else { + nextPosition = lowerPosition + } + if nextPosition == lowerPosition + 1 { + return lowerPosition + 1 + } else { + return nextPosition + } + } + + var place: Int32? { + return self.getPlace(myBid: nil, myBidDate: nil) } var startDate: Int32 { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift index fb4109cb2f..7545f87984 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift @@ -1781,6 +1781,8 @@ private final class GiftAuctionBidScreenComponent: Component { self.backgroundLayer.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor.cgColor } + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize)) var contentHeight: CGFloat = 0.0 @@ -2055,7 +2057,6 @@ private final class GiftAuctionBidScreenComponent: Component { minBidAnimatedItems.append(AnimatedTextComponent.Item(id: "static", content: .text(minBidString))) } - let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let dropTimeout = max(0, nextDropDate - currentTime) let minutes = Int(dropTimeout / 60) @@ -2235,19 +2236,16 @@ private final class GiftAuctionBidScreenComponent: Component { var topBidsComponents: [(EnginePeer.Id, AnyComponent)] = [] if let giftAuctionState = self.giftAuctionState, case let .ongoing(_, _, _, _, bidLevels, topBidders, _, _, _, _) = giftAuctionState.auctionState { - if var myBidAmount = giftAuctionState.myState.bidAmount, let myBidDate = giftAuctionState.myState.bidDate, let peer = self.peersMap[component.context.account.peerId] { + if var myBidAmount = giftAuctionState.myState.bidAmount, var myBidDate = giftAuctionState.myState.bidDate, let peer = self.peersMap[component.context.account.peerId] { var place: Int32 = 1 var isBiddingUp = false if self.amount.realValue > myBidAmount { myBidAmount = Int64(self.amount.realValue) + myBidDate = currentTime isBiddingUp = true } - for level in bidLevels { - if myBidAmount < level.amount || (myBidAmount == level.amount && myBidDate > level.date) { - place = level.position + 1 - } - } - + place = giftAuctionState.getPlace(myBid: myBidAmount, myBidDate: myBidDate) ?? 1 + var bidTitle: String var bidTitleColor: UIColor var bidStatus: PeerComponent.Status? From b0f4ca9ad93fd54e4f4097fbbee08c09526f0327 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Nov 2025 18:41:57 +0400 Subject: [PATCH 3/4] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 5 ++++ ...onSequenceCountrySelectionController.swift | 3 +- .../Sources/LocationPickerController.swift | 4 +-- .../LocationPickerControllerNode.swift | 3 +- .../Sources/CameraLiveStreamComponent.swift | 6 ++-- .../Sources/CaptureControlsComponent.swift | 3 +- .../Sources/GiftAuctionBidScreen.swift | 30 +++++++++++++++++-- 7 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index e25e896157..a96e20a07f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15293,3 +15293,8 @@ Error: %8$@"; "Gift.Auction.Info.Missed.Text" = "If your bid doesn't win after the final drop, your Stars will be fully refunded."; "Gift.Auction.Info.Understood" = "Understood"; + +"Location.SendLocation" = "Send Location"; + +"Camera.LiveStream.Change" = "change"; +"Camera.LiveStream.StartLiveStream" = "Start Live Stream"; diff --git a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift index 017cc15f01..f2dfd41c2d 100644 --- a/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift +++ b/submodules/CountrySelectionUI/Sources/AuthorizationSequenceCountrySelectionController.swift @@ -341,8 +341,7 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll self.statusBar.statusBarStyle = theme.rootController.statusBarStyle.style if glass { - //TODO:localize - self.title = "Select Country" + self.title = strings.Login_SelectCountry } else { let navigationContentNode = AuthorizationSequenceCountrySelectionNavigationContentNode(theme: theme, strings: strings, cancel: { [weak self] in self?.dismissed?() diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 1f10085c7b..434b96002e 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -446,9 +446,7 @@ public func storyLocationPickerController( ) -> ViewController { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme) let updatedPresentationData: (PresentationData, Signal) = (presentationData, .single(presentationData)) - let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: { - return nil - }) + let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false) controller.requestController = { _, present in let locationPickerController = LocationPickerController(context: context, updatedPresentationData: updatedPresentationData, mode: .share(peer: nil, selfPeer: nil, hasLiveLocation: false), source: .story, initialLocation: location, completion: { location, queryId, resultId, address, countryCode in completion(location, queryId, resultId, address, countryCode) diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index f07f056ca2..fef407f8b8 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -1533,11 +1533,10 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM foreground: self.presentationData.theme.list.itemCheckColors.foregroundColor, pressedColor: self.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), ) - //TODO:localize var buttonContents: [AnyComponentWithIdentity] = [ AnyComponentWithIdentity( id: AnyHashable("label"), - component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Send Location", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: self.presentationData.strings.Location_SendLocation, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) ) ] diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift index 80a6e982fa..d9d44d6857 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraLiveStreamComponent.swift @@ -413,12 +413,13 @@ public final class StreamAsComponent: Component { }) } + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) let avatarSize = CGSize(width: 32.0, height: 32.0) self.avatarNode.frame = CGRect(origin: .zero, size: avatarSize) if let peer = self.peer { self.avatarNode.setPeer( context: component.context, - theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme, + theme: presentationData.theme, peer: peer, synchronousLoad: true ) @@ -444,12 +445,11 @@ public final class StreamAsComponent: Component { var maxWidth = titleFrame.maxX if !component.isCustomTarget { - //TODO:localize let subtitleSize = self.subtitle.update( transition: .immediate, component: AnyComponent( MultilineTextComponent( - text: .plain(NSAttributedString(string: "change", font: Font.regular(11.0), textColor: UIColor(white: 1.0, alpha: 0.8), paragraphAlignment: .left)) + text: .plain(NSAttributedString(string: presentationData.strings.Camera_LiveStream_Change, font: Font.regular(11.0), textColor: UIColor(white: 1.0, alpha: 0.8), paragraphAlignment: .left)) ) ), environment: {}, diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift index 1ea33c9d0d..d6f15d18cc 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CaptureControlsComponent.swift @@ -252,11 +252,10 @@ private final class ShutterButtonContentComponent: Component { } } - //TODO:localize let labelSize = self.label.update( transition: .immediate, component: AnyComponent( - Text(text: "Start Live Stream", font: Font.semibold(17.0), color: .white) + Text(text: component.strings.Camera_LiveStream_StartLiveStream, font: Font.semibold(17.0), color: .white) ), environment: {}, containerSize: availableSize diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift index 7545f87984..24b78f2d89 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift @@ -29,6 +29,7 @@ import BotPaymentsUI import UndoUI import GiftItemComponent import LottieComponent +import EdgeEffect private final class BadgeComponent: Component { let theme: PresentationTheme @@ -1039,6 +1040,9 @@ private final class GiftAuctionBidScreenComponent: Component { private let scrollContentView: UIView private let hierarchyTrackingNode: HierarchyTrackingNode + private let topEdgeEffectView: EdgeEffectView + private let bottomEdgeEffectView: EdgeEffectView + private var balanceOverlay = ComponentView() private let backgroundHandleView: UIImageView @@ -1115,6 +1119,13 @@ private final class GiftAuctionBidScreenComponent: Component { self.hierarchyTrackingNode = HierarchyTrackingNode() + self.topEdgeEffectView = EdgeEffectView() + self.topEdgeEffectView.clipsToBounds = true + self.topEdgeEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + self.topEdgeEffectView.layer.cornerRadius = 40.0 + + self.bottomEdgeEffectView = EdgeEffectView() + super.init(frame: frame) self.addSubview(self.dimView) @@ -2518,9 +2529,24 @@ private final class GiftAuctionBidScreenComponent: Component { containerSize: CGSize(width: availableSize.width - buttonInsets.left - buttonInsets.right, height: 54.0) ) - + let edgeEffectHeight: CGFloat = 80.0 + let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight)) + transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame) + self.topEdgeEffectView.update(content: environment.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition) + if self.topEdgeEffectView.superview == nil { + self.navigationBarContainer.insertSubview(self.topEdgeEffectView, at: 0) + } var bottomPanelHeight = 13.0 + buttonInsets.bottom + actionButtonSize.height + + let bottomEdgeEffectHeight: CGFloat = bottomPanelHeight + let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomEdgeEffectHeight), size: CGSize(width: availableSize.width, height: bottomEdgeEffectHeight)) + transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) + self.bottomEdgeEffectView.update(content: environment.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: transition) + if self.bottomEdgeEffectView.superview == nil { + self.containerView.addSubview(self.bottomEdgeEffectView) + } + let actionButtonFrame = CGRect(origin: CGPoint(x: buttonInsets.left, y: availableSize.height - buttonInsets.bottom - actionButtonSize.height), size: actionButtonSize) bottomPanelHeight -= 1.0 if let actionButtonView = actionButton.view { @@ -2539,7 +2565,7 @@ private final class GiftAuctionBidScreenComponent: Component { let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset) - self.scrollContentClippingView.layer.cornerRadius = 10.0 + self.scrollContentClippingView.layer.cornerRadius = 38.0 self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, containerCornerRadius: environment.deviceMetrics.screenCornerRadius, bottomInset: environment.safeInsets.bottom, topInset: topInset) From 1be0aaef7cc19f910f3928668e3acc80f2d4b59e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Nov 2025 19:43:42 +0400 Subject: [PATCH 4/4] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 1 + .../Payments/StarGiftsAuctions.swift | 2 +- .../Sources/BadgeLabelView.swift | 1 + .../Sources/GiftAuctionBidScreen.swift | 49 +++++++++++++++---- .../Sources/GiftViewScreen.swift | 29 +++++++++-- 5 files changed, 69 insertions(+), 13 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index a96e20a07f..b64080c9e6 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15221,6 +15221,7 @@ Error: %8$@"; "Gift.AuctionBid.AddMoreStars.Stars_1" = "%@ Star"; "Gift.AuctionBid.AddMoreStars.Stars_any" = "%@ Stars"; "Gift.AuctionBid.Top" = "TOP %@"; +"Gift.AuctionBid.Custom" = "Custom"; "Gift.Auction.Context.About" = "About"; "Gift.Auction.Context.CopyLink" = "Copy Link"; diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift index a032c64bc5..c4bf54f9a3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift @@ -350,7 +350,7 @@ public class GiftAuctionsManager { for (giftId, update) in updates { if let auctionContext = self.auctionContexts[giftId] { auctionContext.updateAuctionState(update) - } else { + } else if case .ongoing = update { reload = true break } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/BadgeLabelView.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/BadgeLabelView.swift index 6e24d18396..de97a6580f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/BadgeLabelView.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/BadgeLabelView.swift @@ -98,6 +98,7 @@ final class BadgeLabelView: UIView { } } + func update(value: String, transition: ComponentTransition) -> CGSize { if value.contains(" ") { for (_, view) in self.itemViews { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift index 24b78f2d89..474aded7b7 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift @@ -952,7 +952,7 @@ private final class GiftAuctionBidScreenComponent: Component { private static func makeSliderSteps(minRealValue: Int, maxRealValue: Int, isLogarithmic: Bool) -> [Int] { if isLogarithmic { - var sliderSteps: [Int] = [1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 10_000, 20_000, 30_000] + var sliderSteps: [Int] = [1, 10, 50, 100, 500, 1_000, 2_000, 5_000, 7_500, 15_000, 20_000, 30_000, 40_000, 50_000] sliderSteps.removeAll(where: { $0 <= minRealValue }) sliderSteps.insert(minRealValue, at: 0) sliderSteps.removeAll(where: { $0 >= maxRealValue }) @@ -1028,6 +1028,10 @@ private final class GiftAuctionBidScreenComponent: Component { func withMinAllowedRealValue(_ minAllowedRealValue: Int) -> Amount { return Amount(realValue: self.realValue, minRealValue: self.minRealValue, minAllowedRealValue: minAllowedRealValue, maxRealValue: self.maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) } + + func withMaxRealValue(_ maxRealValue: Int) -> Amount { + return Amount(realValue: self.realValue, minRealValue: self.minRealValue, minAllowedRealValue: self.minAllowedRealValue, maxRealValue: maxRealValue, maxSliderValue: self.maxSliderValue, isLogarithmic: self.isLogarithmic) + } } final class View: UIView, UIScrollViewDelegate { @@ -1369,13 +1373,12 @@ private final class GiftAuctionBidScreenComponent: Component { } private var isLoading = false - private func placeBid() { + private func commitBid(value: Int64) { guard let component = self.component, case let .generic(gift) = component.gift, let controller = self.environment?.controller() else { return } var isUpdate = false - let value = Int64(self.amount.realValue) if let myBidAmount = self.giftAuctionState?.myState.bidAmount { isUpdate = true if value == myBidAmount { @@ -1447,10 +1450,19 @@ private final class GiftAuctionBidScreenComponent: Component { return } self.isLoading = false + + let newMaxValue = Int(Double(value) * 1.5) + var updatedAmount = self.amount.withMinAllowedRealValue(Int(value)) + if newMaxValue > self.amount.maxRealValue { + updatedAmount = updatedAmount.withMaxRealValue(newMaxValue) + } + self.amount = updatedAmount self.state?.updated() - self.amount = self.amount.withMinAllowedRealValue(Int(value)) - + if !isUpdate { + component.auctionContext.load() + } + let title = isUpdate ? presentationData.strings.Gift_AuctionBid_Increased_Title : presentationData.strings.Gift_AuctionBid_Placed_Title let text = isUpdate ? presentationData.strings.Gift_AuctionBid_Increased_Text("\(giftsPerRounds)").string : presentationData.strings.Gift_AuctionBid_Placed_Text("\(giftsPerRounds)").string @@ -1737,8 +1749,12 @@ private final class GiftAuctionBidScreenComponent: Component { peerIds.append(context.account.peerId) var minBidAmount: Int64 = 100 - if case let .ongoing(_, _, _, auctionMinBidAmount, _, _, _, _, _, _) = state?.auctionState { + var maxBidAmount: Int64 = 50000 + if case let .ongoing(_, _, _, auctionMinBidAmount, bidLevels, _, _, _, _, _) = state?.auctionState { minBidAmount = auctionMinBidAmount + if let firstLevel = bidLevels.first(where: { $0.position == 1 }) { + maxBidAmount = max(maxBidAmount, Int64(Double(firstLevel.amount) * 1.5)) + } } var currentValue = max(Int(minBidAmount), 100) if let myBidAmount = state?.myState.bidAmount { @@ -1750,7 +1766,7 @@ private final class GiftAuctionBidScreenComponent: Component { minAllowedRealValue = myBidAmount } - self.amount = Amount(realValue: currentValue, minRealValue: Int(minBidAmount), minAllowedRealValue: Int(minAllowedRealValue), maxRealValue: 30000, maxSliderValue: 999, isLogarithmic: true) + self.amount = Amount(realValue: currentValue, minRealValue: Int(minBidAmount), minAllowedRealValue: Int(minAllowedRealValue), maxRealValue: Int(maxBidAmount), maxSliderValue: 999, isLogarithmic: true) transition = .immediate } @@ -2521,8 +2537,7 @@ private final class GiftAuctionBidScreenComponent: Component { return } - - self.placeBid() + self.commitBid(value: Int64(self.amount.realValue)) } )), environment: {}, @@ -2666,10 +2681,26 @@ public class GiftAuctionBidScreen: ViewControllerComponentContainer { } } + fileprivate func dismissAllTooltips() { + self.window?.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismiss() + } + }) + self.forEachController({ controller in + if let controller = controller as? UndoOverlayController { + controller.dismiss() + } + return true + }) + } + override public func dismiss(completion: (() -> Void)? = nil) { if !self.isDismissed { self.isDismissed = true + self.dismissAllTooltips() + if let componentView = self.node.hostView.componentView as? GiftAuctionBidScreenComponent.View { componentView.animateOut(completion: { [weak self] in completion?() diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index f10ececef5..9306999fb1 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -292,9 +292,7 @@ private final class GiftViewSheetContent: CombinedComponent { } })) - if arguments.upgradeStars == nil { - self.fetchUpgradeForm() - } + self.fetchUpgradeForm() } } @@ -2139,6 +2137,31 @@ private final class GiftViewSheetContent: CombinedComponent { } } else { self.scheduledUpgradeCommit = true + + self.inProgress = true + self.updated() + + Queue.mainQueue().after(5.0, { + if self.scheduledUpgradeCommit { + self.scheduledUpgradeCommit = false + self.inProgress = false + self.updated() + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let alertController = textAlertController( + context: self.context, + title: nil, + text: presentationData.strings.Login_UnknownError, + actions: [ + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {}) + ], + parseMarkdown: true + ) + if let controller = self.getController() { + controller.present(alertController, in: .window(.root)) + } + } + }) } }