From 82addaa43478924db546edd92e499bb95cbc8a50 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 15 Nov 2025 01:14:50 +0400 Subject: [PATCH] Various improvements --- .../Payments/StarGiftsAuctions.swift | 11 ++- .../Sources/GiftAuctionBidScreen.swift | 98 +++++++++++-------- .../GiftAuctionCustomBidController.swift | 4 +- .../Sources/GiftAuctionViewScreen.swift | 50 +++++----- .../Sources/SliderComponent.swift | 6 +- 5 files changed, 100 insertions(+), 69 deletions(-) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift index 776724d3ea..98bd792c9a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGiftsAuctions.swift @@ -60,7 +60,7 @@ public final class GiftAuctionContext { } public struct MyState: Equatable { - public var isOutbid: Bool + public var isReturned: Bool public var bidAmount: Int64? public var bidDate: Int32? public var minBidAmount: Int64? @@ -132,7 +132,7 @@ public final class GiftAuctionContext { private var currentVersion: Int32 { var currentVersion: Int32 = 0 - if case let .ongoing(version, _, _, _, _, _, _, _, _, _) = self.auctionState { + if case let .ongoing(version, _, _, _, _, _, _, _, _, _) = self.auctionState { currentVersion = version } return currentVersion @@ -180,7 +180,10 @@ public final class GiftAuctionContext { } func updateAuctionState(_ auctionState: GiftAuctionContext.State.AuctionState) { - self.auctionState = auctionState + if case let .ongoing(version, _, _, _, _, _, _, _, _, _) = auctionState, version < self.currentVersion { + } else { + self.auctionState = auctionState + } self.pushState() } @@ -255,7 +258,7 @@ extension GiftAuctionContext.State.MyState { init(apiAuctionUserState: Api.StarGiftAuctionUserState) { switch apiAuctionUserState { case let .starGiftAuctionUserState(flags, bidAmount, bidDate, minBidAmount, bidPeerId, acquiredCount): - self.isOutbid = (flags & (1 << 1)) != 0 + self.isReturned = (flags & (1 << 1)) != 0 self.bidAmount = bidAmount self.bidDate = bidDate self.minBidAmount = minBidAmount diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift index 25de37a809..8899093020 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionBidScreen.swift @@ -362,13 +362,13 @@ private final class BadgeComponent: Component { private final class PeerPlaceComponent: Component { let theme: PresentationTheme let color: UIColor - let place: Int32 + let place: Int32? let groupingSeparator: String init( theme: PresentationTheme, color: UIColor, - place: Int32, + place: Int32?, groupingSeparator: String ) { self.theme = theme @@ -414,9 +414,9 @@ private final class PeerPlaceComponent: Component { let textColor: UIColor let backgroundColors: [UIColor]? - if component.place < 4 { + if let place = component.place, place < 4 { textColor = .white - switch component.place { + switch place { case 1: backgroundColors = [UIColor(rgb: 0xffa901), UIColor(rgb: 0xffcd3b)] case 2: @@ -446,11 +446,21 @@ private final class PeerPlaceComponent: Component { let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - backgroundSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - backgroundSize.height) * 0.5)), size: backgroundSize) self.background.frame = backgroundFrame + var placeString: String + if let place = component.place { + placeString = presentationStringsFormattedNumber(place, component.groupingSeparator) + if place >= 100 { + placeString = "\(placeString)+" + } + } else { + placeString = "–" + } + let labelSize = self.label.update( transition: .immediate, - component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(component.place, component.groupingSeparator), font: Font.regular(17.0), textColor: textColor)))), + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: placeString, font: Font.with(size: 17.0, traits: .monospacedNumbers), textColor: textColor)))), environment: {}, - containerSize: CGSize(width: 40.0, height: 40.0) + containerSize: CGSize(width: 60.0, height: 40.0) ) let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - labelSize.width) * 0.5), y: floorToScreenPixels((availableSize.height - labelSize.height) * 0.5)), size: labelSize) if let labelView = self.label.view { @@ -477,6 +487,7 @@ private final class PeerComponent: Component { enum Status { case winning case outbid + case returned } let context: AccountContext @@ -596,7 +607,7 @@ private final class PeerComponent: Component { switch component.status { case .winning: color = component.theme.list.itemDisclosureActions.constructive.fillColor - case .outbid: + case .outbid, .returned: color = component.theme.list.itemDestructiveColor default: break @@ -606,7 +617,7 @@ private final class PeerComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let placeSize = self.place.update( transition: .immediate, - component: AnyComponent(PeerPlaceComponent(theme: component.theme, color: color, place: component.place, groupingSeparator: presentationData.dateTimeFormat.groupingSeparator)), + component: AnyComponent(PeerPlaceComponent(theme: component.theme, color: color, place: component.status == .returned ? nil : component.place, groupingSeparator: presentationData.dateTimeFormat.groupingSeparator)), environment: {}, containerSize: CGSize(width: 40.0, height: 40.0) ) @@ -1309,17 +1320,10 @@ private final class GiftAuctionBidScreenComponent: Component { } } - private var lastAcquiredGiftsReloadTime: Double? - private func reloadAcquiredGifts() { + private func loadAcquiredGifts() { guard let component = self.component, case let .generic(gift) = component.gift else { return } - - let currentTime = CFAbsoluteTimeGetCurrent() - if let lastAcquiredGiftsReloadTime = self.lastAcquiredGiftsReloadTime, currentTime < lastAcquiredGiftsReloadTime + 5 { - return - } - self.lastAcquiredGiftsReloadTime = currentTime self.giftAuctionAcquiredGiftsDisposable.set((component.context.engine.payments.getGiftAuctionAcquiredGifts(giftId: gift.id) |> deliverOnMainQueue).startStrict(next: { [weak self] acquiredGifts in guard let self else { @@ -1557,14 +1561,7 @@ private final class GiftAuctionBidScreenComponent: Component { let signal = BotCheckoutController.InputData.fetch(context: component.context, source: source) |> `catch` { error -> Signal in - switch error { - case .disallowedStarGifts: - return .fail(.disallowedStarGift) - case .starGiftsUserLimit: - return .fail(.starGiftUserLimit) - default: - return .fail(.generic) - } + return .fail(.generic) } |> mapToSignal { inputData -> Signal in return component.context.engine.payments.sendStarsPaymentForm(formId: inputData.form.id, source: source) @@ -1612,6 +1609,18 @@ private final class GiftAuctionBidScreenComponent: Component { ) component.context.starsContext?.load(force: true) + }, error: { [weak self] _ in + guard let self else { + return + } + + self.component?.context.starsContext?.load(force: true) + self.resetSliderValue() + + Queue.mainQueue().after(0.1) { + self.isLoading = false + self.state?.updated() + } }) } @@ -1926,13 +1935,15 @@ private final class GiftAuctionBidScreenComponent: Component { } let context = component.context + let auctionContext = component.auctionContext self.giftAuctionDisposable = (component.auctionContext.state - |> deliverOnMainQueue).start(next: { [weak self] state in + |> deliverOnMainQueue).start(next: { [weak self] auctionState in guard let self else { return } let isFirstTime = self.giftAuctionState == nil - self.giftAuctionState = state + let previousState = self.giftAuctionState + self.giftAuctionState = auctionState var peerIds: [EnginePeer.Id] = [] var transition = ComponentTransition.spring(duration: 0.4) @@ -1964,6 +1975,18 @@ private final class GiftAuctionBidScreenComponent: Component { }) } self.state?.updated(transition: transition) + + if let acquiredCount = auctionState?.myState.acquiredCount, acquiredCount > (previousState?.myState.acquiredCount ?? 0) { + self.loadAcquiredGifts() + } + + if case .finished = auctionState?.auctionState, let controller = self.environment?.controller() { + if let navigationController = controller.navigationController as? NavigationController { + controller.dismiss() + let auctionController = context.sharedContext.makeGiftAuctionViewScreen(context: context, auctionContext: auctionContext, completion: {}) + navigationController.pushViewController(auctionController) + } + } }) self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in @@ -1971,10 +1994,6 @@ private final class GiftAuctionBidScreenComponent: Component { self?.state?.updated() }, queue: Queue.mainQueue()) self.giftAuctionTimer?.start() - - Queue.mainQueue().justDispatch { - self.reloadAcquiredGifts() - } } self.component = component @@ -2224,7 +2243,7 @@ private final class GiftAuctionBidScreenComponent: Component { myBidDate = currentDate isBiddingUp = false } - + place = giftAuctionState.getPlace(myBid: myBidAmount, myBidDate: myBidDate) ?? 1 var bidTitle: String @@ -2233,6 +2252,10 @@ private final class GiftAuctionBidScreenComponent: Component { if isBiddingUp { bidTitleColor = environment.theme.list.itemSecondaryTextColor bidTitle = environment.strings.Gift_AuctionBid_BidPreview + } else if giftAuctionState.myState.isReturned { + bidTitle = environment.strings.Gift_AuctionBid_Outbid + bidTitleColor = environment.theme.list.itemDestructiveColor + bidStatus = .returned } else if place > giftsPerRound { bidTitle = environment.strings.Gift_AuctionBid_Outbid bidTitleColor = environment.theme.list.itemDestructiveColor @@ -2351,12 +2374,6 @@ private final class GiftAuctionBidScreenComponent: Component { let minutes = Int(dropTimeout / 60) let seconds = Int(dropTimeout % 60) - if dropTimeout == 0, place <= giftsPerRound { - Queue.mainQueue().after(1.5, { - self.reloadAcquiredGifts() - }) - } - untilNextDropAnimatedItems.append(AnimatedTextComponent.Item(id: "m", content: .number(minutes, minDigits: 2))) untilNextDropAnimatedItems.append(AnimatedTextComponent.Item(id: "colon", content: .text(":"))) untilNextDropAnimatedItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2))) @@ -2423,7 +2440,8 @@ private final class GiftAuctionBidScreenComponent: Component { contentHeight += perkHeight contentHeight += 24.0 - if let giftAuctionAcquiredGifts = self.giftAuctionAcquiredGifts, giftAuctionAcquiredGifts.count > 0, case let .generic(gift) = component.gift { + let acquiredGiftsCount = self.giftAuctionState?.myState.acquiredCount ?? 0 + if acquiredGiftsCount > 0, case let .generic(gift) = component.gift { var myGiftsTransition = transition let myGiftsSize = self.myGifts.update( transition: .immediate, @@ -2431,7 +2449,7 @@ private final class GiftAuctionBidScreenComponent: Component { PlainButtonComponent(content: AnyComponent( HStack([ AnyComponentWithIdentity(id: "count", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(Int32(giftAuctionAcquiredGifts.count), environment.dateTimeFormat.groupingSeparator), font: Font.regular(17.0), textColor: environment.theme.actionSheet.controlAccentColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(Int32(acquiredGiftsCount), environment.dateTimeFormat.groupingSeparator), font: Font.regular(17.0), textColor: environment.theme.actionSheet.controlAccentColor))) )), AnyComponentWithIdentity(id: "spacing", component: AnyComponent( Rectangle(color: .clear, width: 8.0, height: 1.0) @@ -2447,7 +2465,7 @@ private final class GiftAuctionBidScreenComponent: Component { ) )), AnyComponentWithIdentity(id: "text", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: " \(environment.strings.Gift_Auction_ItemsBought(Int32(giftAuctionAcquiredGifts.count)))", font: Font.regular(17.0), textColor: environment.theme.actionSheet.controlAccentColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: " \(environment.strings.Gift_Auction_ItemsBought(Int32(acquiredGiftsCount)))", font: Font.regular(17.0), textColor: environment.theme.actionSheet.controlAccentColor))) )), AnyComponentWithIdentity(id: "arrow", component: AnyComponent( BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: environment.theme.actionSheet.controlAccentColor) diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionCustomBidController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionCustomBidController.swift index 924a2d214f..d9cf921a9a 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionCustomBidController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionCustomBidController.swift @@ -272,7 +272,9 @@ private final class GiftAuctionCustomBidAlertContentNode: AlertContentNode { if !hadValidLayout { if let amountFieldView = self.amountField.view as? AmountFieldComponent.View { amountFieldView.activateInput() - amountFieldView.selectAll() + Queue.mainQueue().justDispatch { + amountFieldView.selectAll() + } } } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift index 38b480327d..95c6c53d87 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftAuctionViewScreen.swift @@ -65,11 +65,11 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { private let getController: () -> ViewController? private var disposable: Disposable? - private(set) var auctionState: GiftAuctionContext.State? + private(set) var giftAuctionState: GiftAuctionContext.State? private var giftAuctionTimer: SwiftSignalKit.Timer? fileprivate var giftAuctionAcquiredGifts: [GiftAuctionAcquiredGift] = [] - private var giftAuctionAcquiredGiftsDisposable: Disposable? + private var giftAuctionAcquiredGiftsDisposable = MetaDisposable() var cachedStarImage: (UIImage, PresentationTheme)? @@ -90,23 +90,19 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { super.init() self.disposable = (auctionContext.state - |> deliverOnMainQueue).start(next: { [weak self] state in + |> deliverOnMainQueue).start(next: { [weak self] auctionState in guard let self else { return } - self.auctionState = state + let previousState = self.giftAuctionState + self.giftAuctionState = auctionState self.updated() - }) - - self.giftAuctionAcquiredGiftsDisposable = (context.engine.payments.getGiftAuctionAcquiredGifts(giftId: auctionContext.gift.giftId) - |> deliverOnMainQueue).startStrict(next: { [weak self] acquiredGifts in - guard let self else { - return + + if let acquiredCount = auctionState?.myState.acquiredCount, acquiredCount > (previousState?.myState.acquiredCount ?? 0) { + self.loadAcquiredGifts() } - self.giftAuctionAcquiredGifts = acquiredGifts - self.updated(transition: .easeInOut(duration: 0.25)) }) - + self.giftAuctionTimer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in self?.updated() }, queue: Queue.mainQueue()) @@ -115,10 +111,21 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { deinit { self.disposable?.dispose() - self.giftAuctionAcquiredGiftsDisposable?.dispose() + self.giftAuctionAcquiredGiftsDisposable.dispose() self.giftAuctionTimer?.invalidate() } + func loadAcquiredGifts() { + self.giftAuctionAcquiredGiftsDisposable.set((self.context.engine.payments.getGiftAuctionAcquiredGifts(giftId: self.auctionContext.gift.giftId) + |> deliverOnMainQueue).startStrict(next: { [weak self] acquiredGifts in + guard let self else { + return + } + self.giftAuctionAcquiredGifts = acquiredGifts + self.updated(transition: .easeInOut(duration: 0.25)) + })) + } + func showAttributeInfo(tag: Any, text: String) { guard let controller = self.getController() as? GiftAuctionViewScreen else { return @@ -163,9 +170,6 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { self.dismiss(animated: true) controller.completion() - -// let bidController = self.context.sharedContext.makeGiftAuctionBidScreen(context: self.context, toPeerId: self.auctionContext.currentBidPeerId ?? self.toPeerId, auctionContext: self.auctionContext) -// navigationController.pushViewController(bidController) } func openPeer(_ peer: EnginePeer, dismiss: Bool = true) { @@ -302,7 +306,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { var items: [ContextMenuItem] = [] - if let auctionState = self.auctionState, case .ongoing = auctionState.auctionState { + if let auctionState = self.giftAuctionState, 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) @@ -445,7 +449,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { var isEnded = false var tableItems: [TableComponent.Item] = [] - if let auctionState = state.auctionState, case let .generic(gift) = component.auctionContext.gift { + if let auctionState = state.giftAuctionState, case let .generic(gift) = component.auctionContext.gift { endTime = auctionState.endDate if case .finished = auctionState.auctionState { isEnded = true @@ -637,14 +641,16 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { originY += table.size.height + 26.0 var hasAdditionalButtons = false - if state.giftAuctionAcquiredGifts.count > 0, case let .generic(gift) = component.auctionContext.gift { + + let acquiredGiftsCount = state.giftAuctionState?.myState.acquiredCount ?? 0 + if acquiredGiftsCount > 0, case let .generic(gift) = component.auctionContext.gift { originY += 5.0 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))) + MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(Int32(acquiredGiftsCount), 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) @@ -660,7 +666,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent { ) )), AnyComponentWithIdentity(id: "text", component: AnyComponent( - MultilineTextComponent(text: .plain(NSAttributedString(string: " \(strings.Gift_Auction_ItemsBought(Int32(state.giftAuctionAcquiredGifts.count)))", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor))) + MultilineTextComponent(text: .plain(NSAttributedString(string: " \(strings.Gift_Auction_ItemsBought(Int32(acquiredGiftsCount)))", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor))) )), AnyComponentWithIdentity(id: "arrow", component: AnyComponent( BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor) diff --git a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift index 0f557c58de..d9cffb9fe4 100644 --- a/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift +++ b/submodules/TelegramUI/Components/SliderComponent/Sources/SliderComponent.swift @@ -143,8 +143,10 @@ public final class SliderComponent: Component { public func cancelGestures() { if let sliderView = self.sliderView, let gestureRecognizers = sliderView.gestureRecognizers { for gestureRecognizer in gestureRecognizers { - gestureRecognizer.isEnabled = false - gestureRecognizer.isEnabled = true + if gestureRecognizer.isEnabled { + gestureRecognizer.isEnabled = false + gestureRecognizer.isEnabled = true + } } } }