Various improvements

This commit is contained in:
Ilya Laktyushin 2025-08-01 23:25:24 +02:00
parent cefc76d4fc
commit 72c58813a8
32 changed files with 437 additions and 273 deletions

View File

@ -14870,3 +14870,9 @@ Sorry for the inconvenience.";
"Stories.Post.AlbumCount_any" = "%@ Albums";
"Gift.Options.Gift.Premium" = "premium";
"Stars.Transaction.FragmentTopUpTon.Title" = "TON Top-Up";
"Stars.Transaction.FragmentWithdrawalTon.Title" = "TON Withdrawal";
"Stars.Transaction.SearchFee.Title" = "Extra Search Fee";
"Stars.Intro.Transaction.SearchFee" = "Extra Search Fee";

View File

@ -277,7 +277,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
case let .starsSubscriptionLowBalance(amount, peers):
let title: String
let text: String
let starsValue = item.strings.ChatList_SubscriptionsLowBalance_Stars(Int32(amount.value))
let starsValue = item.strings.ChatList_SubscriptionsLowBalance_Stars(Int32(clamping: amount.value))
if let peer = peers.first, peers.count == 1 {
title = item.strings.ChatList_SubscriptionsLowBalance_Single_Title(starsValue, peer.compactDisplayTitle).string
text = item.strings.ChatList_SubscriptionsLowBalance_Single_Text

View File

@ -592,7 +592,7 @@ final class ChatSendMessageContextScreenComponent: Component {
let titleLayout: ContextMenuActionItemTextLayout
if let currentPrice {
title = environment.strings.Attachment_Paid_EditPrice
titleLayout = .secondLineWithValue(environment.strings.Attachment_Paid_EditPrice_Stars(Int32(currentPrice)))
titleLayout = .secondLineWithValue(environment.strings.Attachment_Paid_EditPrice_Stars(Int32(clamping: currentPrice)))
} else {
title = environment.strings.Attachment_Paid_Create
titleLayout = .twoLinesMax

View File

@ -2799,7 +2799,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let titleLayout: ContextMenuActionItemTextLayout
if let price {
title = strings.Attachment_Paid_EditPrice
titleLayout = .secondLineWithValue(strings.Attachment_Paid_EditPrice_Stars(Int32(price)))
titleLayout = .secondLineWithValue(strings.Attachment_Paid_EditPrice_Stars(Int32(clamping: price)))
} else {
title = strings.Attachment_Paid_Create
titleLayout = .twoLinesMax

View File

@ -793,7 +793,7 @@ private func createGiveawayControllerEntries(
if !state.starsExpanded && product.giveawayOption.isExtended {
continue
}
let giftTitle: String = presentationData.strings.BoostGift_Stars_Stars(Int32(product.giveawayOption.count))
let giftTitle: String = presentationData.strings.BoostGift_Stars_Stars(Int32(clamping: product.giveawayOption.count))
let maxWinners = product.giveawayOption.winners.sorted(by: { $0.users < $1.users }).last?.users ?? 1
let starsPerUser: Int64
@ -951,7 +951,7 @@ private func createGiveawayControllerEntries(
entries.append(.prizeDescriptionText(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizesPlaceholder, state.prizeDescription, state.subscriptions))
if state.mode == .starsGiveaway {
let starsString = presentationData.strings.BoostGift_AdditionalPrizesInfoStars(Int32(state.stars))
let starsString = presentationData.strings.BoostGift_AdditionalPrizesInfoStars(Int32(clamping: state.stars))
if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoStarsOn(starsString, "").string
} else {

View File

@ -154,7 +154,7 @@ public func presentGiveawayInfoController(
let intro: String
if stars > 0 {
let starsString = presentationData.strings.Chat_Giveaway_Info_Stars_Stars(Int32(stars))
let starsString = presentationData.strings.Chat_Giveaway_Info_Stars_Stars(Int32(clamping: stars))
if case .almostOver = status {
if isGroup {
intro = presentationData.strings.Chat_Giveaway_Info_Stars_Group_EndedIntro(peerName, starsString).string
@ -280,7 +280,7 @@ public func presentGiveawayInfoController(
let intro: String
if stars > 0 {
let starsString = presentationData.strings.Chat_Giveaway_Info_Stars_Stars(Int32(stars))
let starsString = presentationData.strings.Chat_Giveaway_Info_Stars_Stars(Int32(clamping: stars))
if isGroup {
intro = presentationData.strings.Chat_Giveaway_Info_Stars_Group_EndedIntro(peerName, starsString).string
} else {

View File

@ -931,10 +931,10 @@ struct PremiumIntroConfiguration {
private struct PremiumProduct: Equatable {
let option: PremiumPromoConfiguration.PremiumProductOption
let storeProduct: InAppPurchaseManager.Product
let storeProduct: InAppPurchaseManager.Product?
var id: String {
return self.option.storeProductId ?? self.storeProduct.id
return self.storeProduct?.id ?? self.option.botUrl
}
var months: Int32 {
@ -942,11 +942,39 @@ private struct PremiumProduct: Equatable {
}
var price: String {
return self.storeProduct.price
if let storeProduct = self.storeProduct {
return storeProduct.price
} else {
return formatCurrencyAmount(self.option.amount, currency: self.option.currency)
}
}
var pricePerMonth: String {
return self.storeProduct.pricePerMonth(Int(self.months))
if let storeProduct = self.storeProduct {
return storeProduct.pricePerMonth(Int(self.months))
} else {
return formatCurrencyAmount(self.option.amount / Int64(self.months), currency: self.option.currency)
}
}
var priceCurrencyAndAmount: (currency: String, amount: Int64) {
if let priceCurrencyAndAmount = self.storeProduct?.priceCurrencyAndAmount {
return priceCurrencyAndAmount
} else {
return (self.option.currency, self.option.amount)
}
}
var priceValue: NSDecimalNumber {
if let priceValue = self.storeProduct?.priceValue {
return priceValue
} else {
return self.optionPriceValue
}
}
var optionPriceValue: NSDecimalNumber {
return currencyToFractionalAmount(value: self.option.amount, currency: self.option.currency).flatMap { NSDecimalNumber(floatLiteral: $0) } ?? 0.0
}
var isCurrent: Bool {
@ -1552,7 +1580,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
var isBiannual: Bool {
return self.products?.first(where: { $0.id == self.selectedProductId })?.id.hasSuffix(".biannual") ?? false
return self.products?.first(where: { $0.id == self.selectedProductId })?.months == 24
}
var canUpgrade: Bool {
@ -1957,15 +1985,23 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
if let products = state.products, products.count > 1, state.isPremium == false || (!context.component.justBought && state.canUpgrade) {
var optionsItems: [SectionGroupComponent.Item] = []
let shortestOptionPrice: (Int64, NSDecimalNumber)
let shortestProductPrice: (Int64, NSDecimalNumber)
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = (Int64(Float(product.storeProduct.priceCurrencyAndAmount.amount)), product.storeProduct.priceValue)
shortestProductPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
} else {
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
shortestProductPrice = (1, NSDecimalNumber(decimal: 1))
}
let currentProductMonths = state.products?.first(where: { $0.isCurrent })?.months ?? 0
var referenceProduct: InAppPurchaseManager.Product?
for product in products {
if let storeProduct = product.storeProduct {
referenceProduct = storeProduct
break
}
}
var i = 0
for product in products {
let giftTitle: String
@ -1973,13 +2009,13 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
giftTitle = strings.Premium_Monthly
} else if product.id.hasSuffix(".semiannual") {
giftTitle = strings.Premium_Semiannual
} else if product.id.hasSuffix(".biannual") {
} else if product.months == 24 {
giftTitle = strings.Premium_Biannual
} else {
giftTitle = strings.Premium_Annual
}
let fraction = Float(product.storeProduct.priceCurrencyAndAmount.amount) / Float(product.months) / Float(shortestOptionPrice.0)
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(product.months) / Float(shortestProductPrice.0)
let discountValue = Int(round((1.0 - fraction) * 20.0) * 5.0)
let discount: String
if discountValue > 0 {
@ -1988,13 +2024,16 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
discount = ""
}
let defaultPrice = product.storeProduct.defaultPrice(shortestOptionPrice.1, monthsCount: Int(product.months))
var defaultPrice: String = ""
if let referenceProduct {
defaultPrice = referenceProduct.defaultPrice(shortestProductPrice.1, monthsCount: Int(product.months))
}
var subtitle = ""
var accessibilitySubtitle = ""
var pricePerMonth = product.price
if product.months > 1 {
pricePerMonth = product.storeProduct.pricePerMonth(Int(product.months))
pricePerMonth = product.pricePerMonth
if discountValue > 0 {
subtitle = "**\(defaultPrice)** \(product.price)"
@ -2964,6 +3003,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
private let source: PremiumSource
private let updateInProgress: (Bool) -> Void
private let present: (ViewController) -> Void
var navigationController: (() -> NavigationController?)?
private let completion: () -> Void
var topContentOffset: CGFloat?
@ -2987,7 +3027,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
var emojiPackTitle: String?
private var emojiFileDisposable: Disposable?
private var disposable: Disposable?
private var paymentDisposable = MetaDisposable()
private var activationDisposable = MetaDisposable()
@ -3002,7 +3041,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
}
var isBiannual: Bool {
return self.products?.first(where: { $0.id == self.selectedProductId })?.id.hasSuffix(".biannual") ?? false
return self.products?.first(where: { $0.id == self.selectedProductId })?.months == 24
}
var canUpgrade: Bool {
@ -3017,7 +3056,14 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
}
}
init(screenContext: PremiumIntroScreen.ScreenContext, source: PremiumSource, forceHasPremium: Bool, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
init(
screenContext: PremiumIntroScreen.ScreenContext,
source: PremiumSource,
forceHasPremium: Bool,
updateInProgress: @escaping (Bool) -> Void,
present: @escaping (ViewController) -> Void,
completion: @escaping () -> Void
) {
self.screenContext = screenContext
self.source = source
self.updateInProgress = updateInProgress
@ -3093,6 +3139,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
for option in promoConfiguration.premiumProductOptions {
if let product = availableProducts.first(where: { $0.id == option.storeProductId }), product.isSubscription {
products.append(PremiumProduct(option: option, storeProduct: product))
} else {
products.append(PremiumProduct(option: option, storeProduct: nil))
}
}
@ -3219,117 +3267,127 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
self.updateInProgress(true)
self.updated(transition: .immediate)
let purpose: AppStoreTransactionPurpose = isUpgrade ? .upgrade : .subscription
if let storeProduct = premiumProduct.storeProduct {
let purpose: AppStoreTransactionPurpose = isUpgrade ? .upgrade : .subscription
let canPurchasePremium: Signal<Bool, NoError>
switch self.screenContext {
case let .accountContext(context):
canPurchasePremium = context.engine.payments.canPurchasePremium(purpose: purpose)
case let .sharedContext(_, engine, _):
canPurchasePremium = engine.payments.canPurchasePremium(purpose: purpose)
}
let _ = (canPurchasePremium
|> deliverOnMainQueue).start(next: { [weak self] available in
guard let self else {
return
let canPurchasePremium: Signal<Bool, NoError>
switch self.screenContext {
case let .accountContext(context):
canPurchasePremium = context.engine.payments.canPurchasePremium(purpose: purpose)
case let .sharedContext(_, engine, _):
canPurchasePremium = engine.payments.canPurchasePremium(purpose: purpose)
}
if available {
self.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct.storeProduct, purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let self, case .purchased = status {
let activation: Signal<Never, AssignAppStoreTransactionError>
if let context = self.screenContext.context {
activation = context.account.postbox.peerView(id: context.account.peerId)
|> castError(AssignAppStoreTransactionError.self)
|> take(until: { view in
if let peer = view.peers[view.peerId], peer.isPremium {
return SignalTakeAction(passthrough: false, complete: true)
} else {
return SignalTakeAction(passthrough: false, complete: false)
let _ = (canPurchasePremium
|> deliverOnMainQueue).start(next: { [weak self] available in
guard let self else {
return
}
if available {
self.paymentDisposable.set((inAppPurchaseManager.buyProduct(storeProduct, purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let self, case .purchased = status {
let activation: Signal<Never, AssignAppStoreTransactionError>
if let context = self.screenContext.context {
activation = context.account.postbox.peerView(id: context.account.peerId)
|> castError(AssignAppStoreTransactionError.self)
|> take(until: { view in
if let peer = view.peers[view.peerId], peer.isPremium {
return SignalTakeAction(passthrough: false, complete: true)
} else {
return SignalTakeAction(passthrough: false, complete: false)
}
})
|> mapToSignal { _ -> Signal<Never, AssignAppStoreTransactionError> in
return .never()
}
})
|> mapToSignal { _ -> Signal<Never, AssignAppStoreTransactionError> in
return .never()
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .fail(.timeout))
} else {
activation = .complete()
}
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .fail(.timeout))
} else {
activation = .complete()
}
self.activationDisposable.set((activation
|> deliverOnMainQueue).start(error: { [weak self] _ in
if let self {
self.activationDisposable.set((activation
|> deliverOnMainQueue).start(error: { [weak self] _ in
if let self {
self.inProgress = false
self.updateInProgress(false)
self.updated(transition: .immediate)
if let context = self.screenContext.context {
addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail")
}
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
self.present(alertController)
}
}, completed: { [weak self] in
guard let self else {
return
}
if let context = self.screenContext.context {
let _ = updatePremiumPromoConfigurationOnce(account: context.account).start()
}
self.inProgress = false
self.updateInProgress(false)
self.updated(transition: .immediate)
self.isPremium = true
self.justBought = true
if let context = self.screenContext.context {
addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail")
}
self.updated(transition: .easeInOut(duration: 0.25))
self.completion()
}))
}
}, error: { [weak self] error in
guard let self else {
return
}
self.inProgress = false
self.updateInProgress(false)
self.updated(transition: .immediate)
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
self.present(alertController)
}
}, completed: { [weak self] in
guard let self else {
return
}
var errorText: String?
switch error {
case .generic:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .network:
errorText = presentationData.strings.Premium_Purchase_ErrorNetwork
case .notAllowed:
errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed
case .cantMakePayments:
errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments
case .assignFailed:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .tryLater:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .cancelled:
break
}
if let errorText = errorText {
if let context = self.screenContext.context {
let _ = updatePremiumPromoConfigurationOnce(account: context.account).start()
addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail")
}
self.inProgress = false
self.updateInProgress(false)
self.isPremium = true
self.justBought = true
self.updated(transition: .easeInOut(duration: 0.25))
self.completion()
}))
}
}, error: { [weak self] error in
guard let self else {
return
}
let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
self.present(alertController)
}
}))
} else {
self.inProgress = false
self.updateInProgress(false)
self.updated(transition: .immediate)
}
})
} else if case let .accountContext(context) = self.screenContext, let navigationController = self.navigationController?() {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: premiumProduct.option.botUrl, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
var errorText: String?
switch error {
case .generic:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .network:
errorText = presentationData.strings.Premium_Purchase_ErrorNetwork
case .notAllowed:
errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed
case .cantMakePayments:
errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments
case .assignFailed:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .tryLater:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .cancelled:
break
}
if let errorText = errorText {
if let context = self.screenContext.context {
addAppLogEvent(postbox: context.account.postbox, type: "premium.promo_screen_fail")
}
let alertController = textAlertController(sharedContext: self.screenContext.sharedContext, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
self.present(alertController)
}
}))
} else {
Queue.mainQueue().after(3.0) {
self.inProgress = false
self.updateInProgress(false)
self.updated(transition: .immediate)
}
})
}
}
func updateIsFocused(_ isFocused: Bool) {
@ -3366,6 +3424,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
return { context in
let environment = context.environment[EnvironmentType.self].value
let state = context.state
state.navigationController = { [weak environment] in
return environment?.controller()?.navigationController as? NavigationController
}
let background = background.update(component: Rectangle(color: environment.theme.list.blocksBackgroundColor), environment: {}, availableSize: context.availableSize, transition: context.transition)

View File

@ -1019,7 +1019,7 @@ private enum StatsEntry: ItemListNodeEntry {
icon = .image(color: color, name: "Premium/Unclaimed")
} else if boost.flags.contains(.isGiveaway) {
if let stars = boost.stars {
title = presentationData.strings.Stats_Boosts_Stars(Int32(stars))
title = presentationData.strings.Stats_Boosts_Stars(Int32(clamping: stars))
icon = .image(color: .stars, name: "Premium/PremiumStar")
expiresString = expiresValue
} else {
@ -1476,7 +1476,7 @@ private func boostsEntries(
title = presentationData.strings.Stats_Boosts_PrepaidGiveawayCount(giveaway.quantity)
text = presentationData.strings.Stats_Boosts_PrepaidGiveawayMonths("\(months)").string
case let .stars(stars, _):
title = presentationData.strings.Stats_Boosts_Stars(Int32(stars))
title = presentationData.strings.Stats_Boosts_Stars(Int32(clamping: stars))
text = presentationData.strings.Stats_Boosts_StarsWinners(giveaway.quantity)
}
entries.append(.boostPrepaid(i, presentationData.theme, title, text, giveaway))

View File

@ -347,7 +347,7 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
theme: item.presentationData.theme,
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)),
contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: item.context, theme: item.presentationData.theme, peer: item.transaction.peer, photo: nil, media: [], uniqueGift: nil, backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor))), false),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: item.context, theme: item.presentationData.theme, peer: .transactionPeer(item.transaction.peer), photo: nil, media: [], uniqueGift: nil, backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor))), false),
icon: nil,
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel, iconName: itemIconName, iconColor: itemIconColor))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
action: { [weak self] _ in

View File

@ -720,6 +720,9 @@ private extension StarsContext.State.Transaction {
if (apiFlags & (1 << 22)) != 0 {
flags.insert(.isStarGiftResale)
}
if (apiFlags & (1 << 24)) != 0 {
flags.insert(.isPostsSearch)
}
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
let _ = subscriptionPeriod
@ -774,6 +777,7 @@ public final class StarsContext {
public static let isPaidMessage = Flags(rawValue: 1 << 7)
public static let isBusinessTransfer = Flags(rawValue: 1 << 8)
public static let isStarGiftResale = Flags(rawValue: 1 << 9)
public static let isPostsSearch = Flags(rawValue: 1 << 10)
}
public enum Peer: Equatable {

View File

@ -96,7 +96,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
for attribute in message.attributes {
if let attribute = attribute as? PaidStarsMessageAttribute {
let messageCount = Int32(messageCount ?? 1)
let price = strings.Notification_PaidMessage_Stars(Int32(attribute.stars.value) * messageCount)
let price = strings.Notification_PaidMessage_Stars(Int32(clamping: attribute.stars.value) * messageCount)
if message.author?.id == accountPeerId {
if messageCount > 1 {
let messagesString = strings.Notification_PaidMessage_Messages(messageCount)
@ -1048,7 +1048,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
let resultTitleString: PresentationStrings.FormattedString
if let stars {
let starsString = strings.Notification_StarsGiveawayStarted_Stars(Int32(stars))
let starsString = strings.Notification_StarsGiveawayStarted_Stars(Int32(clamping: stars))
resultTitleString = isGroup ? strings.Notification_StarsGiveawayStartedGroup(compactAuthorName, starsString) : strings.Notification_StarsGiveawayStarted(compactAuthorName, starsString)
} else {
resultTitleString = isGroup ? strings.Notification_GiveawayStartedGroup(compactAuthorName) : strings.Notification_GiveawayStarted(compactAuthorName)
@ -1146,7 +1146,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
if let upgradeStars {
finalPrice += upgradeStars
}
let starsPrice = strings.Notification_StarsGift_Stars(Int32(finalPrice))
let starsPrice = strings.Notification_StarsGift_Stars(Int32(clamping: finalPrice))
var authorName = compactAuthorName
var peerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 {
@ -1207,7 +1207,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
let starsString: String
switch resaleStars.currency {
case .stars:
starsString = strings.Notification_StarsGift_Bought_Stars(Int32(resaleStars.amount.value))
starsString = strings.Notification_StarsGift_Bought_Stars(Int32(clamping: resaleStars.amount.value))
case .ton:
starsString = formatTonAmountText(resaleStars.amount.value, dateTimeFormat: dateTimeFormat) + " TON"
}
@ -1246,7 +1246,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
let starsString: String
switch resaleStars.currency {
case .stars:
starsString = strings.Notification_StarsGift_Bought_Stars(Int32(resaleStars.amount.value))
starsString = strings.Notification_StarsGift_Bought_Stars(Int32(clamping: resaleStars.amount.value))
case .ton:
starsString = formatTonAmountText(resaleStars.amount.value, dateTimeFormat: dateTimeFormat) + " TON"
}
@ -1262,7 +1262,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
}
case let .paidMessagesRefunded(_, stars):
let starsString = strings.Notification_PaidMessageRefund_Stars(Int32(stars))
let starsString = strings.Notification_PaidMessageRefund_Stars(Int32(clamping: stars))
var isOutgoing = false
var messagePeer: EnginePeer?
@ -1306,7 +1306,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
}
case let .paidMessagesPriceEdited(stars, broadcastMessagesAllowed):
let starsString = strings.Notification_PaidMessagePriceChanged_Stars(Int32(stars))
let starsString = strings.Notification_PaidMessagePriceChanged_Stars(Int32(clamping: stars))
if message.author?.id == accountPeerId {
let resultString: PresentationStrings.FormattedString
resultString = strings.Notification_PaidMessagePriceChangedYou(starsString)

View File

@ -503,7 +503,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
peerName = EnginePeer(channel).compactDisplayTitle
}
title = item.presentationData.strings.Notification_StarsGiveaway_Title
let starsString = item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(count)).replacingOccurrences(of: " ", with: "\u{00A0}")
let starsString = item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(clamping: count)).replacingOccurrences(of: " ", with: "\u{00A0}")
text = item.presentationData.strings.Notification_StarsGiveaway_Subtitle(peerName, starsString).string
case let .giftCode(_, fromGiveaway, unclaimed, channelId, monthsValue, _, _, _, _, giftText, giftEntities):
if channelId == nil {
@ -576,23 +576,23 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Upgraded
} else if incoming {
if converted {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Converted(item.presentationData.strings.Notification_StarGift_Subtitle_Converted_Stars(Int32(convertStars ?? 0))).string
text = item.presentationData.strings.Notification_StarGift_Subtitle_Converted(item.presentationData.strings.Notification_StarGift_Subtitle_Converted_Stars(Int32(clamping: convertStars ?? 0))).string
} else if upgradeStars != nil {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Upgrade
} else if isSelfGift && canUpgrade {
text = item.presentationData.strings.Notification_StarsGift_Subtitle_Self
} else if savedToProfile {
if let convertStars {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Displaying(item.presentationData.strings.Notification_StarGift_Subtitle_Displaying_Stars(Int32(convertStars))).string
text = item.presentationData.strings.Notification_StarGift_Subtitle_Displaying(item.presentationData.strings.Notification_StarGift_Subtitle_Displaying_Stars(Int32(clamping: convertStars))).string
} else {
text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle_Displaying
}
} else {
if let convertStars, convertStars > 0 {
if isChannelGift {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Channel(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars))).string
text = item.presentationData.strings.Notification_StarGift_Subtitle_Channel(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(clamping: convertStars))).string
} else {
text = item.presentationData.strings.Notification_StarGift_Subtitle(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars))).string
text = item.presentationData.strings.Notification_StarGift_Subtitle(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(clamping: convertStars))).string
}
} else {
text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle
@ -605,7 +605,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
}
if peerName.isEmpty {
if let convertStars, convertStars > 0 {
let starsString = item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars)).replacingOccurrences(of: " ", with: "\u{00A0}")
let starsString = item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(clamping: convertStars)).replacingOccurrences(of: " ", with: "\u{00A0}")
text = item.presentationData.strings.Notification_StarGift_Subtitle(starsString).string
} else {
text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle
@ -614,7 +614,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
if upgradeStars != nil {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Upgrade_Other(peerName).string
} else if let convertStars, convertStars > 0 {
let starsString = item.presentationData.strings.Notification_StarGift_Subtitle_Other_Stars(Int32(convertStars)).replacingOccurrences(of: " ", with: "\u{00A0}")
let starsString = item.presentationData.strings.Notification_StarGift_Subtitle_Other_Stars(Int32(clamping: convertStars)).replacingOccurrences(of: " ", with: "\u{00A0}")
let formattedString = item.presentationData.strings.Notification_StarGift_Subtitle_Other(peerName, starsString)
text = formattedString.string
if let starsRange = formattedString.ranges.last {

View File

@ -350,7 +350,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
}
), textAlignment: .center)
case let .stars(amount):
let starsString = item.presentationData.strings.Chat_Giveaway_Message_Stars_Stars(Int32(amount))
let starsString = item.presentationData.strings.Chat_Giveaway_Message_Stars_Stars(Int32(clamping: amount))
prizeTextString = parseMarkdownIntoAttributedString(item.presentationData.strings.Chat_Giveaway_Message_Stars_PrizeText(
starsString,
item.presentationData.strings.Chat_Giveaway_Message_Stars_Winners(giveaway.quantity)
@ -468,7 +468,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
dateTextString = NSAttributedString(string: stringForFullDate(timestamp: giveaway.untilDate, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat), font: textFont, textColor: textColor)
} else if let giveawayResults {
if case let .stars(stars) = giveawayResults.prize {
let starsString = item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_Stars(Int32(stars))
let starsString = item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_Stars(Int32(clamping: stars))
dateTextString = parseMarkdownIntoAttributedString(giveawayResults.winnersCount > 1 ? item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_Stars_Many(starsString).string : item.presentationData.strings.Chat_Giveaway_Message_WinnersInfo_Stars_One(starsString).string, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),

View File

@ -505,8 +505,8 @@ public func chatMessagePaymentAlertController(
let text: String
if peers.count == 1, let peer = peers.first {
let amountString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(amount.value))
let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(amount.value * Int64(count)))
let amountString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value))
let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value * Int64(count)))
if case let .channel(channel) = peer.chatOrMonoforumMainPeer, case .broadcast = channel.info {
text = presentationData.strings.Chat_PaidMessage_Confirm_SingleComment_Text(EnginePeer(channel).compactDisplayTitle, amountString, totalString, messagesString).string
} else {
@ -515,7 +515,7 @@ public func chatMessagePaymentAlertController(
} else {
let amount = totalAmount ?? amount
let usersString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Users(Int32(peers.count))
let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(amount.value * Int64(count)))
let totalString = presentationData.strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value * Int64(count)))
text = presentationData.strings.Chat_PaidMessage_Confirm_Multiple_Text(usersString, totalString, messagesString).string
}
@ -573,7 +573,7 @@ public func chatMessageRemovePaymentAlertController(
text = strings.Chat_PaidMessage_RemoveFee_Text(peer.compactDisplayTitle).string
}
let optionText = amount.flatMap { strings.Chat_PaidMessage_RemoveFee_Refund(strings.Chat_PaidMessage_RemoveFee_Refund_Stars(Int32($0.value))).string }
let optionText = amount.flatMap { strings.Chat_PaidMessage_RemoveFee_Refund(strings.Chat_PaidMessage_RemoveFee_Refund_Stars(Int32(clamping: $0.value))).string }
let contentNode = ChatMessagePaymentAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, title: title, text: text, optionText: optionText, actions: actions, alignment: .horizontal)

View File

@ -1866,7 +1866,7 @@ private final class ChatSendStarsScreenComponent: Component {
switch component.initialData.subjectInitialData {
case let .react(reactData):
if let currentSentAmount = reactData.currentSentAmount {
text = environment.strings.SendStarReactions_TextSentStars(Int32(currentSentAmount))
text = environment.strings.SendStarReactions_TextSentStars(Int32(clamping: currentSentAmount))
} else {
text = environment.strings.SendStarReactions_TextGeneric(reactData.peer.debugDisplayTitle).string
}

View File

@ -236,7 +236,7 @@ final class GiftSetupScreenComponent: Component {
title: environment.strings.Gift_Send_Premium_Confirmation_Title,
text: environment.strings.Gift_Send_Premium_Confirmation_Text(
peer.compactDisplayTitle,
environment.strings.Gift_Send_Premium_Confirmation_Text_Stars(Int32(starsPrice))
environment.strings.Gift_Send_Premium_Confirmation_Text_Stars(Int32(clamping: starsPrice))
).string,
actions: [
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}),
@ -427,7 +427,7 @@ final class GiftSetupScreenComponent: Component {
file: starGift.file,
loop: true,
title: nil,
text: presentationData.strings.Gift_Send_Success(self.peerMap[peerId]?.compactDisplayTitle ?? "", presentationData.strings.Gift_Send_Success_Stars(Int32(starGift.price))).string,
text: presentationData.strings.Gift_Send_Success(self.peerMap[peerId]?.compactDisplayTitle ?? "", presentationData.strings.Gift_Send_Success_Stars(Int32(clamping: starGift.price))).string,
undoText: nil,
customAction: nil
),

View File

@ -310,7 +310,7 @@ private final class GiftPurchaseAlertContentNode: AlertContentNode {
if let resellPrice {
switch resellPrice.currency {
case .stars:
priceString = self.strings.Gift_Buy_Confirm_Text_Stars(Int32(resellPrice.amount.value))
priceString = self.strings.Gift_Buy_Confirm_Text_Stars(Int32(clamping: resellPrice.amount.value))
case .ton:
priceString = "**\(formatTonAmountText(resellPrice.amount.value, dateTimeFormat: presentationData.dateTimeFormat)) TON**"
}

View File

@ -297,7 +297,7 @@ public func giftTransferAlertController(
let text: String
let buttonText: String
if transferStars > 0 {
text = strings.Gift_Transfer_Confirmation_Text("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), strings.Gift_Transfer_Confirmation_Text_Stars(Int32(transferStars))).string
text = strings.Gift_Transfer_Confirmation_Text("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), strings.Gift_Transfer_Confirmation_Text_Stars(Int32(clamping: transferStars))).string
buttonText = "\(strings.Gift_Transfer_Confirmation_Transfer) $ \(transferStars)"
} else {
text = strings.Gift_Transfer_Confirmation_TextFree("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string

View File

@ -548,7 +548,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let text = presentationData.strings.Gift_Convert_Period_Text(
fromPeerName,
presentationData.strings.Gift_Convert_Period_Stars(Int32(convertStars)),
presentationData.strings.Gift_Convert_Period_Stars(Int32(clamping: convertStars)),
presentationData.strings.Gift_Convert_Period_Days(days)
).string
@ -579,11 +579,11 @@ private final class GiftViewSheetContent: CombinedComponent {
let text: String
if isChannelGift {
text = presentationData.strings.Gift_Convert_Success_ChannelText(
presentationData.strings.Gift_Convert_Success_ChannelText_Stars(Int32(convertStars))
presentationData.strings.Gift_Convert_Success_ChannelText_Stars(Int32(clamping: convertStars))
).string
} else {
text = presentationData.strings.Gift_Convert_Success_Text(
presentationData.strings.Gift_Convert_Success_Text_Stars(Int32(convertStars))
presentationData.strings.Gift_Convert_Success_Text_Stars(Int32(clamping: convertStars))
).string
if let starsContext = self.context.starsContext {
navigationController.pushViewController(
@ -932,7 +932,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let priceString: String
switch price.currency {
case .stars:
priceString = presentationData.strings.Gift_View_Resale_Relist_Success_Stars(Int32(price.amount.value))
priceString = presentationData.strings.Gift_View_Resale_Relist_Success_Stars(Int32(clamping: price.amount.value))
case .ton:
priceString = formatTonAmountText(price.amount.value, dateTimeFormat: presentationData.dateTimeFormat, maxDecimalPositions: nil) + " TON"
}
@ -1257,7 +1257,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let originalPriceString: String
switch resellAmount.currency {
case .stars:
originalPriceString = presentationData.strings.Gift_Buy_ErrorPriceChanged_Text_Stars(Int32(resellAmount.amount.value))
originalPriceString = presentationData.strings.Gift_Buy_ErrorPriceChanged_Text_Stars(Int32(clamping: resellAmount.amount.value))
case .ton:
originalPriceString = formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat, maxDecimalPositions: nil) + " TON"
}
@ -1266,7 +1266,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let buttonText: String
switch newPrice.currency {
case .stars:
newPriceString = presentationData.strings.Gift_Buy_ErrorPriceChanged_Text_Stars(Int32(newPrice.amount.value))
newPriceString = presentationData.strings.Gift_Buy_ErrorPriceChanged_Text_Stars(Int32(clamping: newPrice.amount.value))
buttonText = presentationData.strings.Gift_Buy_Confirm_BuyFor(Int32(newPrice.amount.value))
case .ton:
let tonValueString = formatTonAmountText(newPrice.amount.value, dateTimeFormat: presentationData.dateTimeFormat, maxDecimalPositions: nil)
@ -2246,10 +2246,10 @@ private final class GiftViewSheetContent: CombinedComponent {
descriptionText = strings.Gift_View_UpgradeDescription
}
} else {
descriptionText = isChannelGift ? strings.Gift_View_KeepOrConvertDescription_Channel(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string : strings.Gift_View_KeepOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string
descriptionText = isChannelGift ? strings.Gift_View_KeepOrConvertDescription_Channel(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(clamping: convertStars))).string : strings.Gift_View_KeepOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(clamping: convertStars))).string
}
} else {
descriptionText = strings.Gift_View_ConvertedDescription(strings.Gift_View_ConvertedDescription_Stars(Int32(convertStars))).string
descriptionText = strings.Gift_View_ConvertedDescription(strings.Gift_View_ConvertedDescription_Stars(Int32(clamping: convertStars))).string
}
} else {
descriptionText = strings.Gift_View_BotDescription
@ -2258,7 +2258,7 @@ private final class GiftViewSheetContent: CombinedComponent {
if let _ = upgradeStars {
descriptionText = strings.Gift_View_FreeUpgradeOtherDescription(peer.compactDisplayTitle).string
} else if case .message = subject, let convertStars {
descriptionText = strings.Gift_View_OtherDescription(peer.compactDisplayTitle, strings.Gift_View_OtherDescription_Stars(Int32(convertStars))).string
descriptionText = strings.Gift_View_OtherDescription(peer.compactDisplayTitle, strings.Gift_View_OtherDescription_Stars(Int32(clamping: convertStars))).string
} else {
descriptionText = ""
}
@ -3085,7 +3085,7 @@ private final class GiftViewSheetContent: CombinedComponent {
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: component.context,
text: strings.Gift_View_Sale(strings.Gift_View_Sale_Stars(Int32(convertStars))).string,
text: strings.Gift_View_Sale(strings.Gift_View_Sale_Stars(Int32(clamping: convertStars))).string,
color: theme.list.itemAccentColor
)),
action: { [weak state] in

View File

@ -174,7 +174,7 @@ private final class GiftWithdrawAlertContentNode: AlertContentNode {
StarsAvatarComponent(
context: self.context,
theme: self.presentationTheme,
peer: .fragment,
peer: .transactionPeer(.fragment),
photo: nil,
media: [],
uniqueGift: nil,

View File

@ -368,7 +368,7 @@ private class MessagePriceItemNode: ListViewItemNode {
strongSelf.leftTextNode.attributedText = NSAttributedString(string: "\(item.minValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor)
strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor)
let centralLeftText = item.value == 0 ? item.strings.Stars_SendMessage_PriceFree : item.strings.Privacy_Messages_Stars(Int32(item.value))
let centralLeftText = item.value == 0 ? item.strings.Stars_SendMessage_PriceFree : item.strings.Privacy_Messages_Stars(Int32(clamping: item.value))
strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor : item.theme.list.itemPrimaryTextColor)
strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.openSetCustom != nil ? item.theme.list.itemAccentColor.withMultipliedAlpha(0.5) : item.theme.list.itemSecondaryTextColor)

View File

@ -14,9 +14,14 @@ import MultilineTextComponent
import GiftItemComponent
public final class StarsAvatarComponent: Component {
public enum Peer: Equatable {
case transactionPeer(StarsContext.State.Transaction.Peer)
case search
}
let context: AccountContext
let theme: PresentationTheme
let peer: StarsContext.State.Transaction.Peer?
let peer: StarsAvatarComponent.Peer?
let photo: TelegramMediaWebFile?
let media: [Media]
let uniqueGift: StarGift.UniqueGift?
@ -26,7 +31,7 @@ public final class StarsAvatarComponent: Component {
public init(
context: AccountContext,
theme: PresentationTheme,
peer: StarsContext.State.Transaction.Peer?,
peer: StarsAvatarComponent.Peer?,
photo: TelegramMediaWebFile?,
media: [Media],
uniqueGift: StarGift.UniqueGift?,
@ -250,108 +255,125 @@ public final class StarsAvatarComponent: Component {
switch component.peer {
case .none:
break
case let .peer(peer):
if !didSetup {
self.avatarNode.setPeer(
context: component.context,
theme: component.theme,
peer: peer,
synchronousLoad: true
case let .transactionPeer(peer):
switch peer {
case let .peer(peer):
if !didSetup {
self.avatarNode.setPeer(
context: component.context,
theme: component.theme,
peer: peer,
synchronousLoad: true
)
self.backgroundView.isHidden = true
self.iconView.isHidden = true
self.avatarNode.isHidden = false
}
case .appStore:
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x2a9ef1).cgColor,
UIColor(rgb: 0x72d5fd).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = true
self.iconView.isHidden = true
self.avatarNode.isHidden = false
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple")
case .playMarket:
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x54cb68).cgColor,
UIColor(rgb: 0xa0de7e).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Google")
case .fragment:
self.backgroundView.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1b1f24))
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
iconOffset = 2.0
case .ads:
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0xffa85c).cgColor,
UIColor(rgb: 0xffcd6a).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white)
case .premiumBot:
iconInset = 7.0
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x8d77ff).cgColor,
UIColor(rgb: 0xb56eec).cgColor,
UIColor(rgb: 0xb56eec).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
case .apiLimitExtension:
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x32b83b).cgColor,
UIColor(rgb: 0x87d93b).cgColor
],
direction: .vertical
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/PaidBroadcast")
case .unsupported:
iconInset = 7.0
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0xb1b1b1).cgColor,
UIColor(rgb: 0xcdcdcd).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
}
case .appStore:
case .search:
iconInset = 6.0
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x2a9ef1).cgColor,
UIColor(rgb: 0x72d5fd).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Apple")
case .playMarket:
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x54cb68).cgColor,
UIColor(rgb: 0xa0de7e).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Google")
case .fragment:
self.backgroundView.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1b1f24))
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/Fragment")
iconOffset = 2.0
case .ads:
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0xffa85c).cgColor,
UIColor(rgb: 0xffcd6a).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white)
case .premiumBot:
iconInset = 7.0
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x6b93ff).cgColor,
UIColor(rgb: 0x8d77ff).cgColor,
UIColor(rgb: 0xb56eec).cgColor,
UIColor(rgb: 0xb56eec).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
case .apiLimitExtension:
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0x32b83b).cgColor,
UIColor(rgb: 0x87d93b).cgColor
],
direction: .vertical
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = UIImage(bundleImageName: "Premium/Stars/PaidBroadcast")
case .unsupported:
iconInset = 7.0
self.backgroundView.image = generateGradientFilledCircleImage(
diameter: size.width,
colors: [
UIColor(rgb: 0xb1b1b1).cgColor,
UIColor(rgb: 0xcdcdcd).cgColor
],
direction: .mirroredDiagonal
)
self.backgroundView.isHidden = false
self.iconView.isHidden = false
self.avatarNode.isHidden = true
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchInlineButtonIcon"), color: .white)
}
self.avatarNode.frame = CGRect(origin: .zero, size: size)

View File

@ -255,6 +255,7 @@ public final class StarsImageComponent: Component {
case transactionPeer(StarsContext.State.Transaction.Peer)
case gift(Int32)
case color(UIColor)
case search
public static func == (lhs: StarsImageComponent.Subject, rhs: StarsImageComponent.Subject) -> Bool {
switch lhs {
@ -300,6 +301,12 @@ public final class StarsImageComponent: Component {
} else {
return false
}
case .search:
if case .search = rhs {
return true
} else {
return false
}
}
}
}
@ -880,6 +887,36 @@ public final class StarsImageComponent: Component {
let animationFrame = imageFrame.insetBy(dx: -imageFrame.width * 0.19, dy: -imageFrame.height * 0.19).offsetBy(dx: 0.0, dy: -14.0)
animationNode.frame = animationFrame
animationNode.updateLayout(size: animationFrame.size)
case .search:
let iconBackgroundView: UIImageView
let iconView: UIImageView
if let currentBackground = self.iconBackgroundView, let current = self.iconView {
iconBackgroundView = currentBackground
iconView = current
} else {
iconBackgroundView = UIImageView()
iconView = UIImageView()
containerNode.view.addSubview(iconBackgroundView)
containerNode.view.addSubview(iconView)
self.iconBackgroundView = iconBackgroundView
self.iconView = iconView
}
let iconInset: CGFloat = 11.0
let iconOffset: CGFloat = 0.0
iconBackgroundView.image = generateGradientFilledCircleImage(
diameter: imageSize.width,
colors: [
UIColor(rgb: 0x2a9ef1).cgColor,
UIColor(rgb: 0x72d5fd).cgColor
],
direction: .vertical
)
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/SearchInlineButtonIcon"), color: .white)
iconBackgroundView.frame = imageFrame
iconView.frame = imageFrame.insetBy(dx: iconInset, dy: iconInset).offsetBy(dx: 0.0, dy: iconOffset)
}
if let icon = component.icon {

View File

@ -331,7 +331,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
continue
}
let title = strings.Stars_Purchase_Stars(Int32(product.count))
let title = strings.Stars_Purchase_Stars(Int32(clamping: product.count))
let price = product.price
let titleComponent = AnyComponent(MultilineTextComponent(

View File

@ -245,6 +245,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
var giftAvailability: StarGift.Gift.Availability?
var isRefProgram = false
var isPaidMessage = false
var isPostsSearch = false
var premiumGiftMonths: Int32?
var delayedCloseOnOpenPeer = true
@ -254,7 +255,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
fatalError()
}
let boosts = boost.multiplier
titleText = strings.Stars_Transaction_Giveaway_Boost_Stars(Int32(stars))
titleText = strings.Stars_Transaction_Giveaway_Boost_Stars(Int32(clamping: stars))
descriptionText = ""
boostsText = strings.Stars_Transaction_Giveaway_Boost_Boosts(boosts)
count = CurrencyAmount(amount: StarsAmount(value: stars, nanos: 0), currency: .stars)
@ -471,6 +472,13 @@ private final class StarsTransactionSheetContent: CombinedComponent {
}
transactionPeer = transaction.peer
isReaction = true
} else if transaction.flags.contains(.isPostsSearch) {
titleText = strings.Stars_Transaction_SearchFee_Title
descriptionText = ""
count = transaction.count
transactionId = transaction.id
date = transaction.date
isPostsSearch = true
} else {
switch transaction.peer {
case let .peer(peer):
@ -497,14 +505,29 @@ private final class StarsTransactionSheetContent: CombinedComponent {
case .fragment:
if parentPeer.id == component.context.account.peerId {
if (transaction.count.amount.value < 0 && !transaction.flags.contains(.isRefund)) || (transaction.count.amount.value > 0 && transaction.flags.contains(.isRefund)) {
titleText = strings.Stars_Transaction_FragmentWithdrawal_Title
switch transaction.count.currency {
case .stars:
titleText = strings.Stars_Transaction_FragmentWithdrawal_Title
case .ton:
titleText = strings.Stars_Transaction_FragmentWithdrawalTon_Title
}
via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle
} else {
titleText = strings.Stars_Transaction_FragmentTopUp_Title
switch transaction.count.currency {
case .stars:
titleText = strings.Stars_Transaction_FragmentTopUp_Title
case .ton:
titleText = strings.Stars_Transaction_FragmentTopUpTon_Title
}
via = strings.Stars_Transaction_FragmentTopUp_Subtitle
}
} else {
titleText = strings.Stars_Transaction_FragmentWithdrawal_Title
switch transaction.count.currency {
case .stars:
titleText = strings.Stars_Transaction_FragmentWithdrawal_Title
case .ton:
titleText = strings.Stars_Transaction_FragmentWithdrawalTon_Title
}
via = strings.Stars_Transaction_FragmentWithdrawal_Subtitle
}
case .ads:
@ -718,7 +741,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let imageSubject: StarsImageComponent.Subject
var imageIcon: StarsImageComponent.Icon?
if let premiumGiftMonths {
if isPostsSearch {
imageSubject = .search
} else if let premiumGiftMonths {
imageSubject = .gift(premiumGiftMonths)
} else if isGift {
var value: Int32 = 3
@ -1034,7 +1059,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
id: "prize",
title: strings.Stars_Transaction_Giveaway_Prize,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_Giveaway_Stars(Int32(count.amount.value)), font: tableFont, textColor: tableTextColor)))
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_Giveaway_Stars(Int32(clamping: count.amount.value)), font: tableFont, textColor: tableTextColor)))
)
))
@ -2379,7 +2404,7 @@ private final class PeerCellComponent: Component {
let avatarNaturalSize = self.avatar.update(
transition: .immediate,
component: AnyComponent(
StarsAvatarComponent(context: component.context, theme: component.theme, peer: peer, photo: nil, media: [], uniqueGift: nil, backgroundColor: .clear)
StarsAvatarComponent(context: component.context, theme: component.theme, peer: .transactionPeer(peer), photo: nil, media: [], uniqueGift: nil, backgroundColor: .clear)
),
environment: {},
containerSize: CGSize(width: 40.0, height: 40.0)

View File

@ -924,7 +924,7 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer {
scale: 0.066,
colors: [:],
title: presentationData.strings.Stars_Intro_PurchasedTitle,
text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string,
text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(clamping: stars))).string,
customUndoText: nil,
timeout: nil
),

View File

@ -299,7 +299,7 @@ final class StarsTransactionsListPanelComponent: Component {
var itemTitle: String
let itemSubtitle: String?
var itemDate: String
var itemPeer = item.peer
var itemPeer: StarsAvatarComponent.Peer = .transactionPeer(item.peer)
var itemFile: TelegramMediaFile?
var uniqueGift: StarGift.UniqueGift?
switch item.peer {
@ -307,6 +307,10 @@ final class StarsTransactionsListPanelComponent: Component {
if let months = item.premiumGiftMonths {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
itemSubtitle = environment.strings.Stars_Intro_Transaction_TelegramPremium(months)
} else if item.flags.contains(.isPostsSearch) {
itemTitle = environment.strings.Stars_Intro_Transaction_SearchFee
itemSubtitle = ""
itemPeer = .search
} else if item.flags.contains(.isPaidMessage) {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
itemSubtitle = environment.strings.Stars_Intro_Transaction_PaidMessage(item.paidMessageCount ?? 1)
@ -371,7 +375,7 @@ final class StarsTransactionsListPanelComponent: Component {
if item.flags.contains(.isGift) {
itemTitle = environment.strings.Stars_Intro_Transaction_Gift_UnknownUser
itemSubtitle = environment.strings.Stars_Intro_Transaction_Gift_Title
itemPeer = .fragment
itemPeer = .transactionPeer(.fragment)
} else {
if (item.count.amount.value < 0 && !item.flags.contains(.isRefund)) || (item.count.amount.value > 0 && item.flags.contains(.isRefund)) {
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentWithdrawal_Title

View File

@ -969,7 +969,7 @@ final class StarsTransactionsScreenComponent: Component {
theme: environment.theme,
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)),
contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: .peer(subscription.peer), photo: nil, media: [], uniqueGift: nil, backgroundColor: environment.theme.list.plainBackgroundColor))), false),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: .transactionPeer(.peer(subscription.peer)), photo: nil, media: [], uniqueGift: nil, backgroundColor: environment.theme.list.plainBackgroundColor))), false),
icon: nil,
accessory: .custom(ListActionItemComponent.CustomAccessory(component: labelComponent, insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
action: { [weak self] _ in
@ -1336,7 +1336,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
scale: 0.066,
colors: [:],
title: presentationData.strings.Stars_Intro_PurchasedTitle,
text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(stars))).string,
text: presentationData.strings.Stars_Intro_PurchasedText(presentationData.strings.Stars_Intro_PurchasedText_Stars(Int32(clamping: stars))).string,
customUndoText: nil,
timeout: nil
),

View File

@ -413,9 +413,9 @@ private final class SheetContent: CombinedComponent {
let amount = component.invoice.totalAmount
let infoText: String
if case .starsChatSubscription = context.component.source {
infoText = strings.Stars_Transfer_SubscribeInfo(state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_Info_Stars(Int32(amount))).string
infoText = strings.Stars_Transfer_SubscribeInfo(state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_Info_Stars(Int32(clamping: amount))).string
} else if let _ = component.invoice.subscriptionPeriod {
infoText = strings.Stars_Transfer_BotSubscribeInfo(component.invoice.title, state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_BotSubscribeInfo_Stars(Int32(amount))).string
infoText = strings.Stars_Transfer_BotSubscribeInfo(component.invoice.title, state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_BotSubscribeInfo_Stars(Int32(clamping: amount))).string
} else if !component.extendedMedia.isEmpty {
var description: String = ""
var photoCount: Int32 = 0
@ -447,26 +447,26 @@ private final class SheetContent: CombinedComponent {
infoText = strings.Stars_Transfer_UnlockBotInfo(
description,
authorPeerName,
strings.Stars_Transfer_Info_Stars(Int32(amount))
strings.Stars_Transfer_Info_Stars(Int32(clamping: amount))
).string
} else if let botPeerName = state.botPeer?.compactDisplayTitle {
infoText = strings.Stars_Transfer_UnlockBotInfo(
description,
botPeerName,
strings.Stars_Transfer_Info_Stars(Int32(amount))
strings.Stars_Transfer_Info_Stars(Int32(clamping: amount))
).string
} else {
infoText = strings.Stars_Transfer_UnlockInfo(
description,
state.chatPeer?.compactDisplayTitle ?? "",
strings.Stars_Transfer_Info_Stars(Int32(amount))
strings.Stars_Transfer_Info_Stars(Int32(clamping: amount))
).string
}
} else {
infoText = strings.Stars_Transfer_Info(
component.invoice.title,
state.botPeer?.compactDisplayTitle ?? "",
strings.Stars_Transfer_Info_Stars(Int32(amount))
strings.Stars_Transfer_Info_Stars(Int32(clamping: amount))
).string
}
@ -611,11 +611,11 @@ private final class SheetContent: CombinedComponent {
let text: String
if isSubscription {
title = presentationData.strings.Stars_Transfer_Subscribe_Successful_Title
text = presentationData.strings.Stars_Transfer_Subscribe_Successful_Text(presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount)), botTitle).string
text = presentationData.strings.Stars_Transfer_Subscribe_Successful_Text(presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(clamping: invoice.totalAmount)), botTitle).string
} else if let _ = component.invoice.extendedMedia {
text = presentationData.strings.Stars_Transfer_UnlockedText( presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string
text = presentationData.strings.Stars_Transfer_UnlockedText( presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(clamping: invoice.totalAmount))).string
} else {
text = presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string
text = presentationData.strings.Stars_Transfer_PurchasedText(invoice.title, botTitle, presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(clamping: invoice.totalAmount))).string
}
if let navigationController = controller?.navigationController {

View File

@ -1228,9 +1228,9 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
func presentMinAmountTooltip(_ minAmount: Int64, currency: CurrencyAmount.Currency) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string
var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(clamping: minAmount))).string
if case .starGiftResell = self.mode {
text = presentationData.strings.Stars_SellGiftMinAmountToast_Text("\(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount)))").string
text = presentationData.strings.Stars_SellGiftMinAmountToast_Text("\(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(clamping: minAmount)))").string
} else if case let .suggestedPost(mode, _, _, _) = self.mode {
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
switch currency {

View File

@ -111,7 +111,7 @@ extension ChatControllerImpl {
}
let title = self.presentationData.strings.Chat_PaidMessage_Sent_Title(count)
let text = self.presentationData.strings.Chat_PaidMessage_Sent_Text(self.presentationData.strings.Chat_PaidMessage_Sent_Text_Stars(Int32(amount.value * Int64(count)))).string
let text = self.presentationData.strings.Chat_PaidMessage_Sent_Text(self.presentationData.strings.Chat_PaidMessage_Sent_Text_Stars(Int32(clamping: amount.value * Int64(count)))).string
let textItems: [AnimatedTextComponent.Item] = [
AnimatedTextComponent.Item(id: 0, content: .text(text))
]

View File

@ -756,6 +756,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
var profile: Bool = false
var referrer: String?
var albumId: Int64?
var collectionId: Int64?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
@ -793,6 +794,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
referrer = value
} else if queryItem.name == "album" {
albumId = Int64(value)
} else if queryItem.name == "collection" {
collectionId = Int64(value)
}
} else if ["voicechat", "videochat", "livestream"].contains(queryItem.name) {
voiceChat = ""
@ -866,6 +869,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
result += "?attach=\(attach)"
} else if let albumId {
result += "/a/\(albumId)"
} else if let collectionId {
result += "/c/\(collectionId)"
}
if let startAttach = startAttach {
if attach == nil {