mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-16 08:19:23 +00:00
Stars
This commit is contained in:
parent
51a16c7110
commit
b56a0143f3
@ -287,7 +287,8 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
} else {
|
} else {
|
||||||
labelString = "+ \(formattedLabel)"
|
labelString = "+ \(formattedLabel)"
|
||||||
}
|
}
|
||||||
itemLabel = NSAttributedString(string: labelString, font: Font.medium(fontBaseDisplaySize), textColor: labelString.hasPrefix("-") ? item.presentationData.theme.list.itemDestructiveColor : item.presentationData.theme.list.itemDisclosureActions.constructive.fillColor)
|
let itemLabelColor = labelString.hasPrefix("-") ? item.presentationData.theme.list.itemDestructiveColor : item.presentationData.theme.list.itemDisclosureActions.constructive.fillColor
|
||||||
|
itemLabel = NSAttributedString(string: labelString, font: Font.medium(fontBaseDisplaySize), textColor: itemLabelColor)
|
||||||
|
|
||||||
var itemDateColor = item.presentationData.theme.list.itemSecondaryTextColor
|
var itemDateColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||||
itemDate = stringForMediumCompactDate(timestamp: item.transaction.date, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
|
itemDate = stringForMediumCompactDate(timestamp: item.transaction.date, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
|
||||||
@ -341,7 +342,7 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.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: item.transaction.peer, photo: nil, media: [], uniqueGift: nil, backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor))), false),
|
||||||
icon: nil,
|
icon: nil,
|
||||||
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
|
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(theme: item.presentationData.theme, currency: item.transaction.currency, textColor: itemLabelColor, text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
|
||||||
action: { [weak self] _ in
|
action: { [weak self] _ in
|
||||||
guard let self, let item = self.item else {
|
guard let self, let item = self.item else {
|
||||||
return
|
return
|
||||||
|
@ -1016,8 +1016,8 @@ extension StoreMessage {
|
|||||||
attributes.append(SuggestedPostMessageAttribute(apiSuggestedPost: suggestedPost))
|
attributes.append(SuggestedPostMessageAttribute(apiSuggestedPost: suggestedPost))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags2 & (1 << 8)) != 0 {
|
if (flags2 & (1 << 8)) != 0 || (flags2 & (1 << 9)) != 0 {
|
||||||
attributes.append(PublishedSuggestedPostMessageAttribute())
|
attributes.append(PublishedSuggestedPostMessageAttribute(currency: (flags2 & (1 << 8)) != 0 ? .stars : .ton))
|
||||||
}
|
}
|
||||||
|
|
||||||
var storeFlags = StoreMessageFlags()
|
var storeFlags = StoreMessageFlags()
|
||||||
|
@ -93,16 +93,24 @@ extension SuggestedPostMessageAttribute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class PublishedSuggestedPostMessageAttribute: Equatable, MessageAttribute {
|
public final class PublishedSuggestedPostMessageAttribute: Equatable, MessageAttribute {
|
||||||
public init() {
|
public let currency: CurrencyAmount.Currency
|
||||||
|
|
||||||
|
public init(currency: CurrencyAmount.Currency) {
|
||||||
|
self.currency = currency
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
|
self.currency = CurrencyAmount.Currency(rawValue: decoder.decodeInt32ForKey("c", orElse: 0)) ?? .stars
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
|
encoder.encodeInt32(self.currency.rawValue, forKey: "c")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func == (lhs: PublishedSuggestedPostMessageAttribute, rhs: PublishedSuggestedPostMessageAttribute) -> Bool {
|
public static func == (lhs: PublishedSuggestedPostMessageAttribute, rhs: PublishedSuggestedPostMessageAttribute) -> Bool {
|
||||||
|
if lhs.currency != rhs.currency {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -618,7 +618,7 @@ private final class StarsContextImpl {
|
|||||||
}
|
}
|
||||||
var transactions = state.transactions
|
var transactions = state.transactions
|
||||||
if addTransaction {
|
if addTransaction {
|
||||||
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, giveawayMessageId: nil, media: [], subscriptionPeriod: nil, starGift: nil, floodskipNumber: nil, starrefCommissionPermille: nil, starrefPeerId: nil, starrefAmount: nil, paidMessageCount: nil, premiumGiftMonths: nil), at: 0)
|
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, currency: self.ton ? .ton : .stars, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, giveawayMessageId: nil, media: [], subscriptionPeriod: nil, starGift: nil, floodskipNumber: nil, starrefCommissionPermille: nil, starrefPeerId: nil, starrefAmount: nil, paidMessageCount: nil, premiumGiftMonths: nil), at: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: max(StarsAmount(value: 0, nanos: 0), state.balance + balance), subscriptions: state.subscriptions, canLoadMoreSubscriptions: state.canLoadMoreSubscriptions, transactions: transactions, canLoadMoreTransactions: state.canLoadMoreTransactions, isLoading: state.isLoading))
|
self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: max(StarsAmount(value: 0, nanos: 0), state.balance + balance), subscriptions: state.subscriptions, canLoadMoreSubscriptions: state.canLoadMoreSubscriptions, transactions: transactions, canLoadMoreTransactions: state.canLoadMoreTransactions, isLoading: state.isLoading))
|
||||||
@ -723,8 +723,10 @@ private extension StarsContext.State.Transaction {
|
|||||||
|
|
||||||
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
|
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
|
||||||
let _ = subscriptionPeriod
|
let _ = subscriptionPeriod
|
||||||
|
|
||||||
|
let amount = CurrencyAmount(apiAmount: stars)
|
||||||
|
|
||||||
self.init(flags: flags, id: id, count: StarsAmount(apiAmount: stars), date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, giveawayMessageId: giveawayMessageId, media: media, subscriptionPeriod: subscriptionPeriod, starGift: starGift.flatMap { StarGift(apiStarGift: $0) }, floodskipNumber: floodskipNumber, starrefCommissionPermille: starrefCommissionPermille, starrefPeerId: starrefPeer?.peerId, starrefAmount: starrefAmount.flatMap(StarsAmount.init(apiAmount:)), paidMessageCount: paidMessageCount, premiumGiftMonths: premiumGiftMonths)
|
self.init(flags: flags, id: id, count: amount.amount, currency: amount.currency, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, giveawayMessageId: giveawayMessageId, media: media, subscriptionPeriod: subscriptionPeriod, starGift: starGift.flatMap { StarGift(apiStarGift: $0) }, floodskipNumber: floodskipNumber, starrefCommissionPermille: starrefCommissionPermille, starrefPeerId: starrefPeer?.peerId, starrefAmount: starrefAmount.flatMap(StarsAmount.init(apiAmount:)), paidMessageCount: paidMessageCount, premiumGiftMonths: premiumGiftMonths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -790,6 +792,7 @@ public final class StarsContext {
|
|||||||
public let flags: Flags
|
public let flags: Flags
|
||||||
public let id: String
|
public let id: String
|
||||||
public let count: StarsAmount
|
public let count: StarsAmount
|
||||||
|
public let currency: CurrencyAmount.Currency
|
||||||
public let date: Int32
|
public let date: Int32
|
||||||
public let peer: Peer
|
public let peer: Peer
|
||||||
public let title: String?
|
public let title: String?
|
||||||
@ -813,6 +816,7 @@ public final class StarsContext {
|
|||||||
flags: Flags,
|
flags: Flags,
|
||||||
id: String,
|
id: String,
|
||||||
count: StarsAmount,
|
count: StarsAmount,
|
||||||
|
currency: CurrencyAmount.Currency,
|
||||||
date: Int32,
|
date: Int32,
|
||||||
peer: Peer,
|
peer: Peer,
|
||||||
title: String?,
|
title: String?,
|
||||||
@ -835,6 +839,7 @@ public final class StarsContext {
|
|||||||
self.flags = flags
|
self.flags = flags
|
||||||
self.id = id
|
self.id = id
|
||||||
self.count = count
|
self.count = count
|
||||||
|
self.currency = currency
|
||||||
self.date = date
|
self.date = date
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.title = title
|
self.title = title
|
||||||
@ -1074,7 +1079,7 @@ public final class StarsContext {
|
|||||||
return peerId!
|
return peerId!
|
||||||
}
|
}
|
||||||
|
|
||||||
let ton: Bool
|
public let ton: Bool
|
||||||
|
|
||||||
public var currentState: StarsContext.State? {
|
public var currentState: StarsContext.State? {
|
||||||
var state: StarsContext.State?
|
var state: StarsContext.State?
|
||||||
|
@ -63,6 +63,17 @@ private func loadCurrencyFormatterEntries() -> [String: CurrencyFormatterEntry]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tonEntry = CurrencyFormatterEntry(
|
||||||
|
symbol: "TON",
|
||||||
|
thousandsSeparator: ".",
|
||||||
|
decimalSeparator: ",",
|
||||||
|
symbolOnLeft: true,
|
||||||
|
spaceBetweenAmountAndSymbol: false,
|
||||||
|
decimalDigits: 9
|
||||||
|
)
|
||||||
|
result["TON"] = tonEntry
|
||||||
|
result["ton"] = tonEntry
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1106,10 +1106,17 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||||||
var range = NSRange(location: NSNotFound, length: 0)
|
var range = NSRange(location: NSNotFound, length: 0)
|
||||||
range = (mutableString.string as NSString).range(of: "{amount}")
|
range = (mutableString.string as NSString).range(of: "{amount}")
|
||||||
if range.location != NSNotFound {
|
if range.location != NSNotFound {
|
||||||
if currency == "XTR" {
|
if currency == "TON" {
|
||||||
|
let amountAttributedString = NSMutableAttributedString(string: "#\(formatTonAmountText(totalAmount, dateTimeFormat: dateTimeFormat))", font: titleBoldFont, textColor: primaryTextColor)
|
||||||
|
if let range = amountAttributedString.string.range(of: "#") {
|
||||||
|
amountAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: true)), range: NSRange(range, in: amountAttributedString.string))
|
||||||
|
amountAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: amountAttributedString.string))
|
||||||
|
}
|
||||||
|
mutableString.replaceCharacters(in: range, with: amountAttributedString)
|
||||||
|
} else if currency == "XTR" {
|
||||||
let amountAttributedString = NSMutableAttributedString(string: "#\(totalAmount)", font: titleBoldFont, textColor: primaryTextColor)
|
let amountAttributedString = NSMutableAttributedString(string: "#\(totalAmount)", font: titleBoldFont, textColor: primaryTextColor)
|
||||||
if let range = amountAttributedString.string.range(of: "#") {
|
if let range = amountAttributedString.string.range(of: "#") {
|
||||||
amountAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: NSRange(range, in: amountAttributedString.string))
|
amountAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: amountAttributedString.string))
|
||||||
amountAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: amountAttributedString.string))
|
amountAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: amountAttributedString.string))
|
||||||
}
|
}
|
||||||
mutableString.replaceCharacters(in: range, with: amountAttributedString)
|
mutableString.replaceCharacters(in: range, with: amountAttributedString)
|
||||||
|
@ -112,6 +112,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
private var fetchDisposable: Disposable?
|
private var fetchDisposable: Disposable?
|
||||||
private var setupTimestamp: Double?
|
private var setupTimestamp: Double?
|
||||||
|
|
||||||
|
private var cachedTonImage: (UIImage, UIColor)?
|
||||||
|
|
||||||
required public init() {
|
required public init() {
|
||||||
self.labelNode = TextNode()
|
self.labelNode = TextNode()
|
||||||
self.labelNode.isUserInteractionEnabled = false
|
self.labelNode.isUserInteractionEnabled = false
|
||||||
@ -339,6 +341,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let currentIsExpanded = self.isExpanded
|
let currentIsExpanded = self.isExpanded
|
||||||
|
|
||||||
|
let cachedTonImage = self.cachedTonImage
|
||||||
|
|
||||||
return { item, layoutConstants, _, _, _, _ in
|
return { item, layoutConstants, _, _, _, _ in
|
||||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
|
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
|
||||||
|
|
||||||
@ -425,8 +429,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let cryptoAmount = cryptoAmount ?? 0
|
let cryptoAmount = cryptoAmount ?? 0
|
||||||
|
|
||||||
title = item.presentationData.strings.Notification_StarsGift_Title(Int32(cryptoAmount))
|
title = "$ \(formatTonAmountText(cryptoAmount, dateTimeFormat: item.presentationData.dateTimeFormat))"
|
||||||
text = incoming ? item.presentationData.strings.Notification_StarsGift_Subtitle : item.presentationData.strings.Notification_StarsGift_SubtitleYou(peerName).string
|
text = incoming ? "Use TON to unlock content and services on Telegram." : "With TON, \(peerName) will be able to unlock content and services on Telegram."
|
||||||
case let .prizeStars(count, _, channelId, _, _):
|
case let .prizeStars(count, _, channelId, _, _):
|
||||||
if count <= 1000 {
|
if count <= 1000 {
|
||||||
months = 3
|
months = 3
|
||||||
@ -596,7 +600,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
} else {
|
} else {
|
||||||
title = isStoryEntity ? uniqueGift.title : item.presentationData.strings.Notification_StarGift_Title(authorName).string
|
title = isStoryEntity ? uniqueGift.title : item.presentationData.strings.Notification_StarGift_Title(authorName).string
|
||||||
}
|
}
|
||||||
text = isStoryEntity ? "**\(item.presentationData.strings.Notification_StarGift_Collectible) #\(presentationStringsFormattedNumber(uniqueGift.number, item.presentationData.dateTimeFormat.groupingSeparator))**" : "**\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, item.presentationData.dateTimeFormat.groupingSeparator))**"
|
text = isStoryEntity ? "**\(item.presentationData.strings.Notification_StarGift_Collectible) #\(presentationStringsFormattedNumber(uniqueGift.number, item.presentationData.dateTimeFormat.groupingSeparator))**" : "**\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, item.presentationData.dateTimeFormat.groupingSeparator))**"
|
||||||
ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift
|
ribbonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_Gift
|
||||||
buttonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_View
|
buttonTitle = isStoryEntity ? "" : item.presentationData.strings.Notification_StarGift_View
|
||||||
modelTitle = item.presentationData.strings.Notification_StarGift_Model
|
modelTitle = item.presentationData.strings.Notification_StarGift_Model
|
||||||
@ -648,7 +652,32 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let titleAttributedString = NSMutableAttributedString(attributedString: NSAttributedString(string: title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center))
|
||||||
|
var updatedCachedTonImage: (UIImage, UIColor)? = cachedTonImage
|
||||||
|
if let range = titleAttributedString.string.range(of: "$") {
|
||||||
|
if updatedCachedTonImage == nil || updatedCachedTonImage?.1 != primaryTextColor {
|
||||||
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonAbout"), color: primaryTextColor) {
|
||||||
|
let imageScale: CGFloat = 0.8
|
||||||
|
let imageSize = CGSize(width: floor(image.size.width * imageScale), height: floor(image.size.height * imageScale))
|
||||||
|
updatedCachedTonImage = (generateImage(CGSize(width: imageSize.width + 2.0, height: imageSize.height), opaque: false, scale: nil, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
defer {
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
}
|
||||||
|
image.draw(in: CGRect(origin: CGPoint(x: 2.0, y: 0.0), size: imageSize))
|
||||||
|
})!, primaryTextColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let tonImage = updatedCachedTonImage?.0 {
|
||||||
|
titleAttributedString.addAttribute(.attachment, value: tonImage, range: NSRange(range, in: titleAttributedString.string))
|
||||||
|
titleAttributedString.addAttribute(.foregroundColor, value: primaryTextColor, range: NSRange(range, in: titleAttributedString.string))
|
||||||
|
titleAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: titleAttributedString.string))
|
||||||
|
titleAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: titleAttributedString.string))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
let (moreLayout, moreApply) = makeMoreTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_More, font: Font.semibold(13.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
let (moreLayout, moreApply) = makeMoreTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_More, font: Font.semibold(13.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
@ -853,6 +882,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
strongSelf.animationNode.updateLayout(size: iconSize)
|
strongSelf.animationNode.updateLayout(size: iconSize)
|
||||||
strongSelf.placeholderNode.frame = animationFrame
|
strongSelf.placeholderNode.frame = animationFrame
|
||||||
|
|
||||||
|
strongSelf.cachedTonImage = updatedCachedTonImage
|
||||||
|
|
||||||
let _ = labelApply()
|
let _ = labelApply()
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = subtitleApply(TextNodeWithEntities.Arguments(
|
let _ = subtitleApply(TextNodeWithEntities.Arguments(
|
||||||
|
@ -487,8 +487,11 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
|||||||
if tinted {
|
if tinted {
|
||||||
self.updateTintColor()
|
self.updateTintColor()
|
||||||
}
|
}
|
||||||
case .ton:
|
case let .ton(tinted):
|
||||||
self.updateTon()
|
self.updateTon(tinted: tinted)
|
||||||
|
if tinted {
|
||||||
|
self.updateTintColor()
|
||||||
|
}
|
||||||
case let .animation(name):
|
case let .animation(name):
|
||||||
self.updateLocalAnimation(name: name, attemptSynchronousLoad: attemptSynchronousLoad)
|
self.updateLocalAnimation(name: name, attemptSynchronousLoad: attemptSynchronousLoad)
|
||||||
case .verification:
|
case .verification:
|
||||||
@ -581,7 +584,7 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
|||||||
}
|
}
|
||||||
} else if let emoji = self.arguments?.emoji, let custom = emoji.custom {
|
} else if let emoji = self.arguments?.emoji, let custom = emoji.custom {
|
||||||
switch custom {
|
switch custom {
|
||||||
case .stars(true), .verification:
|
case .stars(true), .ton(true), .verification:
|
||||||
customColor = self.dynamicColor
|
customColor = self.dynamicColor
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -687,8 +690,8 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
|||||||
self.contents = tinted ? tintedStarImage?.cgImage : starImage?.cgImage
|
self.contents = tinted ? tintedStarImage?.cgImage : starImage?.cgImage
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateTon() {
|
private func updateTon(tinted: Bool) {
|
||||||
self.contents = tonImage?.cgImage
|
self.contents = tinted ? tintedTonImage?.cgImage : tonImage?.cgImage
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateVerification() {
|
private func updateVerification() {
|
||||||
@ -1053,6 +1056,16 @@ private let tonImage: UIImage? = {
|
|||||||
})?.withRenderingMode(.alwaysTemplate)
|
})?.withRenderingMode(.alwaysTemplate)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
private let tintedTonImage: UIImage? = {
|
||||||
|
generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in
|
||||||
|
context.clear(CGRect(origin: .zero, size: size))
|
||||||
|
|
||||||
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonBig"), color: .white), let cgImage = image.cgImage {
|
||||||
|
context.draw(cgImage, in: CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 4.0), byTiling: false)
|
||||||
|
}
|
||||||
|
})?.withRenderingMode(.alwaysTemplate)
|
||||||
|
}()
|
||||||
|
|
||||||
private let verificationImage: UIImage? = {
|
private let verificationImage: UIImage? = {
|
||||||
if let backgroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconForeground") {
|
if let backgroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconForeground") {
|
||||||
return generateImage(backgroundImage.size, contextGenerator: { size, context in
|
return generateImage(backgroundImage.size, contextGenerator: { size, context in
|
||||||
|
@ -1609,7 +1609,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
let string = "*\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))"
|
let string = "*\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||||
let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
if let range = attributedString.string.range(of: "*") {
|
if let range = attributedString.string.range(of: "*") {
|
||||||
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton), range: NSRange(range, in: attributedString.string))
|
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: NSRange(range, in: attributedString.string))
|
||||||
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
|
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
|
||||||
}
|
}
|
||||||
items[.balances]!.append(PeerInfoScreenDisclosureItem(id: 21, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_BotBalance_Ton, icon: PresentationResourcesSettings.ton, action: {
|
items[.balances]!.append(PeerInfoScreenDisclosureItem(id: 21, label: .attributedText(attributedString), text: presentationData.strings.PeerInfo_BotBalance_Ton, icon: PresentationResourcesSettings.ton, action: {
|
||||||
@ -1933,7 +1933,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
attributedString.append(starsAttributedString)
|
attributedString.append(starsAttributedString)
|
||||||
}
|
}
|
||||||
if let range = attributedString.string.range(of: "#") {
|
if let range = attributedString.string.range(of: "#") {
|
||||||
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton), range: NSRange(range, in: attributedString.string))
|
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: NSRange(range, in: attributedString.string))
|
||||||
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
|
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
|
||||||
}
|
}
|
||||||
if let range = attributedString.string.range(of: "*") {
|
if let range = attributedString.string.range(of: "*") {
|
||||||
|
@ -372,18 +372,36 @@ public final class StarsAvatarComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class StarsLabelComponent: CombinedComponent {
|
public final class StarsLabelComponent: CombinedComponent {
|
||||||
|
let theme: PresentationTheme
|
||||||
|
let currency: CurrencyAmount.Currency
|
||||||
|
let textColor: UIColor
|
||||||
let text: NSAttributedString
|
let text: NSAttributedString
|
||||||
let subtext: NSAttributedString?
|
let subtext: NSAttributedString?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
theme: PresentationTheme,
|
||||||
|
currency: CurrencyAmount.Currency,
|
||||||
|
textColor: UIColor,
|
||||||
text: NSAttributedString,
|
text: NSAttributedString,
|
||||||
subtext: NSAttributedString? = nil
|
subtext: NSAttributedString? = nil
|
||||||
) {
|
) {
|
||||||
|
self.currency = currency
|
||||||
|
self.theme = theme
|
||||||
|
self.textColor = textColor
|
||||||
self.text = text
|
self.text = text
|
||||||
self.subtext = subtext
|
self.subtext = subtext
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: StarsLabelComponent, rhs: StarsLabelComponent) -> Bool {
|
public static func ==(lhs: StarsLabelComponent, rhs: StarsLabelComponent) -> Bool {
|
||||||
|
if lhs.currency != rhs.currency {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.theme !== rhs.theme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.textColor != rhs.textColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.text != rhs.text {
|
if lhs.text != rhs.text {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -406,7 +424,6 @@ public final class StarsLabelComponent: CombinedComponent {
|
|||||||
availableSize: CGSize(width: 140.0, height: 40.0),
|
availableSize: CGSize(width: 140.0, height: 40.0),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
var subtext: _UpdatedChildComponent? = nil
|
var subtext: _UpdatedChildComponent? = nil
|
||||||
if let sublabel = component.subtext {
|
if let sublabel = component.subtext {
|
||||||
@ -417,11 +434,12 @@ public final class StarsLabelComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconSize = CGSize(width: 20.0, height: 20.0)
|
let iconSize: CGSize = component.currency == .ton ? CGSize(width: 16.0, height: 16.0) : CGSize(width: 20.0, height: 20.0)
|
||||||
let icon = icon.update(
|
let icon = icon.update(
|
||||||
component: BundleIconComponent(
|
component: BundleIconComponent(
|
||||||
name: "Premium/Stars/StarMedium",
|
name: component.currency == .ton ? "Ads/TonBig" : "Premium/Stars/StarMedium",
|
||||||
tintColor: nil
|
tintColor: component.currency == .ton ? component.textColor : nil,
|
||||||
|
maxSize: iconSize
|
||||||
),
|
),
|
||||||
availableSize: iconSize,
|
availableSize: iconSize,
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
|
@ -306,6 +306,7 @@ public final class StarsImageComponent: Component {
|
|||||||
|
|
||||||
public enum Icon {
|
public enum Icon {
|
||||||
case star
|
case star
|
||||||
|
case ton
|
||||||
}
|
}
|
||||||
|
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
@ -865,7 +866,7 @@ public final class StarsImageComponent: Component {
|
|||||||
animationNode.updateLayout(size: animationFrame.size)
|
animationNode.updateLayout(size: animationFrame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let _ = component.icon {
|
if let icon = component.icon {
|
||||||
let smallIconView: UIImageView
|
let smallIconView: UIImageView
|
||||||
let smallIconOutlineView: UIImageView
|
let smallIconOutlineView: UIImageView
|
||||||
if let current = self.smallIconView, let currentOutline = self.smallIconOutlineView {
|
if let current = self.smallIconView, let currentOutline = self.smallIconOutlineView {
|
||||||
@ -880,15 +881,27 @@ public final class StarsImageComponent: Component {
|
|||||||
containerNode.view.addSubview(smallIconView)
|
containerNode.view.addSubview(smallIconView)
|
||||||
self.smallIconView = smallIconView
|
self.smallIconView = smallIconView
|
||||||
|
|
||||||
smallIconOutlineView.image = UIImage(bundleImageName: "Premium/Stars/TransactionStarOutline")?.withRenderingMode(.alwaysTemplate)
|
switch icon {
|
||||||
smallIconView.image = UIImage(bundleImageName: "Premium/Stars/TransactionStar")
|
case .star:
|
||||||
|
smallIconOutlineView.image = UIImage(bundleImageName: "Premium/Stars/TransactionStarOutline")?.withRenderingMode(.alwaysTemplate)
|
||||||
|
smallIconView.image = UIImage(bundleImageName: "Premium/Stars/TransactionStar")
|
||||||
|
case .ton:
|
||||||
|
smallIconOutlineView.image = UIImage(bundleImageName: "Ads/TonMedium")?.withRenderingMode(.alwaysTemplate)
|
||||||
|
smallIconView.image = UIImage(bundleImageName: "Ads/TonMedium")?.withRenderingMode(.alwaysTemplate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
smallIconOutlineView.tintColor = component.backgroundColor
|
smallIconOutlineView.tintColor = component.backgroundColor
|
||||||
|
|
||||||
if let icon = smallIconView.image {
|
if let iconImage = smallIconView.image {
|
||||||
let smallIconFrame = CGRect(origin: CGPoint(x: imageFrame.maxX - icon.size.width, y: imageFrame.maxY - icon.size.height), size: icon.size)
|
let smallIconFrame = CGRect(origin: CGPoint(x: imageFrame.maxX - iconImage.size.width, y: imageFrame.maxY - iconImage.size.height), size: iconImage.size)
|
||||||
smallIconView.frame = smallIconFrame
|
smallIconView.frame = smallIconFrame
|
||||||
|
switch icon {
|
||||||
|
case .star:
|
||||||
|
smallIconView.tintColor = nil
|
||||||
|
case .ton:
|
||||||
|
smallIconView.tintColor = component.theme.list.itemAccentColor
|
||||||
|
}
|
||||||
smallIconOutlineView.frame = smallIconFrame
|
smallIconOutlineView.frame = smallIconFrame
|
||||||
}
|
}
|
||||||
} else if let smallIconView = self.smallIconView, let smallIconOutlineView = self.smallIconOutlineView {
|
} else if let smallIconView = self.smallIconView, let smallIconOutlineView = self.smallIconOutlineView {
|
||||||
|
@ -217,7 +217,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
var statusText: String?
|
var statusText: String?
|
||||||
var statusIsDestructive = false
|
var statusIsDestructive = false
|
||||||
|
|
||||||
let count: StarsAmount
|
let count: CurrencyAmount
|
||||||
var countIsGeneric = false
|
var countIsGeneric = false
|
||||||
var countOnTop = false
|
var countOnTop = false
|
||||||
var transactionId: String?
|
var transactionId: String?
|
||||||
@ -257,7 +257,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
titleText = strings.Stars_Transaction_Giveaway_Boost_Stars(Int32(stars))
|
titleText = strings.Stars_Transaction_Giveaway_Boost_Stars(Int32(stars))
|
||||||
descriptionText = ""
|
descriptionText = ""
|
||||||
boostsText = strings.Stars_Transaction_Giveaway_Boost_Boosts(boosts)
|
boostsText = strings.Stars_Transaction_Giveaway_Boost_Boosts(boosts)
|
||||||
count = StarsAmount(value: stars, nanos: 0)
|
count = CurrencyAmount(amount: StarsAmount(value: stars, nanos: 0), currency: .stars)
|
||||||
date = boost.date
|
date = boost.date
|
||||||
toPeer = state.peerMap[peerId]
|
toPeer = state.peerMap[peerId]
|
||||||
giveawayMessageId = boost.giveawayMessageId
|
giveawayMessageId = boost.giveawayMessageId
|
||||||
@ -266,7 +266,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
let usdValue = formatTonUsdValue(pricing.amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat)
|
let usdValue = formatTonUsdValue(pricing.amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat)
|
||||||
titleText = strings.Stars_Transaction_Subscription_Title
|
titleText = strings.Stars_Transaction_Subscription_Title
|
||||||
descriptionText = strings.Stars_Transaction_Subscription_PerMonthUsd(usdValue).string
|
descriptionText = strings.Stars_Transaction_Subscription_PerMonthUsd(usdValue).string
|
||||||
count = pricing.amount
|
count = CurrencyAmount(amount: pricing.amount, currency: .stars)
|
||||||
countOnTop = true
|
countOnTop = true
|
||||||
date = importer.date
|
date = importer.date
|
||||||
toPeer = importer.peer.peer.flatMap(EnginePeer.init)
|
toPeer = importer.peer.peer.flatMap(EnginePeer.init)
|
||||||
@ -288,7 +288,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
photo = subscription.photo
|
photo = subscription.photo
|
||||||
|
|
||||||
descriptionText = ""
|
descriptionText = ""
|
||||||
count = subscription.pricing.amount
|
count = CurrencyAmount(amount: subscription.pricing.amount, currency: .stars)
|
||||||
date = subscription.untilDate
|
date = subscription.untilDate
|
||||||
if let creationDate = (subscription.peer._asPeer() as? TelegramChannel)?.creationDate, creationDate > 0 {
|
if let creationDate = (subscription.peer._asPeer() as? TelegramChannel)?.creationDate, creationDate > 0 {
|
||||||
additionalDate = creationDate
|
additionalDate = creationDate
|
||||||
@ -376,7 +376,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
titleText = gift.title
|
titleText = gift.title
|
||||||
descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))"
|
descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))"
|
||||||
}
|
}
|
||||||
count = transaction.count
|
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency)
|
||||||
transactionId = transaction.id
|
transactionId = transaction.id
|
||||||
date = transaction.date
|
date = transaction.date
|
||||||
if case let .peer(peer) = transaction.peer {
|
if case let .peer(peer) = transaction.peer {
|
||||||
@ -395,7 +395,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
} else if let giveawayMessageIdValue = transaction.giveawayMessageId {
|
} else if let giveawayMessageIdValue = transaction.giveawayMessageId {
|
||||||
titleText = strings.Stars_Transaction_Giveaway_Title
|
titleText = strings.Stars_Transaction_Giveaway_Title
|
||||||
descriptionText = ""
|
descriptionText = ""
|
||||||
count = transaction.count
|
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency)
|
||||||
transactionId = transaction.id
|
transactionId = transaction.id
|
||||||
date = transaction.date
|
date = transaction.date
|
||||||
giveawayMessageId = giveawayMessageIdValue
|
giveawayMessageId = giveawayMessageIdValue
|
||||||
@ -406,7 +406,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
} else if let _ = transaction.subscriptionPeriod {
|
} else if let _ = transaction.subscriptionPeriod {
|
||||||
titleText = strings.Stars_Transaction_SubscriptionFee
|
titleText = strings.Stars_Transaction_SubscriptionFee
|
||||||
descriptionText = ""
|
descriptionText = ""
|
||||||
count = transaction.count
|
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency)
|
||||||
transactionId = transaction.id
|
transactionId = transaction.id
|
||||||
date = transaction.date
|
date = transaction.date
|
||||||
if case let .peer(peer) = transaction.peer {
|
if case let .peer(peer) = transaction.peer {
|
||||||
@ -417,7 +417,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
} else if transaction.flags.contains(.isGift) {
|
} else if transaction.flags.contains(.isGift) {
|
||||||
titleText = strings.Stars_Gift_Received_Title
|
titleText = strings.Stars_Gift_Received_Title
|
||||||
descriptionText = strings.Stars_Gift_Received_Text
|
descriptionText = strings.Stars_Gift_Received_Text
|
||||||
count = transaction.count
|
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency)
|
||||||
countOnTop = true
|
countOnTop = true
|
||||||
transactionId = transaction.id
|
transactionId = transaction.id
|
||||||
date = transaction.date
|
date = transaction.date
|
||||||
@ -446,7 +446,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
countOnTop = false
|
countOnTop = false
|
||||||
descriptionText = ""
|
descriptionText = ""
|
||||||
}
|
}
|
||||||
count = transaction.count
|
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency)
|
||||||
transactionId = transaction.id
|
transactionId = transaction.id
|
||||||
date = transaction.date
|
date = transaction.date
|
||||||
transactionPeer = transaction.peer
|
transactionPeer = transaction.peer
|
||||||
@ -457,7 +457,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
titleText = strings.Stars_Transaction_Reaction_Title
|
titleText = strings.Stars_Transaction_Reaction_Title
|
||||||
descriptionText = ""
|
descriptionText = ""
|
||||||
messageId = transaction.paidMessageId
|
messageId = transaction.paidMessageId
|
||||||
count = transaction.count
|
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency)
|
||||||
transactionId = transaction.id
|
transactionId = transaction.id
|
||||||
date = transaction.date
|
date = transaction.date
|
||||||
if case let .peer(peer) = transaction.peer {
|
if case let .peer(peer) = transaction.peer {
|
||||||
@ -545,7 +545,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
|
|
||||||
messageId = transaction.paidMessageId
|
messageId = transaction.paidMessageId
|
||||||
|
|
||||||
count = transaction.count
|
count = CurrencyAmount(amount: transaction.count, currency: transaction.currency)
|
||||||
transactionId = transaction.id
|
transactionId = transaction.id
|
||||||
date = transaction.date
|
date = transaction.date
|
||||||
if case let .peer(peer) = transaction.peer {
|
if case let .peer(peer) = transaction.peer {
|
||||||
@ -564,7 +564,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
case let .receipt(receipt):
|
case let .receipt(receipt):
|
||||||
titleText = receipt.invoiceMedia.title
|
titleText = receipt.invoiceMedia.title
|
||||||
descriptionText = receipt.invoiceMedia.description
|
descriptionText = receipt.invoiceMedia.description
|
||||||
count = StarsAmount(value: (receipt.invoice.prices.first?.amount ?? receipt.invoiceMedia.totalAmount) * -1, nanos: 0)
|
count = CurrencyAmount(amount: StarsAmount(value: (receipt.invoice.prices.first?.amount ?? receipt.invoiceMedia.totalAmount) * -1, nanos: 0), currency: .stars)
|
||||||
transactionId = receipt.transactionId
|
transactionId = receipt.transactionId
|
||||||
date = receipt.date
|
date = receipt.date
|
||||||
if let peer = state.peerMap[receipt.botPaymentId] {
|
if let peer = state.peerMap[receipt.botPaymentId] {
|
||||||
@ -581,7 +581,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
if case let .giftStars(_, _, countValue, _, _, _) = action.action {
|
if case let .giftStars(_, _, countValue, _, _, _) = action.action {
|
||||||
titleText = incoming ? strings.Stars_Gift_Received_Title : strings.Stars_Gift_Sent_Title
|
titleText = incoming ? strings.Stars_Gift_Received_Title : strings.Stars_Gift_Sent_Title
|
||||||
|
|
||||||
count = StarsAmount(value: countValue, nanos: 0)
|
count = CurrencyAmount(amount: StarsAmount(value: countValue, nanos: 0), currency: .stars)
|
||||||
if !incoming {
|
if !incoming {
|
||||||
countIsGeneric = true
|
countIsGeneric = true
|
||||||
}
|
}
|
||||||
@ -595,7 +595,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
} else if case let .prizeStars(countValue, _, boostPeerId, _, giveawayMessageIdValue) = action.action {
|
} else if case let .prizeStars(countValue, _, boostPeerId, _, giveawayMessageIdValue) = action.action {
|
||||||
titleText = strings.Stars_Transaction_Giveaway_Title
|
titleText = strings.Stars_Transaction_Giveaway_Title
|
||||||
|
|
||||||
count = StarsAmount(value: countValue, nanos: 0)
|
count = CurrencyAmount(amount: StarsAmount(value: countValue, nanos: 0), currency: .stars)
|
||||||
countOnTop = true
|
countOnTop = true
|
||||||
transactionId = nil
|
transactionId = nil
|
||||||
giveawayMessageId = giveawayMessageIdValue
|
giveawayMessageId = giveawayMessageIdValue
|
||||||
@ -648,8 +648,14 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
headerTextColor = theme.actionSheet.primaryTextColor
|
headerTextColor = theme.actionSheet.primaryTextColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let absCount = StarsAmount(value: abs(count.value), nanos: abs(count.nanos))
|
let absCount = StarsAmount(value: abs(count.amount.value), nanos: abs(count.amount.nanos))
|
||||||
let formattedAmount = formatStarsAmountText(absCount, dateTimeFormat: dateTimeFormat)
|
let formattedAmount: String
|
||||||
|
switch count.currency {
|
||||||
|
case .stars:
|
||||||
|
formattedAmount = formatStarsAmountText(absCount, dateTimeFormat: dateTimeFormat)
|
||||||
|
case .ton:
|
||||||
|
formattedAmount = formatTonAmountText(absCount.value, dateTimeFormat: dateTimeFormat)
|
||||||
|
}
|
||||||
let countColor: UIColor
|
let countColor: UIColor
|
||||||
var countFont: UIFont = isSubscription || isSubscriber ? Font.regular(17.0) : Font.semibold(17.0)
|
var countFont: UIFont = isSubscription || isSubscriber ? Font.regular(17.0) : Font.semibold(17.0)
|
||||||
var countBackgroundColor: UIColor?
|
var countBackgroundColor: UIColor?
|
||||||
@ -664,7 +670,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
} else if countIsGeneric {
|
} else if countIsGeneric {
|
||||||
amountText = "\(formattedAmount)"
|
amountText = "\(formattedAmount)"
|
||||||
countColor = theme.list.itemPrimaryTextColor
|
countColor = theme.list.itemPrimaryTextColor
|
||||||
} else if count < StarsAmount.zero {
|
} else if count.amount < StarsAmount.zero {
|
||||||
amountText = "- \(formattedAmount)"
|
amountText = "- \(formattedAmount)"
|
||||||
if case .unique = giftAnimationSubject {
|
if case .unique = giftAnimationSubject {
|
||||||
countColor = .white
|
countColor = .white
|
||||||
@ -706,9 +712,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
imageSubject = .gift(premiumGiftMonths)
|
imageSubject = .gift(premiumGiftMonths)
|
||||||
} else if isGift {
|
} else if isGift {
|
||||||
var value: Int32 = 3
|
var value: Int32 = 3
|
||||||
if count.value <= 1000 {
|
if count.amount.value <= 1000 {
|
||||||
value = 3
|
value = 3
|
||||||
} else if count.value < 2500 {
|
} else if count.amount.value < 2500 {
|
||||||
value = 6
|
value = 6
|
||||||
} else {
|
} else {
|
||||||
value = 12
|
value = 12
|
||||||
@ -726,9 +732,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
imageSubject = .none
|
imageSubject = .none
|
||||||
}
|
}
|
||||||
if isSubscription || isSubscriber || isSubscriptionFee || giveawayMessageId != nil {
|
if isSubscription || isSubscriber || isSubscriptionFee || giveawayMessageId != nil {
|
||||||
imageIcon = .star
|
imageIcon = count.currency == .ton ? .ton : .star
|
||||||
} else {
|
} else {
|
||||||
imageIcon = nil
|
imageIcon = count.currency == .ton ? .ton : nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if isSubscription && "".isEmpty {
|
if isSubscription && "".isEmpty {
|
||||||
@ -811,10 +817,26 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let amountStarIconName: String
|
||||||
|
var amountStarTintColor: UIColor?
|
||||||
|
var amountStarMaxSize: CGSize?
|
||||||
|
var amountOffset = CGPoint()
|
||||||
|
if boostsText != nil {
|
||||||
|
amountStarIconName = "Premium/BoostButtonIcon"
|
||||||
|
} else if case .ton = count.currency {
|
||||||
|
amountStarIconName = "Ads/TonBig"
|
||||||
|
amountStarTintColor = countColor
|
||||||
|
amountStarMaxSize = CGSize(width: 14.0, height: 14.0)
|
||||||
|
amountOffset.y += 3.0
|
||||||
|
} else {
|
||||||
|
amountStarIconName = "Premium/Stars/StarMedium"
|
||||||
|
}
|
||||||
|
|
||||||
let amountStar = amountStar.update(
|
let amountStar = amountStar.update(
|
||||||
component: BundleIconComponent(
|
component: BundleIconComponent(
|
||||||
name: boostsText != nil ? "Premium/BoostButtonIcon" : "Premium/Stars/StarMedium",
|
name: amountStarIconName,
|
||||||
tintColor: nil
|
tintColor: amountStarTintColor,
|
||||||
|
maxSize: amountStarMaxSize
|
||||||
),
|
),
|
||||||
availableSize: context.availableSize,
|
availableSize: context.availableSize,
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
@ -836,7 +858,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
))
|
))
|
||||||
} else if case .unique = giftAnimationSubject {
|
} else if case .unique = giftAnimationSubject {
|
||||||
let reason: String
|
let reason: String
|
||||||
if count < StarsAmount.zero, case let .transaction(transaction, _) = subject {
|
if count.amount < StarsAmount.zero, case let .transaction(transaction, _) = subject {
|
||||||
if transaction.flags.contains(.isStarGiftResale) {
|
if transaction.flags.contains(.isStarGiftResale) {
|
||||||
reason = strings.Stars_Transaction_GiftPurchase
|
reason = strings.Stars_Transaction_GiftPurchase
|
||||||
} else {
|
} else {
|
||||||
@ -892,7 +914,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
} else if isSubscriber {
|
} else if isSubscriber {
|
||||||
title = strings.Stars_Transaction_Subscription_Subscriber
|
title = strings.Stars_Transaction_Subscription_Subscriber
|
||||||
} else {
|
} else {
|
||||||
title = count < StarsAmount.zero || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From
|
title = count.amount < StarsAmount.zero || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From
|
||||||
}
|
}
|
||||||
|
|
||||||
let toComponent: AnyComponent<Empty>
|
let toComponent: AnyComponent<Empty>
|
||||||
@ -997,7 +1019,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
id: "prize",
|
id: "prize",
|
||||||
title: strings.Stars_Transaction_Giveaway_Prize,
|
title: strings.Stars_Transaction_Giveaway_Prize,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_Giveaway_Stars(Int32(count.value)), font: tableFont, textColor: tableTextColor)))
|
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_Giveaway_Stars(Int32(count.amount.value)), font: tableFont, textColor: tableTextColor)))
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -1499,6 +1521,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
amountLabelOffsetY = 2.0
|
amountLabelOffsetY = 2.0
|
||||||
amountStarOffsetY = 5.0
|
amountStarOffsetY = 5.0
|
||||||
}
|
}
|
||||||
|
amountStarOffsetY += amountOffset.y
|
||||||
|
|
||||||
context.add(amount
|
context.add(amount
|
||||||
.position(CGPoint(x: amountLabelOriginX, y: amountOrigin + amount.size.height / 2.0 + amountLabelOffsetY))
|
.position(CGPoint(x: amountLabelOriginX, y: amountOrigin + amount.size.height / 2.0 + amountLabelOffsetY))
|
||||||
|
@ -16,6 +16,7 @@ final class StarsBalanceComponent: Component {
|
|||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let dateTimeFormat: PresentationDateTimeFormat
|
let dateTimeFormat: PresentationDateTimeFormat
|
||||||
|
let currency: CurrencyAmount.Currency
|
||||||
let count: StarsAmount
|
let count: StarsAmount
|
||||||
let rate: Double?
|
let rate: Double?
|
||||||
let actionTitle: String
|
let actionTitle: String
|
||||||
@ -34,6 +35,7 @@ final class StarsBalanceComponent: Component {
|
|||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
dateTimeFormat: PresentationDateTimeFormat,
|
dateTimeFormat: PresentationDateTimeFormat,
|
||||||
|
currency: CurrencyAmount.Currency,
|
||||||
count: StarsAmount,
|
count: StarsAmount,
|
||||||
rate: Double?,
|
rate: Double?,
|
||||||
actionTitle: String,
|
actionTitle: String,
|
||||||
@ -51,6 +53,7 @@ final class StarsBalanceComponent: Component {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
|
self.currency = currency
|
||||||
self.count = count
|
self.count = count
|
||||||
self.rate = rate
|
self.rate = rate
|
||||||
self.actionTitle = actionTitle
|
self.actionTitle = actionTitle
|
||||||
@ -164,7 +167,13 @@ final class StarsBalanceComponent: Component {
|
|||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
var contentHeight: CGFloat = sideInset
|
var contentHeight: CGFloat = sideInset
|
||||||
|
|
||||||
let formattedLabel = formatStarsAmountText(component.count, dateTimeFormat: component.dateTimeFormat)
|
let formattedLabel: String
|
||||||
|
switch component.currency {
|
||||||
|
case .stars:
|
||||||
|
formattedLabel = formatStarsAmountText(component.count, dateTimeFormat: component.dateTimeFormat)
|
||||||
|
case .ton:
|
||||||
|
formattedLabel = formatTonAmountText(component.count.value, dateTimeFormat: component.dateTimeFormat)
|
||||||
|
}
|
||||||
let labelFont: UIFont
|
let labelFont: UIFont
|
||||||
if formattedLabel.contains(component.dateTimeFormat.decimalSeparator) {
|
if formattedLabel.contains(component.dateTimeFormat.decimalSeparator) {
|
||||||
labelFont = Font.with(size: 48.0, design: .round, weight: .semibold)
|
labelFont = Font.with(size: 48.0, design: .round, weight: .semibold)
|
||||||
|
@ -579,6 +579,7 @@ final class StarsStatisticsScreenComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: strings,
|
strings: strings,
|
||||||
dateTimeFormat: environment.dateTimeFormat,
|
dateTimeFormat: environment.dateTimeFormat,
|
||||||
|
currency: .stars,
|
||||||
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
|
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
|
||||||
rate: self.starsState?.usdRate ?? 0,
|
rate: self.starsState?.usdRate ?? 0,
|
||||||
actionTitle: strings.Stars_Intro_BuyShort,
|
actionTitle: strings.Stars_Intro_BuyShort,
|
||||||
@ -622,6 +623,7 @@ final class StarsStatisticsScreenComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: strings,
|
strings: strings,
|
||||||
dateTimeFormat: environment.dateTimeFormat,
|
dateTimeFormat: environment.dateTimeFormat,
|
||||||
|
currency: .stars,
|
||||||
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
|
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
|
||||||
rate: self.starsState?.usdRate ?? 0,
|
rate: self.starsState?.usdRate ?? 0,
|
||||||
actionTitle: strings.Stars_BotRevenue_Withdraw_WithdrawShort,
|
actionTitle: strings.Stars_BotRevenue_Withdraw_WithdrawShort,
|
||||||
|
@ -409,7 +409,13 @@ final class StarsTransactionsListPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let itemLabel: NSAttributedString
|
let itemLabel: NSAttributedString
|
||||||
let formattedLabel = formatStarsAmountText(item.count, dateTimeFormat: environment.dateTimeFormat, showPlus: true)
|
let formattedLabel: String
|
||||||
|
switch item.currency {
|
||||||
|
case .stars:
|
||||||
|
formattedLabel = formatStarsAmountText(item.count, dateTimeFormat: environment.dateTimeFormat, showPlus: true)
|
||||||
|
case .ton:
|
||||||
|
formattedLabel = formatTonAmountText(item.count.value, dateTimeFormat: environment.dateTimeFormat, showPlus: true)
|
||||||
|
}
|
||||||
|
|
||||||
let smallLabelFont = Font.with(size: floor(fontBaseDisplaySize / 17.0 * 13.0))
|
let smallLabelFont = Font.with(size: floor(fontBaseDisplaySize / 17.0 * 13.0))
|
||||||
let labelFont = Font.medium(fontBaseDisplaySize)
|
let labelFont = Font.medium(fontBaseDisplaySize)
|
||||||
@ -496,7 +502,7 @@ final class StarsTransactionsListPanelComponent: Component {
|
|||||||
contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right),
|
contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right),
|
||||||
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: itemPeer, photo: item.photo, media: item.media, uniqueGift: uniqueGift, backgroundColor: environment.theme.list.plainBackgroundColor))), false),
|
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: itemPeer, photo: item.photo, media: item.media, uniqueGift: uniqueGift, backgroundColor: environment.theme.list.plainBackgroundColor))), false),
|
||||||
icon: nil,
|
icon: nil,
|
||||||
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
|
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(theme: environment.theme, currency: item.currency, textColor: labelColor, text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
|
||||||
action: { [weak self] _ in
|
action: { [weak self] _ in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component else {
|
||||||
return
|
return
|
||||||
|
@ -522,12 +522,23 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
starTransition.setBounds(view: starView, bounds: starFrame)
|
starTransition.setBounds(view: starView, bounds: starFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let titleString: String
|
||||||
|
let descriptionString: String
|
||||||
|
if component.starsContext.ton {
|
||||||
|
//TODO:localize
|
||||||
|
titleString = "TON"
|
||||||
|
descriptionString = "Use TON to unlock content and services on Telegram"
|
||||||
|
} else {
|
||||||
|
titleString = environment.strings.Stars_Intro_Title
|
||||||
|
descriptionString = environment.strings.Stars_Intro_Description
|
||||||
|
}
|
||||||
|
|
||||||
let titleSize = self.titleView.update(
|
let titleSize = self.titleView.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
MultilineTextComponent(
|
MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: environment.strings.Stars_Intro_Title, font: Font.bold(28.0), textColor: environment.theme.list.itemPrimaryTextColor)),
|
text: .plain(NSAttributedString(string: titleString, font: Font.bold(28.0), textColor: environment.theme.list.itemPrimaryTextColor)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
truncationType: .end,
|
truncationType: .end,
|
||||||
maximumNumberOfLines: 1
|
maximumNumberOfLines: 1
|
||||||
@ -557,7 +568,12 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
containerSize: CGSize(width: 120.0, height: 100.0)
|
containerSize: CGSize(width: 120.0, height: 100.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
let formattedBalance = formatStarsAmountText(self.starsState?.balance ?? StarsAmount.zero, dateTimeFormat: environment.dateTimeFormat)
|
let formattedBalance: String
|
||||||
|
if component.starsContext.ton {
|
||||||
|
formattedBalance = formatTonAmountText(self.starsState?.balance.value ?? 0, dateTimeFormat: environment.dateTimeFormat)
|
||||||
|
} else {
|
||||||
|
formattedBalance = formatStarsAmountText(self.starsState?.balance ?? StarsAmount.zero, dateTimeFormat: environment.dateTimeFormat)
|
||||||
|
}
|
||||||
let smallLabelFont = Font.regular(11.0)
|
let smallLabelFont = Font.regular(11.0)
|
||||||
let labelFont = Font.semibold(14.0)
|
let labelFont = Font.semibold(14.0)
|
||||||
let balanceText = tonAmountAttributedString(formattedBalance, integralFont: labelFont, fractionalFont: smallLabelFont, color: environment.theme.actionSheet.primaryTextColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator)
|
let balanceText = tonAmountAttributedString(formattedBalance, integralFont: labelFont, fractionalFont: smallLabelFont, color: environment.theme.actionSheet.primaryTextColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator)
|
||||||
@ -573,7 +589,11 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
)
|
)
|
||||||
let topBalanceIconSize = self.topBalanceIconView.update(
|
let topBalanceIconSize = self.topBalanceIconView.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil)),
|
component: AnyComponent(BundleIconComponent(
|
||||||
|
name: component.starsContext.ton ? "Ads/TonBig" : "Premium/Stars/StarSmall",
|
||||||
|
tintColor: component.starsContext.ton ? environment.theme.list.itemAccentColor : nil,
|
||||||
|
maxSize: component.starsContext.ton ? CGSize(width: 12.0, height: 12.0) : nil
|
||||||
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: availableSize
|
containerSize: availableSize
|
||||||
)
|
)
|
||||||
@ -598,7 +618,10 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
starTransition.setFrame(view: topBalanceValueView, frame: topBalanceValueFrame)
|
starTransition.setFrame(view: topBalanceValueView, frame: topBalanceValueFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
let topBalanceIconFrame = CGRect(origin: CGPoint(x: topBalanceValueFrame.minX - topBalanceIconSize.width - 2.0, y: floorToScreenPixels(topBalanceValueFrame.midY - topBalanceIconSize.height / 2.0) - UIScreenPixel), size: topBalanceIconSize)
|
var topBalanceIconFrame = CGRect(origin: CGPoint(x: topBalanceValueFrame.minX - topBalanceIconSize.width - 2.0, y: floorToScreenPixels(topBalanceValueFrame.midY - topBalanceIconSize.height / 2.0) - UIScreenPixel), size: topBalanceIconSize)
|
||||||
|
if component.starsContext.ton {
|
||||||
|
topBalanceIconFrame.origin.y += 1.0 - UIScreenPixel
|
||||||
|
}
|
||||||
if let topBalanceIconView = self.topBalanceIconView.view {
|
if let topBalanceIconView = self.topBalanceIconView.view {
|
||||||
if topBalanceIconView.superview == nil {
|
if topBalanceIconView.superview == nil {
|
||||||
topBalanceIconView.alpha = 0.0
|
topBalanceIconView.alpha = 0.0
|
||||||
@ -613,7 +636,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
BalancedTextComponent(
|
BalancedTextComponent(
|
||||||
text: .plain(NSAttributedString(string: environment.strings.Stars_Intro_Description, font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)),
|
text: .plain(NSAttributedString(string: descriptionString, font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)),
|
||||||
horizontalAlignment: .center,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0,
|
maximumNumberOfLines: 0,
|
||||||
lineSpacing: 0.2
|
lineSpacing: 0.2
|
||||||
@ -648,6 +671,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
dateTimeFormat: environment.dateTimeFormat,
|
dateTimeFormat: environment.dateTimeFormat,
|
||||||
|
currency: component.starsContext.ton ? .ton : .stars,
|
||||||
count: self.starsState?.balance ?? StarsAmount.zero,
|
count: self.starsState?.balance ?? StarsAmount.zero,
|
||||||
rate: nil,
|
rate: nil,
|
||||||
actionTitle: withdrawAvailable ? environment.strings.Stars_Intro_BuyShort : environment.strings.Stars_Intro_Buy,
|
actionTitle: withdrawAvailable ? environment.strings.Stars_Intro_BuyShort : environment.strings.Stars_Intro_Buy,
|
||||||
@ -705,7 +729,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
contentHeight += 34.0
|
contentHeight += 34.0
|
||||||
|
|
||||||
var canJoinRefProgram = false
|
var canJoinRefProgram = false
|
||||||
if let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] {
|
if !component.starsContext.ton, let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] {
|
||||||
if let value = value as? Double {
|
if let value = value as? Double {
|
||||||
canJoinRefProgram = value != 0.0
|
canJoinRefProgram = value != 0.0
|
||||||
} else if let value = value as? Bool {
|
} else if let value = value as? Bool {
|
||||||
@ -835,10 +859,11 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
MultilineTextComponent(text: .plain(NSAttributedString(string: isExpired ? environment.strings.Stars_Intro_Subscriptions_ExpiredStatus : environment.strings.Stars_Intro_Subscriptions_Cancelled, font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)), textColor: environment.theme.list.itemDestructiveColor)))
|
MultilineTextComponent(text: .plain(NSAttributedString(string: isExpired ? environment.strings.Stars_Intro_Subscriptions_ExpiredStatus : environment.strings.Stars_Intro_Subscriptions_Cancelled, font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)), textColor: environment.theme.list.itemDestructiveColor)))
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
let itemLabel = NSAttributedString(string: "\(subscription.pricing.amount)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor)
|
let itemLabelColor = environment.theme.list.itemPrimaryTextColor
|
||||||
|
let itemLabel = NSAttributedString(string: "\(subscription.pricing.amount)", font: Font.medium(fontBaseDisplaySize), textColor: itemLabelColor)
|
||||||
let itemSublabel = NSAttributedString(string: environment.strings.Stars_Intro_Subscriptions_PerMonth, font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)), textColor: environment.theme.list.itemSecondaryTextColor)
|
let itemSublabel = NSAttributedString(string: environment.strings.Stars_Intro_Subscriptions_PerMonth, font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)), textColor: environment.theme.list.itemSecondaryTextColor)
|
||||||
|
|
||||||
labelComponent = AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel, subtext: itemSublabel)))
|
labelComponent = AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(theme: environment.theme, currency: component.starsContext.ton ? .ton : .stars, textColor: itemLabelColor, text: itemLabel, subtext: itemSublabel)))
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptionsItems.append(AnyComponentWithIdentity(
|
subscriptionsItems.append(AnyComponentWithIdentity(
|
||||||
|
@ -91,7 +91,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
currency: state.currency,
|
currency: state.currency,
|
||||||
balance: state.balance,
|
balance: state.currency == .stars ? state.starsBalance : state.tonBalance,
|
||||||
alignment: .right
|
alignment: .right
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: 200.0, height: 200.0),
|
availableSize: CGSize(width: 200.0, height: 200.0),
|
||||||
@ -169,7 +169,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
amountPlaceholder = environment.strings.Stars_Withdraw_AmountPlaceholder
|
amountPlaceholder = environment.strings.Stars_Withdraw_AmountPlaceholder
|
||||||
|
|
||||||
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||||
maxAmount = state.balance
|
maxAmount = state.starsBalance
|
||||||
case .paidMedia:
|
case .paidMedia:
|
||||||
titleString = environment.strings.Stars_PaidContent_Title
|
titleString = environment.strings.Stars_PaidContent_Title
|
||||||
amountTitle = environment.strings.Stars_PaidContent_AmountTitle
|
amountTitle = environment.strings.Stars_PaidContent_AmountTitle
|
||||||
@ -237,9 +237,9 @@ private final class SheetContent: CombinedComponent {
|
|||||||
|
|
||||||
let balance: StarsAmount?
|
let balance: StarsAmount?
|
||||||
if case .accountWithdraw = component.mode {
|
if case .accountWithdraw = component.mode {
|
||||||
balance = state.balance
|
balance = state.starsBalance
|
||||||
} else if case .reaction = component.mode {
|
} else if case .reaction = component.mode {
|
||||||
balance = state.balance
|
balance = state.starsBalance
|
||||||
} else if case let .withdraw(starsState, _) = component.mode {
|
} else if case let .withdraw(starsState, _) = component.mode {
|
||||||
balance = starsState.balances.availableBalance
|
balance = starsState.balances.availableBalance
|
||||||
} else {
|
} else {
|
||||||
@ -329,10 +329,16 @@ private final class SheetContent: CombinedComponent {
|
|||||||
guard let state else {
|
guard let state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currency: CurrencyAmount.Currency
|
||||||
if id == AnyHashable(0) {
|
if id == AnyHashable(0) {
|
||||||
state.currency = .stars
|
currency = .stars
|
||||||
} else {
|
} else {
|
||||||
state.currency = .ton
|
currency = .ton
|
||||||
|
}
|
||||||
|
if state.currency != currency {
|
||||||
|
state.currency = currency
|
||||||
|
state.amount = nil
|
||||||
}
|
}
|
||||||
state.updated(transition: .spring(duration: 0.4))
|
state.updated(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
@ -485,6 +491,7 @@ private final class SheetContent: CombinedComponent {
|
|||||||
placeholderText: amountPlaceholder,
|
placeholderText: amountPlaceholder,
|
||||||
labelText: amountLabel,
|
labelText: amountLabel,
|
||||||
currency: state.currency,
|
currency: state.currency,
|
||||||
|
dateTimeFormat: presentationData.dateTimeFormat,
|
||||||
amountUpdated: { [weak state] amount in
|
amountUpdated: { [weak state] amount in
|
||||||
state?.amount = amount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
state?.amount = amount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||||
state?.updated()
|
state?.updated()
|
||||||
@ -632,13 +639,16 @@ private final class SheetContent: CombinedComponent {
|
|||||||
case .sender:
|
case .sender:
|
||||||
if let amount = state.amount {
|
if let amount = state.amount {
|
||||||
let currencySymbol: String
|
let currencySymbol: String
|
||||||
|
let currencyAmount: String
|
||||||
switch state.currency {
|
switch state.currency {
|
||||||
case .stars:
|
case .stars:
|
||||||
currencySymbol = "#"
|
currencySymbol = "#"
|
||||||
|
currencyAmount = presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator)
|
||||||
case .ton:
|
case .ton:
|
||||||
currencySymbol = "$"
|
currencySymbol = "$"
|
||||||
|
currencyAmount = formatTonAmountText(amount.value, dateTimeFormat: environment.dateTimeFormat)
|
||||||
}
|
}
|
||||||
buttonString = "Offer \(currencySymbol) \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
|
buttonString = "Offer \(currencySymbol) \(currencyAmount)"
|
||||||
} else {
|
} else {
|
||||||
buttonString = "Offer for Free"
|
buttonString = "Offer for Free"
|
||||||
}
|
}
|
||||||
@ -756,8 +766,10 @@ private final class SheetContent: CombinedComponent {
|
|||||||
fileprivate var currency: CurrencyAmount.Currency = .stars
|
fileprivate var currency: CurrencyAmount.Currency = .stars
|
||||||
fileprivate var timestamp: Int32?
|
fileprivate var timestamp: Int32?
|
||||||
|
|
||||||
fileprivate var balance: StarsAmount?
|
fileprivate var starsBalance: StarsAmount?
|
||||||
private var stateDisposable: Disposable?
|
private var starsStateDisposable: Disposable?
|
||||||
|
fileprivate var tonBalance: StarsAmount?
|
||||||
|
private var tonStateDisposable: Disposable?
|
||||||
|
|
||||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||||
var cachedStarImage: (UIImage, PresentationTheme)?
|
var cachedStarImage: (UIImage, PresentationTheme)?
|
||||||
@ -809,14 +821,25 @@ private final class SheetContent: CombinedComponent {
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if needsBalance, let starsContext = component.context.starsContext {
|
if needsBalance {
|
||||||
self.stateDisposable = (starsContext.state
|
if let starsContext = component.context.starsContext {
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
self.starsStateDisposable = (starsContext.state
|
||||||
if let self, let balance = state?.balance {
|
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
||||||
self.balance = balance
|
if let self, let balance = state?.balance {
|
||||||
self.updated()
|
self.starsBalance = balance
|
||||||
}
|
self.updated()
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if let tonContext = component.context.tonContext {
|
||||||
|
self.tonStateDisposable = (tonContext.state
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
||||||
|
if let self, let balance = state?.balance {
|
||||||
|
self.tonBalance = balance
|
||||||
|
self.updated()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .starGiftResell(giftToMatch, update, _) = self.mode {
|
if case let .starGiftResell(giftToMatch, update, _) = self.mode {
|
||||||
@ -851,7 +874,8 @@ private final class SheetContent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.stateDisposable?.dispose()
|
self.starsStateDisposable?.dispose()
|
||||||
|
self.tonStateDisposable?.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1035,6 +1059,453 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
private let invalidAmountCharacters = CharacterSet.decimalDigits.inverted
|
private let invalidAmountCharacters = CharacterSet.decimalDigits.inverted
|
||||||
|
|
||||||
|
private final class AmountFieldTonFormatter: NSObject, UITextFieldDelegate {
|
||||||
|
private struct Representation {
|
||||||
|
private let format: CurrencyFormat
|
||||||
|
private var caretIndex: Int = 0
|
||||||
|
private var wholePart: [Int] = []
|
||||||
|
private var decimalPart: [Int] = []
|
||||||
|
|
||||||
|
init(string: String, format: CurrencyFormat) {
|
||||||
|
self.format = format
|
||||||
|
|
||||||
|
var isDecimalPart = false
|
||||||
|
for c in string {
|
||||||
|
if c.isNumber {
|
||||||
|
if let value = Int(String(c)) {
|
||||||
|
if isDecimalPart {
|
||||||
|
self.decimalPart.append(value)
|
||||||
|
} else {
|
||||||
|
self.wholePart.append(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if String(c) == format.decimalSeparator {
|
||||||
|
isDecimalPart = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while self.wholePart.count > 1 {
|
||||||
|
if self.wholePart[0] != 0 {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
self.wholePart.removeFirst()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.wholePart.isEmpty {
|
||||||
|
self.wholePart = [0]
|
||||||
|
}
|
||||||
|
|
||||||
|
while self.decimalPart.count > 1 {
|
||||||
|
if self.decimalPart[self.decimalPart.count - 1] != 0 {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
self.decimalPart.removeLast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while self.decimalPart.count < format.decimalDigits {
|
||||||
|
self.decimalPart.append(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.caretIndex = self.wholePart.count
|
||||||
|
}
|
||||||
|
|
||||||
|
var minCaretIndex: Int {
|
||||||
|
for i in 0 ..< self.wholePart.count {
|
||||||
|
if self.wholePart[i] != 0 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self.wholePart.count
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func moveCaret(offset: Int) {
|
||||||
|
self.caretIndex = max(self.minCaretIndex, min(self.caretIndex + offset, self.wholePart.count + self.decimalPart.count))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func normalize() {
|
||||||
|
while self.wholePart.count > 1 {
|
||||||
|
if self.wholePart[0] != 0 {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
self.wholePart.removeFirst()
|
||||||
|
self.moveCaret(offset: -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.wholePart.isEmpty {
|
||||||
|
self.wholePart = [0]
|
||||||
|
}
|
||||||
|
|
||||||
|
while self.decimalPart.count < format.decimalDigits {
|
||||||
|
self.decimalPart.append(0)
|
||||||
|
}
|
||||||
|
while self.decimalPart.count > format.decimalDigits {
|
||||||
|
self.decimalPart.removeLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.caretIndex = max(self.minCaretIndex, min(self.caretIndex, self.wholePart.count + self.decimalPart.count))
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func backspace() {
|
||||||
|
if self.caretIndex > self.wholePart.count {
|
||||||
|
let decimalIndex = self.caretIndex - self.wholePart.count
|
||||||
|
if decimalIndex > 0 {
|
||||||
|
self.decimalPart.remove(at: decimalIndex - 1)
|
||||||
|
|
||||||
|
self.moveCaret(offset: -1)
|
||||||
|
self.normalize()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if self.caretIndex > 0 {
|
||||||
|
self.wholePart.remove(at: self.caretIndex - 1)
|
||||||
|
|
||||||
|
self.moveCaret(offset: -1)
|
||||||
|
self.normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func insert(letter: String) {
|
||||||
|
if letter == "." || letter == "," {
|
||||||
|
if self.caretIndex == self.wholePart.count {
|
||||||
|
return
|
||||||
|
} else if self.caretIndex < self.wholePart.count {
|
||||||
|
for i in (self.caretIndex ..< self.wholePart.count).reversed() {
|
||||||
|
self.decimalPart.insert(self.wholePart[i], at: 0)
|
||||||
|
self.wholePart.remove(at: i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.normalize()
|
||||||
|
} else if letter.count == 1 && letter[letter.startIndex].isNumber {
|
||||||
|
if let value = Int(letter) {
|
||||||
|
if self.caretIndex <= self.wholePart.count {
|
||||||
|
self.wholePart.insert(value, at: self.caretIndex)
|
||||||
|
} else {
|
||||||
|
let decimalIndex = self.caretIndex - self.wholePart.count
|
||||||
|
self.decimalPart.insert(value, at: decimalIndex)
|
||||||
|
}
|
||||||
|
self.moveCaret(offset: 1)
|
||||||
|
self.normalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var string: String {
|
||||||
|
var result = ""
|
||||||
|
|
||||||
|
for digit in self.wholePart {
|
||||||
|
result.append("\(digit)")
|
||||||
|
}
|
||||||
|
result.append(self.format.decimalSeparator)
|
||||||
|
for digit in self.decimalPart {
|
||||||
|
result.append("\(digit)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringCaretIndex: Int {
|
||||||
|
var logicalIndex = 0
|
||||||
|
var resolvedIndex = 0
|
||||||
|
|
||||||
|
if logicalIndex == self.caretIndex {
|
||||||
|
return resolvedIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in self.wholePart {
|
||||||
|
logicalIndex += 1
|
||||||
|
resolvedIndex += 1
|
||||||
|
|
||||||
|
if logicalIndex == self.caretIndex {
|
||||||
|
return resolvedIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedIndex += 1
|
||||||
|
|
||||||
|
for _ in self.decimalPart {
|
||||||
|
logicalIndex += 1
|
||||||
|
resolvedIndex += 1
|
||||||
|
|
||||||
|
if logicalIndex == self.caretIndex {
|
||||||
|
return resolvedIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
var numericalValue: Int64 {
|
||||||
|
var result: Int64 = 0
|
||||||
|
|
||||||
|
for digit in self.wholePart {
|
||||||
|
result *= 10
|
||||||
|
result += Int64(digit)
|
||||||
|
}
|
||||||
|
for digit in self.decimalPart {
|
||||||
|
result *= 10
|
||||||
|
result += Int64(digit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private let format: CurrencyFormat
|
||||||
|
private let currency: String
|
||||||
|
private let maxNumericalValue: Int64
|
||||||
|
private let updated: (Int64) -> Void
|
||||||
|
private let isEmptyUpdated: (Bool) -> Void
|
||||||
|
private let focusUpdated: (Bool) -> Void
|
||||||
|
|
||||||
|
private var representation: Representation
|
||||||
|
|
||||||
|
private var previousResolvedCaretIndex: Int = 0
|
||||||
|
private var ignoreTextSelection: Bool = false
|
||||||
|
private var enableTextSelectionProcessing: Bool = false
|
||||||
|
|
||||||
|
init?(textField: UITextField, currency: String, maxNumericalValue: Int64, initialValue: String, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, focusUpdated: @escaping (Bool) -> Void) {
|
||||||
|
guard let format = CurrencyFormat(currency: currency) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.format = format
|
||||||
|
self.currency = currency
|
||||||
|
self.maxNumericalValue = maxNumericalValue
|
||||||
|
self.updated = updated
|
||||||
|
self.isEmptyUpdated = isEmptyUpdated
|
||||||
|
self.focusUpdated = focusUpdated
|
||||||
|
|
||||||
|
self.representation = Representation(string: initialValue, format: format)
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
textField.text = self.representation.string
|
||||||
|
self.previousResolvedCaretIndex = self.representation.stringCaretIndex
|
||||||
|
|
||||||
|
self.isEmptyUpdated(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset(textField: UITextField, initialValue: String) {
|
||||||
|
self.representation = Representation(string: initialValue, format: self.format)
|
||||||
|
self.resetFromRepresentation(textField: textField, notifyUpdated: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resetFromRepresentation(textField: UITextField, notifyUpdated: Bool) {
|
||||||
|
self.ignoreTextSelection = true
|
||||||
|
|
||||||
|
if self.representation.numericalValue > self.maxNumericalValue {
|
||||||
|
self.representation = Representation(string: formatCurrencyAmountCustom(self.maxNumericalValue, currency: self.currency).0, format: self.format)
|
||||||
|
}
|
||||||
|
|
||||||
|
textField.text = self.representation.string
|
||||||
|
self.previousResolvedCaretIndex = self.representation.stringCaretIndex
|
||||||
|
|
||||||
|
if self.enableTextSelectionProcessing {
|
||||||
|
let stringCaretIndex = self.representation.stringCaretIndex
|
||||||
|
if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) {
|
||||||
|
textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.ignoreTextSelection = false
|
||||||
|
|
||||||
|
if notifyUpdated {
|
||||||
|
self.updated(self.representation.numericalValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
if string.count == 1 {
|
||||||
|
self.representation.insert(letter: string)
|
||||||
|
self.resetFromRepresentation(textField: textField, notifyUpdated: true)
|
||||||
|
} else if string.count == 0 {
|
||||||
|
self.representation.backspace()
|
||||||
|
self.resetFromRepresentation(textField: textField, notifyUpdated: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
|
self.enableTextSelectionProcessing = true
|
||||||
|
self.focusUpdated(true)
|
||||||
|
|
||||||
|
let stringCaretIndex = self.representation.stringCaretIndex
|
||||||
|
self.previousResolvedCaretIndex = stringCaretIndex
|
||||||
|
if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) {
|
||||||
|
self.ignoreTextSelection = true
|
||||||
|
textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition)
|
||||||
|
self.ignoreTextSelection = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func textFieldDidChangeSelection(_ textField: UITextField) {
|
||||||
|
if self.ignoreTextSelection {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.enableTextSelectionProcessing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let selectedTextRange = textField.selectedTextRange {
|
||||||
|
let index = textField.offset(from: textField.beginningOfDocument, to: selectedTextRange.end)
|
||||||
|
if self.previousResolvedCaretIndex != index {
|
||||||
|
self.representation.moveCaret(offset: self.previousResolvedCaretIndex < index ? 1 : -1)
|
||||||
|
|
||||||
|
let stringCaretIndex = self.representation.stringCaretIndex
|
||||||
|
self.previousResolvedCaretIndex = stringCaretIndex
|
||||||
|
if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) {
|
||||||
|
textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc public func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
|
self.enableTextSelectionProcessing = false
|
||||||
|
self.focusUpdated(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate {
|
||||||
|
private let currency: CurrencyAmount.Currency
|
||||||
|
private let dateTimeFormat: PresentationDateTimeFormat
|
||||||
|
|
||||||
|
private let textField: UITextField
|
||||||
|
private let minValue: Int64
|
||||||
|
private let maxValue: Int64
|
||||||
|
private let updated: (Int64) -> Void
|
||||||
|
private let isEmptyUpdated: (Bool) -> Void
|
||||||
|
private let animateError: () -> Void
|
||||||
|
private let focusUpdated: (Bool) -> Void
|
||||||
|
|
||||||
|
init?(textField: UITextField, currency: CurrencyAmount.Currency, dateTimeFormat: PresentationDateTimeFormat, minValue: Int64, maxValue: Int64, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, animateError: @escaping () -> Void, focusUpdated: @escaping (Bool) -> Void) {
|
||||||
|
self.textField = textField
|
||||||
|
self.currency = currency
|
||||||
|
self.dateTimeFormat = dateTimeFormat
|
||||||
|
self.minValue = minValue
|
||||||
|
self.maxValue = maxValue
|
||||||
|
self.updated = updated
|
||||||
|
self.isEmptyUpdated = isEmptyUpdated
|
||||||
|
self.animateError = animateError
|
||||||
|
self.focusUpdated = focusUpdated
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func amountFrom(text: String) -> Int64 {
|
||||||
|
var amount: Int64?
|
||||||
|
if !text.isEmpty {
|
||||||
|
switch self.currency {
|
||||||
|
case .stars:
|
||||||
|
if let value = Int64(text) {
|
||||||
|
amount = value
|
||||||
|
}
|
||||||
|
case .ton:
|
||||||
|
let scale: Int64 = 1_000_000_000 // 10⁹ (one “nano”)
|
||||||
|
if let dot = text.firstIndex(of: ".") {
|
||||||
|
// Slices for the parts on each side of the dot
|
||||||
|
var wholeSlice = String(text[..<dot])
|
||||||
|
if wholeSlice.isEmpty {
|
||||||
|
wholeSlice = "0"
|
||||||
|
}
|
||||||
|
let fractionSlice = text[text.index(after: dot)...]
|
||||||
|
|
||||||
|
// Make the fractional string exactly 9 characters long
|
||||||
|
var fractionStr = String(fractionSlice)
|
||||||
|
if fractionStr.count > 9 {
|
||||||
|
fractionStr = String(fractionStr.prefix(9)) // trim extra digits
|
||||||
|
} else {
|
||||||
|
fractionStr = fractionStr.padding(
|
||||||
|
toLength: 9, withPad: "0", startingAt: 0) // pad with zeros
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert and combine
|
||||||
|
if let whole = Int64(wholeSlice),
|
||||||
|
let frac = Int64(fractionStr) {
|
||||||
|
amount = whole * scale + frac
|
||||||
|
}
|
||||||
|
} else if let whole = Int64(text) { // string had no dot at all
|
||||||
|
amount = whole * scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return amount ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func onTextChanged(text: String) {
|
||||||
|
self.updated(self.amountFrom(text: text))
|
||||||
|
self.isEmptyUpdated(text.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
var acceptZero = false
|
||||||
|
if self.minValue <= 0 {
|
||||||
|
acceptZero = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
||||||
|
if newText.contains(where: { c in
|
||||||
|
switch c {
|
||||||
|
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
if case .ton = self.currency {
|
||||||
|
if c == "." {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if newText.count(where: { $0 == "." }) > 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch self.currency {
|
||||||
|
case .stars:
|
||||||
|
if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0")) {
|
||||||
|
newText.removeFirst()
|
||||||
|
textField.text = newText
|
||||||
|
self.onTextChanged(text: newText)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .ton:
|
||||||
|
if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0") && !newText.hasPrefix("0.")) {
|
||||||
|
newText.removeFirst()
|
||||||
|
textField.text = newText
|
||||||
|
self.onTextChanged(text: newText)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let amount: Int64 = self.amountFrom(text: newText)
|
||||||
|
if amount > self.maxValue {
|
||||||
|
switch self.currency {
|
||||||
|
case .stars:
|
||||||
|
textField.text = "\(self.maxValue)"
|
||||||
|
case .ton:
|
||||||
|
textField.text = "\(formatTonAmountText(self.maxValue, dateTimeFormat: self.dateTimeFormat))"
|
||||||
|
}
|
||||||
|
self.onTextChanged(text: self.textField.text ?? "")
|
||||||
|
self.animateError()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onTextChanged(text: newText)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class AmountFieldComponent: Component {
|
private final class AmountFieldComponent: Component {
|
||||||
typealias EnvironmentType = Empty
|
typealias EnvironmentType = Empty
|
||||||
|
|
||||||
@ -1048,6 +1519,7 @@ private final class AmountFieldComponent: Component {
|
|||||||
let placeholderText: String
|
let placeholderText: String
|
||||||
let labelText: String?
|
let labelText: String?
|
||||||
let currency: CurrencyAmount.Currency
|
let currency: CurrencyAmount.Currency
|
||||||
|
let dateTimeFormat: PresentationDateTimeFormat
|
||||||
let amountUpdated: (Int64?) -> Void
|
let amountUpdated: (Int64?) -> Void
|
||||||
let tag: AnyObject?
|
let tag: AnyObject?
|
||||||
|
|
||||||
@ -1062,6 +1534,7 @@ private final class AmountFieldComponent: Component {
|
|||||||
placeholderText: String,
|
placeholderText: String,
|
||||||
labelText: String?,
|
labelText: String?,
|
||||||
currency: CurrencyAmount.Currency,
|
currency: CurrencyAmount.Currency,
|
||||||
|
dateTimeFormat: PresentationDateTimeFormat,
|
||||||
amountUpdated: @escaping (Int64?) -> Void,
|
amountUpdated: @escaping (Int64?) -> Void,
|
||||||
tag: AnyObject? = nil
|
tag: AnyObject? = nil
|
||||||
) {
|
) {
|
||||||
@ -1075,6 +1548,7 @@ private final class AmountFieldComponent: Component {
|
|||||||
self.placeholderText = placeholderText
|
self.placeholderText = placeholderText
|
||||||
self.labelText = labelText
|
self.labelText = labelText
|
||||||
self.currency = currency
|
self.currency = currency
|
||||||
|
self.dateTimeFormat = dateTimeFormat
|
||||||
self.amountUpdated = amountUpdated
|
self.amountUpdated = amountUpdated
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
}
|
}
|
||||||
@ -1127,10 +1601,13 @@ private final class AmountFieldComponent: Component {
|
|||||||
private let placeholderView: ComponentView<Empty>
|
private let placeholderView: ComponentView<Empty>
|
||||||
private let icon = ComponentView<Empty>()
|
private let icon = ComponentView<Empty>()
|
||||||
private let textField: TextFieldNodeView
|
private let textField: TextFieldNodeView
|
||||||
|
private var starsFormatter: AmountFieldStarsFormatter?
|
||||||
|
private var tonFormatter: AmountFieldStarsFormatter?
|
||||||
private let labelView: ComponentView<Empty>
|
private let labelView: ComponentView<Empty>
|
||||||
|
|
||||||
private var component: AmountFieldComponent?
|
private var component: AmountFieldComponent?
|
||||||
private weak var state: EmptyComponentState?
|
private weak var state: EmptyComponentState?
|
||||||
|
private var isUpdating: Bool = false
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.placeholderView = ComponentView<Empty>()
|
self.placeholderView = ComponentView<Empty>()
|
||||||
@ -1138,9 +1615,6 @@ private final class AmountFieldComponent: Component {
|
|||||||
self.labelView = ComponentView<Empty>()
|
self.labelView = ComponentView<Empty>()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.textField.delegate = self
|
|
||||||
self.textField.addTarget(self, action: #selector(self.textChanged(_:)), for: .editingChanged)
|
|
||||||
|
|
||||||
self.addSubview(self.textField)
|
self.addSubview(self.textField)
|
||||||
}
|
}
|
||||||
@ -1148,56 +1622,6 @@ private final class AmountFieldComponent: Component {
|
|||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func textChanged(_ sender: Any) {
|
|
||||||
let text = self.textField.text ?? ""
|
|
||||||
let amount: Int64?
|
|
||||||
if !text.isEmpty, let value = Int64(text) {
|
|
||||||
amount = value
|
|
||||||
} else {
|
|
||||||
amount = nil
|
|
||||||
}
|
|
||||||
self.component?.amountUpdated(amount)
|
|
||||||
self.placeholderView.view?.isHidden = !text.isEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
|
||||||
guard let component = self.component else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if string.rangeOfCharacter(from: invalidAmountCharacters) != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var acceptZero = false
|
|
||||||
if let minValue = component.minValue, minValue <= 0 {
|
|
||||||
acceptZero = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
|
|
||||||
if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0")) {
|
|
||||||
newText.removeFirst()
|
|
||||||
textField.text = newText
|
|
||||||
self.textChanged(self.textField)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
let amount: Int64?
|
|
||||||
if !newText.isEmpty, let value = Int64(normalizeArabicNumeralString(newText, type: .western)) {
|
|
||||||
amount = value
|
|
||||||
} else {
|
|
||||||
amount = nil
|
|
||||||
}
|
|
||||||
if let amount, let maxAmount = component.maxValue, amount > maxAmount {
|
|
||||||
textField.text = "\(maxAmount)"
|
|
||||||
self.textChanged(self.textField)
|
|
||||||
self.animateError()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func activateInput() {
|
func activateInput() {
|
||||||
self.textField.becomeFirstResponder()
|
self.textField.becomeFirstResponder()
|
||||||
@ -1217,18 +1641,109 @@ private final class AmountFieldComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func update(component: AmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
func update(component: AmountFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.isUpdating = true
|
||||||
|
defer {
|
||||||
|
self.isUpdating = false
|
||||||
|
}
|
||||||
|
|
||||||
self.textField.textColor = component.textColor
|
self.textField.textColor = component.textColor
|
||||||
if let value = component.value {
|
if self.component?.currency != component.currency {
|
||||||
self.textField.text = "\(value)"
|
if let value = component.value {
|
||||||
} else {
|
var text = ""
|
||||||
self.textField.text = ""
|
switch component.currency {
|
||||||
|
case .stars:
|
||||||
|
text = "\(value)"
|
||||||
|
case .ton:
|
||||||
|
text = "\(formatTonAmountText(value, dateTimeFormat: component.dateTimeFormat))"
|
||||||
|
}
|
||||||
|
self.textField.text = text
|
||||||
|
} else {
|
||||||
|
self.textField.text = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.textField.font = Font.regular(17.0)
|
self.textField.font = Font.regular(17.0)
|
||||||
|
|
||||||
self.textField.keyboardType = .numberPad
|
|
||||||
self.textField.returnKeyType = .done
|
self.textField.returnKeyType = .done
|
||||||
self.textField.autocorrectionType = .no
|
self.textField.autocorrectionType = .no
|
||||||
self.textField.autocapitalizationType = .none
|
self.textField.autocapitalizationType = .none
|
||||||
|
|
||||||
|
if self.component?.currency != component.currency {
|
||||||
|
switch component.currency {
|
||||||
|
case .stars:
|
||||||
|
self.textField.delegate = self
|
||||||
|
self.textField.keyboardType = .numberPad
|
||||||
|
if self.starsFormatter == nil {
|
||||||
|
self.starsFormatter = AmountFieldStarsFormatter(
|
||||||
|
textField: self.textField,
|
||||||
|
currency: component.currency,
|
||||||
|
dateTimeFormat: component.dateTimeFormat,
|
||||||
|
minValue: component.minValue ?? 0,
|
||||||
|
maxValue: component.maxValue ?? Int64.max,
|
||||||
|
updated: { [weak self] value in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.isUpdating {
|
||||||
|
component.amountUpdated(value == 0 ? nil : value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isEmptyUpdated: { [weak self] isEmpty in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.placeholderView.view?.isHidden = !isEmpty
|
||||||
|
},
|
||||||
|
animateError: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.animateError()
|
||||||
|
},
|
||||||
|
focusUpdated: { _ in
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
self.tonFormatter = nil
|
||||||
|
self.textField.delegate = self.starsFormatter
|
||||||
|
self.textField.text = ""
|
||||||
|
case .ton:
|
||||||
|
self.textField.keyboardType = .numbersAndPunctuation
|
||||||
|
if self.tonFormatter == nil {
|
||||||
|
self.tonFormatter = AmountFieldStarsFormatter(
|
||||||
|
textField: self.textField,
|
||||||
|
currency: component.currency,
|
||||||
|
dateTimeFormat: component.dateTimeFormat,
|
||||||
|
minValue: component.minValue ?? 0,
|
||||||
|
maxValue: component.maxValue ?? Int64.max,
|
||||||
|
updated: { [weak self] value in
|
||||||
|
guard let self, let component = self.component else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.isUpdating {
|
||||||
|
component.amountUpdated(value == 0 ? nil : value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isEmptyUpdated: { [weak self] isEmpty in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.placeholderView.view?.isHidden = !isEmpty
|
||||||
|
},
|
||||||
|
animateError: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.animateError()
|
||||||
|
},
|
||||||
|
focusUpdated: { _ in
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
self.starsFormatter = nil
|
||||||
|
self.textField.delegate = self.tonFormatter
|
||||||
|
}
|
||||||
|
self.textField.reloadInputViews()
|
||||||
|
}
|
||||||
|
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
@ -1460,7 +1975,13 @@ private final class BalanceComponent: CombinedComponent {
|
|||||||
|
|
||||||
let balanceText: String
|
let balanceText: String
|
||||||
if let value = context.component.balance {
|
if let value = context.component.balance {
|
||||||
balanceText = "\(value.stringValue)"
|
switch context.component.currency {
|
||||||
|
case .stars:
|
||||||
|
balanceText = "\(value.stringValue)"
|
||||||
|
case .ton:
|
||||||
|
let dateTimeFormat = context.component.context.sharedContext.currentPresentationData.with({ $0 }).dateTimeFormat
|
||||||
|
balanceText = "\(formatTonAmountText(value.value, dateTimeFormat: dateTimeFormat))"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
balanceText = "..."
|
balanceText = "..."
|
||||||
}
|
}
|
||||||
|
@ -1817,16 +1817,26 @@ extension ChatControllerImpl {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if actions.options.contains(.deleteGlobally) && messages.contains(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }) {
|
if actions.options.contains(.deleteGlobally), let message = messages.first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute {
|
||||||
let commit = { [weak self] in
|
let commit = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
|
let titleString: String
|
||||||
|
let textString: String
|
||||||
|
switch attribute.currency {
|
||||||
|
case .stars:
|
||||||
|
titleString = "Stars Will Be Lost"
|
||||||
|
textString = "You won't receive **Stars** for this post if you delete it now. The post must remain visible for at least **24 hours** after publication."
|
||||||
|
case .ton:
|
||||||
|
titleString = "TON Will Be Lost"
|
||||||
|
textString = "You won't receive **TON** for this post if you delete it now. The post must remain visible for at least **24 hours** after publication."
|
||||||
|
}
|
||||||
self.present(standardTextAlertController(
|
self.present(standardTextAlertController(
|
||||||
theme: AlertControllerTheme(presentationData: self.presentationData),
|
theme: AlertControllerTheme(presentationData: self.presentationData),
|
||||||
title: "Stars Will Be Lost",
|
title: titleString,
|
||||||
text: "You won't receive **Stars** for this post if you delete it now. The post must remain visible for at least **24 hours** after publication.",
|
text: textString,
|
||||||
actions: [
|
actions: [
|
||||||
TextAlertAction(type: .destructiveAction, title: "Delete Anyway", action: { [weak self] in
|
TextAlertAction(type: .destructiveAction, title: "Delete Anyway", action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
@ -409,7 +409,7 @@ public final class ChatTextInputTextCustomEmojiAttribute: NSObject, Codable {
|
|||||||
case topic(id: Int64, info: EngineMessageHistoryThread.Info)
|
case topic(id: Int64, info: EngineMessageHistoryThread.Info)
|
||||||
case nameColors([UInt32])
|
case nameColors([UInt32])
|
||||||
case stars(tinted: Bool)
|
case stars(tinted: Bool)
|
||||||
case ton
|
case ton(tinted: Bool)
|
||||||
case animation(name: String)
|
case animation(name: String)
|
||||||
case verification
|
case verification
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user