Various improvements

This commit is contained in:
Ilya Laktyushin 2025-12-04 07:56:44 +04:00
parent 4660f67271
commit 2cb0c7f147
12 changed files with 464 additions and 192 deletions

View file

@ -15517,6 +15517,9 @@ Error: %8$@";
"Attachment.FilesSearchPlaceholder" = "Search shared audio";
"Chat.GiftPurchaseOffer.Reject" = "Reject";
"Chat.GiftPurchaseOffer.Accept" = "Accept";
"Chat.GiftPurchaseOffer.RejectConfirmation.Title" = "Reject Offer";
"Chat.GiftPurchaseOffer.RejectConfirmation.Text" = "Are you sure you want to reject the offer from **%@**?";
"Chat.GiftPurchaseOffer.RejectConfirmation.Reject" = "Reject";
"Passkeys.DeleteAlert.Title" = "Delete Passkey?";
"Passkeys.DeleteAlert.Text" = "Once deleted, this passkey can't be used to log in.\n\nDon't forget to remove it from your password manager too.";
@ -15559,13 +15562,13 @@ Error: %8$@";
"Gift.Variants.Backdrops" = "Backdrops";
"Gift.Variants.Symbols" = "Symbols";
"Gift.Variants.CollectionInfo" = "This collection features";
"Gift.Variants.CollectionInfo.Model_1" = "%@ unique model";
"Gift.Variants.CollectionInfo.Model_any" = "%@ unique models";
"Gift.Variants.CollectionInfo.Backdrop_1" = "%@ unique backdrop";
"Gift.Variants.CollectionInfo.Backdrop_any" = "%@ unique backdrops";
"Gift.Variants.CollectionInfo.Symbol_1" = "%@ unique symbol";
"Gift.Variants.CollectionInfo.Symbol_any" = "%@ unique symbols";
"Gift.Variants.CollectionInfo" = "This collection features %@";
"Gift.Variants.CollectionInfo.Model_1" = "**%@** unique model";
"Gift.Variants.CollectionInfo.Model_any" = "**%@** unique models";
"Gift.Variants.CollectionInfo.Backdrop_1" = "**%@** unique backdrop";
"Gift.Variants.CollectionInfo.Backdrop_any" = "**%@** unique backdrops";
"Gift.Variants.CollectionInfo.Symbol_1" = "**%@** unique symbol";
"Gift.Variants.CollectionInfo.Symbol_any" = "**%@** unique symbols";
"Gift.Auction.GiftAuction" = "Upcoming Auction";
"Gift.Auction.UpcomingAuction" = "Upcoming Auction";
@ -15589,6 +15592,34 @@ Error: %8$@";
"Gift.Offer.Duration.Hours_1" = "%@ Hour";
"Gift.Offer.Duration.Hours_any" = "%@ Hours";
"Gift.Offer.Offer" = "Offer";
"Gift.Offer.GiftMinAmountToast.Text" = "You cannot offer less than %@ for this gift";
"Gift.WearPreview.Limited" = "limited";
"Gift.WearPreview.Upgraded" = "upgraded";
"Gift.WearPreview.FreeUpgrade" = "Free\nUpgrade";
"Gift.WearPreview.LearnMore" = "Learn more about wearing Telegram Gifts >";
"Notification.StarGift.Sold" = "sold";
"Notification.StarGiftOffer.Offer" = "**%1$@** offered you **%2$@** for your gift **%3$@**.";
"Notification.StarGiftOffer.OfferYou" = "You offered **%1$@** **%2$@** for their gift **%3$@**.";
"Notification.StarGiftOffer.Offer.Stars_1" = "%@ Star";
"Notification.StarGiftOffer.Offer.Stars_any" = "%@ Stars";
"Notification.StarGiftOffer.Status.Accepted" = "This offer was accepted.";
"Notification.StarGiftOffer.Status.Expired" = "This offer has expired.";
"Notification.StarGiftOffer.Status.Expires" = "This offer expires in %@";
"Notification.StarGiftOffer.Status.Rejected" = "This offer was rejected.";
"Notification.StarGiftOffer.Expiration.Hours_1" = "%@ h";
"Notification.StarGiftOffer.Expiration.Hours_any" = "%@ h";
"Notification.StarGiftOffer.Expiration.Minutes_1" = "%@ m";
"Notification.StarGiftOffer.Expiration.Minutes_any" = "%@ m";
"Notification.StarGiftOffer.Expiration.Delimiter" = "";
"Chat.GiftPurchaseOffer.AcceptConfirmation.Title" = "Confirm Sale";
"Chat.GiftPurchaseOffer.AcceptConfirmation.Text" = "Do you want to sell **%1$@** to %2$@ for **%3$@**? You'll receive **%4$@** after fees.";
"Chat.GiftPurchaseOffer.AcceptConfirmation.Text.Stars_1" = "%@ Star";
"Chat.GiftPurchaseOffer.AcceptConfirmation.Text.Stars_any" = "%@ Stars";
"Chat.GiftPurchaseOffer.AcceptConfirmation.BadValue" = "The value of this gift is **%@** higher than the offer.";
"Chat.GiftPurchaseOffer.AcceptConfirmation.Confirm" = "Confirm Sale";

View file

@ -129,7 +129,7 @@ public class ChatMessageGiftOfferBubbleContentNode: ChatMessageBubbleContentNode
let priceString: String
switch amount.currency {
case .stars:
priceString = "\(amount.amount) Stars"
priceString = item.presentationData.strings.Notification_StarGiftOffer_Offer_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
}
@ -143,41 +143,35 @@ public class ChatMessageGiftOfferBubbleContentNode: ChatMessageBubbleContentNode
giftTitle = ""
}
//TODO:localize
if incoming {
text = "**\(peerName)** offered you **\(priceString)** for your gift **\(giftTitle)**."
text = item.presentationData.strings.Notification_StarGiftOffer_Offer(peerName, priceString, giftTitle).string
} else {
text = "You offered **\(peerName)** **\(priceString)** for their gift **\(giftTitle)**."
text = item.presentationData.strings.Notification_StarGiftOffer_OfferYou(peerName, priceString, giftTitle).string
}
if isAccepted {
additionalText = "This offer was accepted."
additionalText = item.presentationData.strings.Notification_StarGiftOffer_Status_Accepted
} else if isDeclined {
additionalText = "This offer was rejected."
additionalText = item.presentationData.strings.Notification_StarGiftOffer_Status_Rejected
} else if expireDate > currentTimestamp {
func textForTimeout(_ value: Int32) -> String {
if value < 3600 {
let minutes = value / 60
//let seconds = value % 60
//let secondsPadding = seconds < 10 ? "0" : ""
return "\(minutes)m" //\(secondsPadding)\(seconds)"
return item.presentationData.strings.Notification_StarGiftOffer_Expiration_Minutes(minutes)
} else {
let hours = value / 3600
let minutes = (value % 3600) / 60
let minutesPadding = minutes < 10 ? "0" : ""
//let seconds = value % 60
//let secondsPadding = seconds < 10 ? "0" : ""
return "\(hours)h \(minutesPadding)\(minutes)m" //:\(secondsPadding)\(seconds)"
return item.presentationData.strings.Notification_StarGiftOffer_Expiration_Hours(hours) + item.presentationData.strings.Notification_StarGiftOffer_Expiration_Delimiter + item.presentationData.strings.Notification_StarGiftOffer_Expiration_Minutes(minutes)
}
}
let delta = expireDate - currentTimestamp
additionalText = "This offer expires in \(textForTimeout(delta))."
additionalText = item.presentationData.strings.Notification_StarGiftOffer_Status_Expires(textForTimeout(delta)).string
if incoming {
hasActionButtons = true
}
} else {
additionalText = "This offer has expired."
additionalText = item.presentationData.strings.Notification_StarGiftOffer_Status_Expired
}
} else {
text = ""
@ -277,8 +271,6 @@ public class ChatMessageGiftOfferBubbleContentNode: ChatMessageBubbleContentNode
)
if let giftIconView = strongSelf.giftIcon.view {
if giftIconView.superview == nil {
// backgroundView.layer.cornerRadius = 20.0
//backgroundView.clipsToBounds = true
strongSelf.view.addSubview(giftIconView)
}
giftIconView.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY + 17.0), size: iconSize)

View file

@ -601,7 +601,9 @@ public final class GiftItemComponent: Component {
}
case let .preview(attributes, _):
animationOffset = 16.0
explicitAnimationOffset = -4.0
if component.mode != .tableIcon {
explicitAnimationOffset = -4.0
}
for attribute in attributes {
switch attribute {
case let .model(_, file, _):

View file

@ -1727,7 +1727,7 @@ private final class GiftSetupScreenComponent: Component {
inactiveValue: "",
inactiveTitleColor: theme.list.itemSecondaryTextColor,
activeTitle: "",
activeValue: environment.strings.Gift_Send_Sold(sold),
activeValue: sold > 0 ? environment.strings.Gift_Send_Sold(sold) : "",
activeTitleColor: .white,
badgeText: "",
badgePosition: position,
@ -1830,22 +1830,30 @@ private final class GiftSetupScreenComponent: Component {
var buttonTitleItems: [AnyComponentWithIdentity<Empty>] = []
if let _ = self.giftAuction {
let buttonAttributedString = NSMutableAttributedString(string: environment.strings.Gift_Setup_PlaceBid, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
buttonTitleItems.append(AnyComponentWithIdentity(id: "bid", component: AnyComponent(
MultilineTextComponent(text: .plain(buttonAttributedString))
)))
var isUpcoming = false
if let giftAuctionState = self.giftAuctionState {
switch giftAuctionState.auctionState {
case let .ongoing(_, _, endTime, _, _, _, _, _, _, _, _, _):
case let .ongoing(_, startTime, endTime, _, _, _, _, _, _, _, _, _):
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
let endTimeout = max(0, endTime - currentTime)
let endTimeout: Int32
if currentTime < startTime {
endTimeout = max(0, startTime - currentTime)
isUpcoming = true
} else {
endTimeout = max(0, endTime - currentTime)
}
let hours = Int(endTimeout / 3600)
let minutes = Int((endTimeout % 3600) / 60)
let seconds = Int(endTimeout % 60)
let rawString = hours > 0 ? environment.strings.Gift_Auction_TimeLeftHours : environment.strings.Gift_Auction_TimeLeftMinutes
let rawString: String
if isUpcoming {
rawString = hours > 0 ? environment.strings.Gift_Auction_StartsInHours : environment.strings.Gift_Auction_StartsInMinutes
} else {
rawString = hours > 0 ? environment.strings.Gift_Auction_TimeLeftHours : environment.strings.Gift_Auction_TimeLeftMinutes
}
var buttonAnimatedTitleItems: [AnimatedTextComponent.Item] = []
var startIndex = rawString.startIndex
while true {
@ -1885,6 +1893,10 @@ private final class GiftSetupScreenComponent: Component {
buttonIsEnabled = false
}
}
let buttonAttributedString = NSMutableAttributedString(string: isUpcoming ? environment.strings.Gift_Auction_EarlyBid : environment.strings.Gift_Setup_PlaceBid, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
buttonTitleItems.insert(AnyComponentWithIdentity(id: "bid", component: AnyComponent(
MultilineTextComponent(text: .plain(buttonAttributedString))
)), at: 0)
} else {
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 {

View file

@ -2493,7 +2493,19 @@ private final class GiftAuctionBidScreenComponent: Component {
untilNextRoundAnimatedItems.append(AnimatedTextComponent.Item(id: "s", content: .number(seconds, minDigits: 2)))
}
dropsLeftAnimatedItems = [AnimatedTextComponent.Item(id: "drops", content: .number(Int(dropsLeft), minDigits: 1))]
if dropsLeft >= 10000 {
var compactString = compactNumericCountString(Int(dropsLeft), decimalSeparator: ".", showDecimalPart: false)
let suffix = String(compactString.suffix(1))
compactString.removeLast()
if let value = Int(compactString) {
dropsLeftAnimatedItems = [
AnimatedTextComponent.Item(id: "drops", content: .number(value, minDigits: 1)),
AnimatedTextComponent.Item(id: "suffix", content: .text(suffix))
]
}
} else {
dropsLeftAnimatedItems = [AnimatedTextComponent.Item(id: "drops", content: .number(Int(dropsLeft), minDigits: 1))]
}
}
auctionStats.append((

View file

@ -78,6 +78,8 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
private(set) var previewBackdropIndex: Int = -1
private(set) var previewSymbolIndex: Int = -1
private var disposables = DisposableSet()
private var fetchedFiles = Set<Int64>()
private(set) var previewModels: [StarGift.UniqueGift.Attribute] = []
private(set) var previewBackdrops: [StarGift.UniqueGift.Attribute] = []
private(set) var previewSymbols: [StarGift.UniqueGift.Attribute] = []
@ -154,6 +156,16 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}).shuffled().prefix(5))
self.previewSymbols = randomSymbols
for case let .model(_, file, _) in self.previewModels where !self.fetchedFiles.contains(file.fileId.id) {
self.disposables.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
self.fetchedFiles.insert(file.fileId.id)
}
for case let .pattern(_, file, _) in self.previewSymbols where !self.fetchedFiles.contains(file.fileId.id) {
self.disposables.add(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
self.fetchedFiles.insert(file.fileId.id)
}
self.updated()
})
@ -169,6 +181,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
deinit {
self.disposable?.dispose()
self.disposables.dispose()
self.giftAuctionAcquiredGiftsDisposable.dispose()
self.giftAuctionTimer?.invalidate()
self.previewTimer?.invalidate()
@ -509,78 +522,6 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
var buttonColor: UIColor = .white.withAlphaComponent(0.1)
var secondaryTextColor: UIColor = .white.withAlphaComponent(0.4)
if let genericGift {
var attributes: [StarGift.UniqueGift.Attribute] = []
if state.previewModelIndex == -1 {
attributes.append(.model(name: "", file: genericGift.file, rarity: 0))
if let background = genericGift.background {
attributes.append(.backdrop(name: "", id: 0, innerColor: background.centerColor, outerColor: background.edgeColor, patternColor: 0, textColor: 0, rarity: 0))
}
} else if !state.previewModels.isEmpty {
attributes.append(state.previewModels[state.previewModelIndex])
if !state.previewBackdrops.isEmpty {
attributes.append(state.previewBackdrops[state.previewBackdropIndex])
}
if !state.previewSymbols.isEmpty {
attributes.append(state.previewSymbols[state.previewSymbolIndex])
}
}
if let backdropAttribute = attributes.first(where: { attribute in
if case .backdrop = attribute {
return true
} else {
return false
}
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
let topColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04)
let bottomColor = UIColor(rgb: UInt32(bitPattern: outerColor)).withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8).withMultipliedBrightnessBy(1.25)
secondaryTextColor = topColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.5)
}
let header = header.update(
component: GiftCompositionComponent(
context: component.context,
theme: theme,
subject: .preview(attributes),
animationOffset: CGPoint(x: 0.0, y: -4.0),
animationScale: nil,
displayAnimationStars: false,
revealedAttributes: Set(),
externalState: giftCompositionExternalState,
requestUpdate: { [weak state] transition in
state?.updated(transition: transition)
}
),
availableSize: CGSize(width: context.availableSize.width, height: 264.0),
transition: context.transition
)
context.add(header
.position(CGPoint(x: context.availableSize.width / 2.0, y: header.size.height * 0.5))
)
originY += header.size.height
originY += 16.0
}
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: titleString,
font: Font.bold(20.0),
textColor: .white,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: 209.0))
)
var descriptionText: String = ""
@ -771,14 +712,89 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
}
}
}
if let genericGift {
var attributes: [StarGift.UniqueGift.Attribute] = []
if state.previewModelIndex == -1 {
attributes.append(.model(name: "", file: genericGift.file, rarity: 0))
if let background = genericGift.background {
attributes.append(.backdrop(name: "", id: 0, innerColor: background.centerColor, outerColor: background.edgeColor, patternColor: 0, textColor: 0, rarity: 0))
}
} else if !state.previewModels.isEmpty {
attributes.append(state.previewModels[state.previewModelIndex])
if !state.previewBackdrops.isEmpty {
attributes.append(state.previewBackdrops[state.previewBackdropIndex])
}
if !state.previewSymbols.isEmpty {
attributes.append(state.previewSymbols[state.previewSymbolIndex])
}
}
if let backdropAttribute = attributes.first(where: { attribute in
if case .backdrop = attribute {
return true
} else {
return false
}
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
let topColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04)
let bottomColor = UIColor(rgb: UInt32(bitPattern: outerColor)).withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89)
buttonColor = topColor.mixedWith(bottomColor, alpha: 0.8).withMultipliedBrightnessBy(1.25)
secondaryTextColor = topColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.5)
}
let headerHeight: CGFloat = 264.0
let header = header.update(
component: GiftCompositionComponent(
context: component.context,
theme: theme,
subject: .preview(attributes),
animationOffset: CGPoint(x: 0.0, y: -4.0),
animationScale: nil,
displayAnimationStars: false,
revealedAttributes: Set(),
externalState: giftCompositionExternalState,
requestUpdate: { [weak state] transition in
state?.updated(transition: transition)
}
),
availableSize: CGSize(width: context.availableSize.width, height: headerHeight),
transition: context.transition
)
context.add(header
.position(CGPoint(x: context.availableSize.width / 2.0, y: header.size.height * 0.5))
)
originY += header.size.height
originY += 16.0
}
let title = title.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: titleString,
font: Font.bold(20.0),
textColor: .white,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 1
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: 209.0))
)
let description = description.update(
component: StarsButtonContentComponent(
context: component.context,
text: descriptionText,
color: buttonColor,
tinted: true,
starsColor: UIColor(white: 1.0, alpha: 0.5),
starsColor: isEnded ? .clear : UIColor(white: 1.0, alpha: 0.5),
font: Font.medium(13.0),
height: 24.0
),
@ -1198,7 +1214,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
component: GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: buttonColor,
isDark: theme.overallDarkAppearance,
isDark: false,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
@ -1224,7 +1240,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
component: GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: buttonColor,
isDark: theme.overallDarkAppearance,
isDark: false,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
LottieComponent(

View file

@ -335,8 +335,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme)
}
//TODO:localize
let learnMoreAttributedString = parseMarkdownIntoAttributedString("Learn more about wearing Telegram Gifts >", attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
let learnMoreAttributedString = parseMarkdownIntoAttributedString(strings.Gift_WearPreview_LearnMore, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
if let range = learnMoreAttributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
learnMoreAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: learnMoreAttributedString.string))
}
@ -367,7 +366,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
theme: theme,
strings: strings,
subject: .starGift(gift: gift, price: ""),
ribbon: GiftItemComponent.Ribbon(text: "limited", color: .blue),
ribbon: GiftItemComponent.Ribbon(text: strings.Gift_WearPreview_Limited, color: .blue),
mode: .thumbnail
),
availableSize: CGSize(width: 120.0, height: 120.0),
@ -391,7 +390,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
theme: theme,
strings: strings,
subject: .preview(attributes: attributes, rarity: 0),
ribbon: GiftItemComponent.Ribbon(text: "upgraded", color: ribbonColor),
ribbon: GiftItemComponent.Ribbon(text: strings.Gift_WearPreview_Upgraded, color: ribbonColor),
animateChanges: true,
mode: .thumbnail
),
@ -414,7 +413,7 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
let upgradeLabel = upgradeLabel.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(string: "Free\nUpgrade", font: Font.medium(13.0), textColor: theme.list.itemSecondaryTextColor)),
text: .plain(NSAttributedString(string: strings.Gift_WearPreview_FreeUpgrade, font: Font.medium(13.0), textColor: theme.list.itemSecondaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 2,
lineSpacing: 0.1

View file

@ -16,6 +16,7 @@ import ChatMessagePaymentAlertController
import ActivityIndicator
import TooltipUI
import MultilineTextComponent
import BalancedTextComponent
import TelegramStringFormatting
private final class GiftOfferAlertContentNode: AlertContentNode {
@ -24,6 +25,7 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
private var presentationTheme: PresentationTheme
private let title: String
private let text: String
private let amount: CurrencyAmount
private let gift: StarGift.UniqueGift
private let titleNode: ASTextNode
@ -32,6 +34,7 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
private let arrowNode: ASImageNode
private let avatarNode: AvatarNode
private let tableView = ComponentView<Empty>()
private let valueDelta = ComponentView<Empty>()
private let modelButtonTag = GenericComponentViewTag()
private let backdropButtonTag = GenericComponentViewTag()
@ -68,6 +71,7 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
peer: EnginePeer,
title: String,
text: String,
amount: CurrencyAmount,
actions: [TextAlertAction]
) {
self.context = context
@ -75,6 +79,7 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
self.presentationTheme = ptheme
self.title = title
self.text = text
self.amount = amount
self.gift = gift
self.titleNode = ASTextNode()
@ -339,17 +344,6 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
}
}
if let valueAmount = self.gift.valueAmount, let valueCurrency = self.gift.valueCurrency {
tableItems.append(.init(
id: "fiatValue",
title: strings.Gift_Unique_Value,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "~\(formatCurrencyAmount(valueAmount, currency: valueCurrency))", font: tableFont, textColor: tableTextColor)))
),
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
))
}
let tableSize = self.tableView.update(
transition: .immediate,
component: AnyComponent(
@ -370,7 +364,57 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
view.frame = tableFrame
}
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + 40.0 + insets.top + insets.bottom)
var valueDeltaHeight: CGFloat = 0.0
if let valueAmount = self.gift.valueUsdAmount {
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
let usdRate: Double
switch self.amount.currency {
case .stars:
usdRate = Double(resaleConfiguration.usdWithdrawRate) / 1000.0 / 100.0
case .ton:
usdRate = Double(resaleConfiguration.tonUsdRate) / 1000.0 / 1000000.0
}
let offerUsdValue = Double(self.amount.amount.value) * usdRate
let giftUsdValue = Double(valueAmount) / 100.0
let fraction = giftUsdValue / offerUsdValue
let percentage = Int(fraction * 100) - 100
if percentage > 20 {
let textColor = self.presentationTheme.list.itemDestructiveColor
let markdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: textColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor),
linkAttribute: { url in
return ("URL", url)
}
)
let valueDeltaSize = self.valueDelta.update(
transition: .immediate,
component: AnyComponent(
BalancedTextComponent(
text: .markdown(text: strings.Chat_GiftPurchaseOffer_AcceptConfirmation_BadValue("\(percentage)%").string, attributes: markdownAttributes),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)
),
environment: {},
containerSize: CGSize(width: contentWidth - 32.0, height: size.height)
)
let valueDeltaFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - valueDeltaSize.width) / 2.0), y: avatarSize.height + titleSize.height + textSize.height + 73.0 + tableSize.height), size: valueDeltaSize)
if let view = self.valueDelta.view {
if view.superview == nil {
self.view.addSubview(view)
}
view.frame = valueDeltaFrame
}
valueDeltaHeight += valueDeltaSize.height + 10.0
}
}
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + valueDeltaHeight + 40.0 + insets.top + insets.bottom)
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
var actionOffset: CGFloat = 0.0
@ -380,14 +424,14 @@ private final class GiftOfferAlertContentNode: AlertContentNode {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
do {
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
do {
currentActionWidth = resultSize.width
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
@ -433,13 +477,13 @@ public func giftOfferAlertController(
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let title = "Confirm Sale"
let buttonText: String = "Confirm Sale"
let title = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Title
let buttonText: String = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Confirm
let priceString: String
switch amount.currency {
case .stars:
priceString = "\(amount.amount) Stars"
priceString = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text_Stars(Int32(clamping: amount.amount.value))
case .ton:
priceString = "\(amount.amount) TON"
}
@ -449,15 +493,14 @@ public func giftOfferAlertController(
switch amount.currency {
case .stars:
let starsValue = Int32(floor(Float(amount.amount.value) * Float(resaleConfiguration.starGiftCommissionStarsPermille) / 1000.0))
finalPriceString = "\(starsValue) Stars"
finalPriceString = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text_Stars(starsValue)
case .ton:
let tonValue = Int64(Float(amount.amount.value) * Float(resaleConfiguration.starGiftCommissionTonPermille) / 1000.0)
finalPriceString = formatTonAmountText(tonValue, dateTimeFormat: presentationData.dateTimeFormat, maxDecimalPositions: 3) + " TON"
}
let giftTitle = "\(gift.title) #\(formatCollectibleNumber(gift.number, dateTimeFormat: presentationData.dateTimeFormat))"
let text = "Do you want to sell **\(giftTitle)** to \(peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)) for **\(priceString)**? You'll receive **\(finalPriceString)** after fees."
let text = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text(giftTitle, peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), priceString, finalPriceString).string
var contentNode: GiftOfferAlertContentNode?
var dismissImpl: ((Bool) -> Void)?
@ -468,7 +511,7 @@ public func giftOfferAlertController(
dismissImpl?(true)
})]
contentNode = GiftOfferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
contentNode = GiftOfferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, amount: amount, actions: actions)
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: nil, chatPeerId: context.account.peerId, showBalance: false)
contentNode?.getController = { [weak controller] in

View file

@ -94,6 +94,8 @@ private final class GiftUpgradePreviewScreenComponent: Component {
private var attributeInfos: [ComponentView<Empty>] = []
private let topEdgeSolidView = UIView()
private let topEdgeEffectView: EdgeEffectView
private let segmentControl = ComponentView<Empty>()
private let descriptionText = ComponentView<Empty>()
@ -146,6 +148,9 @@ private final class GiftUpgradePreviewScreenComponent: Component {
self.navigationBarContainer = SparseContainerView()
self.topEdgeEffectView = EdgeEffectView()
self.topEdgeEffectView.alpha = 0.0
self.glassContainerView = GlassBackgroundContainerView()
self.scrollView = ScrollView()
@ -235,6 +240,8 @@ private final class GiftUpgradePreviewScreenComponent: Component {
var topOffsetFraction = self.scrollView.bounds.minY / 100.0
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
self.topEdgeEffectView.alpha = max(0.0, min(1.0, self.scrollView.bounds.minY / 8.0))
let minScale: CGFloat = (itemLayout.containerSize.width - 6.0 * 2.0) / itemLayout.containerSize.width
let minScaledTranslation: CGFloat = (itemLayout.containerSize.height - itemLayout.containerSize.height * minScale) * 0.5 - 6.0
let minScaledCornerRadius: CGFloat = itemLayout.containerCornerRadius
@ -785,6 +792,14 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}
}
let edgeEffectHeight: CGFloat = 44.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight + 44.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
let edgeSolidFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: availableSize.width, height: 44.0))
transition.setFrame(view: self.topEdgeSolidView, frame: edgeSolidFrame)
transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame)
self.topEdgeSolidView.backgroundColor = theme.list.blocksBackgroundColor
self.topEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
contentHeight += 16.0
let segmentedSize = self.segmentControl.update(
@ -792,9 +807,9 @@ private final class GiftUpgradePreviewScreenComponent: Component {
component: AnyComponent(SegmentControlComponent(
theme: environment.theme,
items: [
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.models), title: "Models"),
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.backdrops), title: "Backgrounds"),
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.symbols), title: "Symbols")
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.models), title: environment.strings.Gift_Variants_Models),
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.backdrops), title: environment.strings.Gift_Variants_Backdrops),
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.symbols), title: environment.strings.Gift_Variants_Symbols)
],
selectedId: "models",
action: { [weak self] id in
@ -805,7 +820,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
self.isPlaying = false
self.updateEffectiveGifts(attributes: component.attributes)
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring))) //.withUserData(AnimationHint(value: .modeChanged)))
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)))
})),
environment: {},
containerSize: CGSize(width: fillingSize - 8.0 * 2.0, height: 100.0)
@ -813,22 +828,30 @@ private final class GiftUpgradePreviewScreenComponent: Component {
let segmentedControlFrame = CGRect(origin: CGPoint(x: floor((fillingSize - segmentedSize.width) * 0.5), y: contentHeight), size: segmentedSize)
if let segmentedControlComponentView = self.segmentControl.view {
if segmentedControlComponentView.superview == nil {
self.scrollContentView.addSubview(segmentedControlComponentView)
self.navigationBarContainer.addSubview(self.topEdgeSolidView)
self.navigationBarContainer.addSubview(self.topEdgeEffectView)
self.navigationBarContainer.addSubview(segmentedControlComponentView)
}
transition.setFrame(view: segmentedControlComponentView, frame: segmentedControlFrame)
}
contentHeight += segmentedSize.height
contentHeight += 18.0
let itemHeight: CGFloat = 126.0
let itemSpacing: CGFloat = 10.0
let descriptionText: String
let itemCount: Int32
switch self.selectedSection {
case .models:
descriptionText = "This collection features **\(self.modelCount)** unique models."
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Model(self.modelCount)).string
itemCount = self.modelCount
case .backdrops:
descriptionText = "This collection features **\(self.backdropCount)** unique backdrops."
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Backdrop(self.backdropCount)).string
itemCount = self.backdropCount
case .symbols:
descriptionText = "This collection features **\(self.symbolCount)** unique symbols."
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Symbol(self.symbolCount)).string
itemCount = self.symbolCount
}
let descriptionFont = Font.regular(13.0)
@ -839,7 +862,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
})
let descriptionSize = self.descriptionText.update(
transition: transition,
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .markdown(text: descriptionText, attributes: descriptionMarkdownAttributes)
)),
@ -851,11 +874,13 @@ private final class GiftUpgradePreviewScreenComponent: Component {
if descriptionView.superview == nil {
self.scrollContentView.addSubview(descriptionView)
}
transition.setFrame(view: descriptionView, frame: descriptionFrame)
descriptionView.frame = descriptionFrame
}
contentHeight += descriptionSize.height
contentHeight += 26.0
contentHeight += (itemHeight + itemSpacing) * ceil(CGFloat(itemCount) / 3.0)
if self.backgroundHandleView.image == nil {
self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate)
}
@ -896,44 +921,20 @@ private final class GiftUpgradePreviewScreenComponent: Component {
if let closeButtonView = self.closeButton.view {
if closeButtonView.superview == nil {
self.navigationBarContainer.addSubview(self.glassContainerView)
self.glassContainerView.contentView.addSubview(closeButtonView)
self.navigationBarContainer.addSubview(closeButtonView)
}
transition.setFrame(view: closeButtonView, frame: closeButtonFrame)
}
var playbackItems: [AnyComponentWithIdentity<Empty>] = []
var playbackSize = CGSize(width: 40.0, height: 40.0)
if self.isPlaying {
playbackItems.append(AnyComponentWithIdentity(id: "pause", component: AnyComponent(
BundleIconComponent(
name: "Media Gallery/PictureInPicturePause",
tintColor: .white
)
)))
} else {
if self.showRandomizeTip {
playbackItems.append(AnyComponentWithIdentity(id: "label", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "Randomize Traits", font: Font.semibold(17.0), textColor: .white)))
)))
playbackSize.width = 186.0
}
playbackItems.append(AnyComponentWithIdentity(id: "play", component: AnyComponent(
BundleIconComponent(
name: "Media Gallery/PlayButton",
tintColor: .white
)
)))
}
let playbackButtonSize = self.playbackButton.update(
transition: transition,
component: AnyComponent(GlassBarButtonComponent(
size: playbackSize,
size: nil,
backgroundColor: buttonColor,
isDark: false,
state: .tintedGlass,
component: AnyComponentWithIdentity(id: "content", component: AnyComponent(
HStack(playbackItems, spacing: 1.0)
PlayButtonComponent(isPlay: !self.isPlaying, title: !self.isPlaying && self.showRandomizeTip ? environment.strings.Gift_Variants_Randomize : nil)
)),
action: { [weak self] _ in
guard let self else {
@ -962,20 +963,16 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}
)),
environment: {},
containerSize: playbackSize
containerSize: CGSize(width: 160.0, height: 40.0)
)
let playbackButtonFrame = CGRect(origin: CGPoint(x: fillingSize - 16.0 - playbackButtonSize.width, y: 16.0), size: playbackButtonSize)
if let playbackButtonView = self.playbackButton.view {
if playbackButtonView.superview == nil {
playbackButtonView.clipsToBounds = true
self.glassContainerView.contentView.addSubview(playbackButtonView)
}
transition.setFrame(view: playbackButtonView, frame: playbackButtonFrame)
}
//TODO:release
contentHeight += 126.0 * 17.0
let containerInset: CGFloat = environment.statusBarHeight + 10.0
contentHeight += environment.safeInsets.bottom
@ -1181,15 +1178,15 @@ private final class AttributeInfoComponent: Component {
switch component.attribute {
case let .model(name, _, rarityValue):
title = name
subtitle = "model"
subtitle = component.strings.Gift_Variants_Model
rarity = rarityValue
case let .backdrop(name, _, _, _, _, _, rarityValue):
title = name
subtitle = "backdrop"
subtitle = component.strings.Gift_Variants_Backdrop
rarity = rarityValue
case let .pattern(name, _, rarityValue):
title = name
subtitle = "symbol"
subtitle = component.strings.Gift_Variants_Symbol
rarity = rarityValue
default:
title = ""
@ -1284,3 +1281,154 @@ private final class AttributeInfoComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class PlayButtonComponent: Component {
let isPlay: Bool
let title: String?
public init(
isPlay: Bool,
title: String?
) {
self.isPlay = isPlay
self.title = title
}
static func ==(lhs: PlayButtonComponent, rhs: PlayButtonComponent) -> Bool {
if lhs.isPlay != rhs.isPlay {
return false
}
if lhs.title != rhs.title {
return false
}
return true
}
final class View: UIView {
private var component: PlayButtonComponent?
private weak var componentState: EmptyComponentState?
private let containerView = UIView()
private let titleContainerView = UIView()
private let title = ComponentView<Empty>()
private let play = ComponentView<Empty>()
private let pause = ComponentView<Empty>()
override init(frame: CGRect) {
super.init(frame: frame)
self.containerView.clipsToBounds = true
self.containerView.layer.cornerRadius = 20.0
self.addSubview(self.containerView)
self.titleContainerView.clipsToBounds = true
self.containerView.addSubview(self.titleContainerView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: PlayButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.componentState = state
var contentSize = CGSize(width: 15.0, height: 21.0)
var titleSize = CGSize()
if let titleString = component.title {
titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: .white)))),
environment: {},
containerSize: availableSize
)
let titleFrame = CGRect(origin: CGPoint(x: 9.0, y: 10.0), size: titleSize)
if let titleView = self.title.view {
titleView.alpha = 1.0
if titleView.superview == nil {
self.titleContainerView.addSubview(titleView)
transition.animateAlpha(view: titleView, from: 0.0, to: 1.0)
}
titleView.frame = titleFrame
}
contentSize.width += titleSize.width + 4.0
} else if let titleView = self.title.view {
transition.setAlpha(view: titleView, alpha: 0.0, completion: { finished in
if finished {
titleView.removeFromSuperview()
}
})
}
transition.setFrame(view: self.titleContainerView, frame: CGRect(origin: .zero, size: CGSize(width: titleSize.width + 14.0, height: 40.0)))
if component.isPlay {
let iconSize = self.play.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(name: "Media Gallery/PlayButton", tintColor: .white)),
environment: {},
containerSize: availableSize
)
let iconFrame = CGRect(origin: CGPoint(x: contentSize.width - iconSize.width + 21.0, y: 5.0), size: iconSize)
if let iconView = self.play.view {
iconView.alpha = 1.0
if iconView.superview == nil {
self.containerView.addSubview(iconView)
transition.animateAlpha(view: iconView, from: 0.0, to: 1.0)
transition.animateScale(view: iconView, from: 0.01, to: 1.0)
}
transition.setFrame(view: iconView, frame: iconFrame)
}
} else if let iconView = self.play.view {
transition.setAlpha(view: iconView, alpha: 0.0, completion: { finished in
if finished {
iconView.removeFromSuperview()
}
})
transition.animateScale(view: iconView, from: 1.0, to: 0.01)
}
if !component.isPlay {
let iconSize = self.pause.update(
transition: .immediate,
component: AnyComponent(BundleIconComponent(name: "Media Gallery/PictureInPicturePause", tintColor: .white)),
environment: {},
containerSize: availableSize
)
let iconFrame = CGRect(origin: CGPoint(x: contentSize.width - iconSize.width + 12.0 - UIScreenPixel, y: 13.0 - UIScreenPixel), size: iconSize)
if let iconView = self.pause.view {
iconView.alpha = 1.0
if iconView.superview == nil {
self.containerView.addSubview(iconView)
transition.animateAlpha(view: iconView, from: 0.0, to: 1.0)
transition.animateScale(view: iconView, from: 0.01, to: 1.0)
}
transition.setFrame(view: iconView, frame: iconFrame)
}
} else if let iconView = self.pause.view {
transition.setAlpha(view: iconView, alpha: 0.0, completion: { finished in
if finished {
iconView.removeFromSuperview()
}
})
transition.animateScale(view: iconView, from: 1.0, to: 0.01)
}
let containerWidth: CGFloat = contentSize.width + 26.0
let containerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((contentSize.width - containerWidth) / 2.0), y: floorToScreenPixels((contentSize.height - 40.0) / 2.0)), size: CGSize(width: containerWidth, height: 40.0))
transition.setFrame(view: self.containerView, frame: containerFrame)
return contentSize
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -79,6 +79,7 @@ public final class GlassBarButtonComponent: Component {
super.init(frame: frame)
self.containerView.isUserInteractionEnabled = false
self.containerView.layer.rasterizationScale = UIScreenScale
self.addSubview(self.containerView)
self.containerView.addSubview(self.genericContainerView)

View file

@ -1449,6 +1449,14 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
case .ton:
break
}
} else if case .starGiftOffer = self.mode {
switch currency {
case .stars:
text = presentationData.strings.Gift_Offer_GiftMinAmountToast_Text("\(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(clamping: minAmount)))").string
case .ton:
let amountString = formatTonAmountText(minAmount, dateTimeFormat: presentationData.dateTimeFormat) + " TON"
text = presentationData.strings.Gift_Offer_GiftMinAmountToast_Text(amountString).string
}
}
let resultController = UndoOverlayController(
@ -2682,14 +2690,8 @@ private final class DurationMenuComponent: Component {
self.itemViews[value] = itemView
}
let repeatString: String
let hours = value / 3600
//TODO:localize
if hours == 1 {
repeatString = "1 hour"
} else {
repeatString = "\(hours) hours"
}
let repeatString: String = component.strings.Gift_Offer_Duration_Hours(Int32(hours))
let itemSize = itemView.update(
transition: transition,

View file

@ -2425,7 +2425,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch buttonType {
case 0:
let _ = strongSelf.context.engine.payments.resolveStarGiftOffer(messageId: message.id, accept: false).startStandalone()
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let peer else {
return
}
self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: self.presentationData.strings.Chat_GiftPurchaseOffer_RejectConfirmation_Title, text: self.presentationData.strings.Chat_GiftPurchaseOffer_RejectConfirmation_Text(peer.compactDisplayTitle).string, actions: [
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Chat_GiftPurchaseOffer_RejectConfirmation_Reject, action: { [weak self] in
guard let self else {
return
}
let _ = self.context.engine.payments.resolveStarGiftOffer(messageId: message.id, accept: false).startStandalone()
})
], parseMarkdown: true), in: .window(.root))
})
case 1:
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in