mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '9146a42cfa'
# Conflicts: # submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift # submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift
This commit is contained in:
commit
403a163692
23 changed files with 1552 additions and 117 deletions
|
|
@ -14892,3 +14892,22 @@ Sorry for the inconvenience.";
|
|||
|
||||
"PeerInfo.Gifts.Upgradable" = "Upgradable";
|
||||
|
||||
"Gift.Upgrade.GiftTitle" = "Make Unique";
|
||||
"Gift.Upgrade.GiftDescription" = "Let %@ turn this gift into a unique collectible.";
|
||||
"Gift.Upgrade.Unique.GiftDescription" = "%@ will get a unique number, model, backdrop, and symbol for the gift.";
|
||||
"Gift.Upgrade.Transferable.GiftDescription" = "%@ will be able to send the gift to anyone Telegram.";
|
||||
"Gift.Upgrade.Tradable.GiftDescription" = "%@ will be able to auction the gift on third-party NFT marketplaces.";
|
||||
|
||||
"Notification.StarGift.Subtitle.Upgrade.Prepaid" = "%@ can now turn this gift to a unique collectible";
|
||||
|
||||
"Notification.StarsGift.SentUpgradeYou" = "You sent an upgrade worth %1$@ for your gift to %2$@";
|
||||
"Notification.StarsGift.SentUpgradeYouOther" = "You sent an upgrade worth %1$@ for the gift %2$@ received from %3$@";
|
||||
"Notification.StarsGift.SentUpgradeYouUnknown" = "You sent an upgrade worth %1$@ for the gift to %2$@";
|
||||
|
||||
"Notification.StarsGift.SentUpgrade" = "%1$@ sent an upgrade worth %2$@ for your gift";
|
||||
"Notification.StarsGift.SentUpgradeOther" = "%1$@ sent an upgrade worth %2$@ for the gift you received from %3$@";
|
||||
|
||||
"Notification.StarsGift.UpgradePrepaidYou" = "You unpacked the gift that %@ helped upgrade";
|
||||
"Notification.StarsGift.UpgradePrepaid" = "%@ unpacked the gift that you helped upgrade";
|
||||
|
||||
"Gift.View.Unknown.Title" = "Gift";
|
||||
|
|
|
|||
|
|
@ -393,7 +393,7 @@ public final class AuthorizationSequencePaymentScreen: ViewControllerComponentCo
|
|||
|
||||
loadServerCountryCodes(accountManager: sharedContext.accountManager, engine: engine, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.requestLayout(forceUpdate: true, transition: .immediate)
|
||||
strongSelf.requestLayout(forceUpdate: true, transition: ContainedViewLayoutTransition.immediate)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
public let followContentSizeChanges: Bool
|
||||
public let clipsContent: Bool
|
||||
public let isScrollEnabled: Bool
|
||||
public let hasDimView: Bool
|
||||
public let externalState: ExternalState?
|
||||
public let animateOut: ActionSlot<Action<()>>
|
||||
public let onPan: () -> Void
|
||||
|
|
@ -75,6 +76,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
followContentSizeChanges: Bool = false,
|
||||
clipsContent: Bool = false,
|
||||
isScrollEnabled: Bool = true,
|
||||
hasDimView: Bool = true,
|
||||
externalState: ExternalState? = nil,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
onPan: @escaping () -> Void = {},
|
||||
|
|
@ -85,6 +87,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
self.followContentSizeChanges = followContentSizeChanges
|
||||
self.clipsContent = clipsContent
|
||||
self.isScrollEnabled = isScrollEnabled
|
||||
self.hasDimView = hasDimView
|
||||
self.externalState = externalState
|
||||
self.animateOut = animateOut
|
||||
self.onPan = onPan
|
||||
|
|
@ -101,6 +104,12 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
if lhs.followContentSizeChanges != rhs.followContentSizeChanges {
|
||||
return false
|
||||
}
|
||||
if lhs.isScrollEnabled != rhs.isScrollEnabled {
|
||||
return false
|
||||
}
|
||||
if lhs.hasDimView != rhs.hasDimView {
|
||||
return false
|
||||
}
|
||||
if lhs.animateOut != rhs.animateOut {
|
||||
return false
|
||||
}
|
||||
|
|
@ -414,6 +423,10 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
|
||||
self.currentAvailableSize = availableSize
|
||||
|
||||
if !component.hasDimView {
|
||||
self.dimView.backgroundColor = .clear
|
||||
}
|
||||
|
||||
if environment[SheetComponentEnvironment.self].value.isDisplaying, !self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) {
|
||||
self.animateIn()
|
||||
} else if !environment[SheetComponentEnvironment.self].value.isDisplaying, self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) {
|
||||
|
|
|
|||
|
|
@ -134,9 +134,13 @@ open class ViewControllerComponentContainer: ViewController {
|
|||
}
|
||||
|
||||
public final class AnimateInTransition {
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
public final class AnimateOutTransition {
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
public final class Node: ViewControllerTracingNode {
|
||||
|
|
@ -356,7 +360,7 @@ open class ViewControllerComponentContainer: ViewController {
|
|||
}
|
||||
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
strongSelf.containerLayoutUpdated(layout, transition: ContainedViewLayoutTransition.immediate)
|
||||
}
|
||||
}
|
||||
}).strict()
|
||||
|
|
@ -412,6 +416,14 @@ open class ViewControllerComponentContainer: ViewController {
|
|||
self.forceNextUpdate = false
|
||||
}
|
||||
|
||||
public func requestLayout(forceUpdate: Bool, transition: ComponentTransition) {
|
||||
self.forceNextUpdate = forceUpdate
|
||||
if self.isViewLoaded, let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout, transition: transition)
|
||||
}
|
||||
self.forceNextUpdate = false
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
|
|
@ -421,6 +433,15 @@ open class ViewControllerComponentContainer: ViewController {
|
|||
self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: ComponentTransition(transition))
|
||||
}
|
||||
|
||||
public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ComponentTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
let navigationHeight = self.navigationLayout(layout: layout).navigationFrame.maxY
|
||||
|
||||
self.validLayout = layout
|
||||
self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
public func updateComponent(component: AnyComponent<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) {
|
||||
self.component = component
|
||||
self.node.updateComponent(component: component, transition: transition)
|
||||
|
|
|
|||
|
|
@ -156,7 +156,8 @@ final class VideoAdComponent: Component {
|
|||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: UIColor.white.withAlphaComponent(0.2),
|
||||
text: .plain(attributedText)
|
||||
text: .plain(attributedText),
|
||||
maximumNumberOfLines: 0
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId))
|
||||
case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId):
|
||||
return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)))
|
||||
case let .messageActionStarGift(flags, apiGift, message, convertStars, upgradeMessageId, upgradeStars, fromId, peer, savedId, _):
|
||||
case let .messageActionStarGift(flags, apiGift, message, convertStars, upgradeMessageId, upgradeStars, fromId, peer, savedId, prepaidUpgradeHash):
|
||||
let text: String?
|
||||
let entities: [MessageTextEntity]?
|
||||
switch message {
|
||||
|
|
@ -191,7 +191,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
}
|
||||
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId))
|
||||
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, isPrepaidUpgrade: (flags & (1 << 13)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, prepaidUpgradeHash: prepaidUpgradeHash))
|
||||
case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleAmount, canTransferDate, canResaleDate):
|
||||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne
|
|||
if let file = files[fileId], let patternFile = files[patternFileId], let numberString = slugComponents.last, let number = Int32(numberString) {
|
||||
let gift = StarGift.UniqueGift(
|
||||
id: id,
|
||||
giftId: 0,
|
||||
title: title,
|
||||
number: number,
|
||||
slug: slug,
|
||||
|
|
@ -356,7 +357,9 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne
|
|||
giftAddress: nil,
|
||||
resellAmounts: nil,
|
||||
resellForTonOnly: false,
|
||||
releasedBy: nil
|
||||
releasedBy: nil,
|
||||
valueAmount: nil,
|
||||
valueCurrency: nil
|
||||
)
|
||||
if let entry = CodableEntry(RecentStarGiftItem(gift)) {
|
||||
items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry))
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String)
|
||||
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
|
||||
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?)
|
||||
case paidMessagesRefunded(count: Int32, stars: Int64)
|
||||
case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool)
|
||||
|
|
@ -375,7 +375,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
}
|
||||
self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId)
|
||||
case 44:
|
||||
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"))
|
||||
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), prepaidUpgradeHash: decoder.decodeOptionalStringForKey("prepaidUpgradeHash"))
|
||||
case 45:
|
||||
var resaleAmount: CurrencyAmount?
|
||||
if let amount = decoder.decodeCodable(CurrencyAmount.self, forKey: "resaleAmount") {
|
||||
|
|
@ -705,7 +705,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "giveawayMsgId")
|
||||
}
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, upgradeMessageId, peerId, senderId, savedId):
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash):
|
||||
encoder.encodeInt32(44, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
if let convertStars {
|
||||
|
|
@ -752,6 +752,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "savedId")
|
||||
}
|
||||
if let prepaidUpgradeHash {
|
||||
encoder.encodeString(prepaidUpgradeHash, forKey: "prepaidUpgradeHash")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "prepaidUpgradeHash")
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate):
|
||||
encoder.encodeInt32(45, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
|
|
@ -877,7 +882,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
return [peerId]
|
||||
case let .prizeStars(_, _, boostPeerId, _, _):
|
||||
return boostPeerId.flatMap { [$0] } ?? []
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _):
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _, _):
|
||||
var peerIds: [PeerId] = []
|
||||
if let peerId {
|
||||
peerIds.append(peerId)
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
public struct UniqueGift: Equatable, Codable, PostboxCoding {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case giftId
|
||||
case title
|
||||
case number
|
||||
case slug
|
||||
|
|
@ -304,6 +305,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
case resellAmounts
|
||||
case resellForTonOnly
|
||||
case releasedBy
|
||||
case valueAmount
|
||||
case valueCurrency
|
||||
}
|
||||
|
||||
public enum Attribute: Equatable, Codable, PostboxCoding {
|
||||
|
|
@ -543,12 +546,30 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public struct ValueInfo: Equatable {
|
||||
public let isLastSaleOnFragment: Bool
|
||||
public let valueIsAverage: Bool
|
||||
public let value: Int64
|
||||
public let currency: String
|
||||
public let initialSaleDate: Int32
|
||||
public let initialSaleStars: Int64
|
||||
public let initialSalePrice: Int64
|
||||
public let lastSaleDate: Int32?
|
||||
public let lastSalePrice: Int64?
|
||||
public let floorPrice: Int64?
|
||||
public let averagePrice: Int64?
|
||||
public let listedCount: Int32?
|
||||
public let fragmentListedCount: Int32?
|
||||
public let fragmentListedUrl: String?
|
||||
}
|
||||
|
||||
public enum DecodingError: Error {
|
||||
case generic
|
||||
}
|
||||
|
||||
public let id: Int64
|
||||
public let giftId: Int64
|
||||
public let title: String
|
||||
public let number: Int32
|
||||
public let slug: String
|
||||
|
|
@ -559,9 +580,12 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
public let resellAmounts: [CurrencyAmount]?
|
||||
public let resellForTonOnly: Bool
|
||||
public let releasedBy: EnginePeer.Id?
|
||||
public let valueAmount: Int64?
|
||||
public let valueCurrency: String?
|
||||
|
||||
public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellAmounts: [CurrencyAmount]?, resellForTonOnly: Bool, releasedBy: EnginePeer.Id?) {
|
||||
public init(id: Int64, giftId: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellAmounts: [CurrencyAmount]?, resellForTonOnly: Bool, releasedBy: EnginePeer.Id?, valueAmount: Int64?, valueCurrency: String?) {
|
||||
self.id = id
|
||||
self.giftId = giftId
|
||||
self.title = title
|
||||
self.number = number
|
||||
self.slug = slug
|
||||
|
|
@ -572,11 +596,14 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
self.resellAmounts = resellAmounts
|
||||
self.resellForTonOnly = resellForTonOnly
|
||||
self.releasedBy = releasedBy
|
||||
self.valueAmount = valueAmount
|
||||
self.valueCurrency = valueCurrency
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.id = try container.decode(Int64.self, forKey: .id)
|
||||
self.giftId = try container.decode(Int64.self, forKey: .giftId)
|
||||
self.title = try container.decode(String.self, forKey: .title)
|
||||
self.number = try container.decode(Int32.self, forKey: .number)
|
||||
self.slug = try container.decodeIfPresent(String.self, forKey: .slug) ?? ""
|
||||
|
|
@ -601,10 +628,13 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
}
|
||||
self.resellForTonOnly = try container.decodeIfPresent(Bool.self, forKey: .resellForTonOnly) ?? false
|
||||
self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy)
|
||||
self.valueAmount = try container.decodeIfPresent(Int64.self, forKey: .valueAmount)
|
||||
self.valueCurrency = try container.decodeIfPresent(String.self, forKey: .valueCurrency)
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.id = decoder.decodeInt64ForKey(CodingKeys.id.rawValue, orElse: 0)
|
||||
self.giftId = decoder.decodeInt64ForKey(CodingKeys.giftId.rawValue, orElse: 0)
|
||||
self.title = decoder.decodeStringForKey(CodingKeys.title.rawValue, orElse: "")
|
||||
self.number = decoder.decodeInt32ForKey(CodingKeys.number.rawValue, orElse: 0)
|
||||
self.slug = decoder.decodeStringForKey(CodingKeys.slug.rawValue, orElse: "")
|
||||
|
|
@ -629,11 +659,14 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
}
|
||||
self.resellForTonOnly = decoder.decodeBoolForKey(CodingKeys.resellForTonOnly.rawValue, orElse: false)
|
||||
self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) }
|
||||
self.valueAmount = decoder.decodeOptionalInt64ForKey(CodingKeys.valueAmount.rawValue)
|
||||
self.valueCurrency = decoder.decodeOptionalStringForKey(CodingKeys.valueCurrency.rawValue)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.id, forKey: .id)
|
||||
try container.encode(self.giftId, forKey: .giftId)
|
||||
try container.encode(self.title, forKey: .title)
|
||||
try container.encode(self.number, forKey: .number)
|
||||
try container.encode(self.slug, forKey: .slug)
|
||||
|
|
@ -651,10 +684,13 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
try container.encodeIfPresent(self.resellAmounts, forKey: .resellAmounts)
|
||||
try container.encode(self.resellForTonOnly, forKey: .resellForTonOnly)
|
||||
try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy)
|
||||
try container.encodeIfPresent(self.valueAmount, forKey: .valueAmount)
|
||||
try container.encodeIfPresent(self.valueCurrency, forKey: .valueCurrency)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.id, forKey: CodingKeys.id.rawValue)
|
||||
encoder.encodeInt64(self.giftId, forKey: CodingKeys.giftId.rawValue)
|
||||
encoder.encodeString(self.title, forKey: CodingKeys.title.rawValue)
|
||||
encoder.encodeInt32(self.number, forKey: CodingKeys.number.rawValue)
|
||||
encoder.encodeString(self.slug, forKey: CodingKeys.slug.rawValue)
|
||||
|
|
@ -684,11 +720,19 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: CodingKeys.releasedBy.rawValue)
|
||||
}
|
||||
if let valueAmount = self.valueAmount, let valueCurrency = self.valueCurrency {
|
||||
encoder.encodeInt64(valueAmount, forKey: CodingKeys.valueAmount.rawValue)
|
||||
encoder.encodeString(valueCurrency, forKey: CodingKeys.valueCurrency.rawValue)
|
||||
} else {
|
||||
encoder.encodeNil(forKey: CodingKeys.valueAmount.rawValue)
|
||||
encoder.encodeNil(forKey: CodingKeys.valueCurrency.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func withResellAmounts(_ resellAmounts: [CurrencyAmount]?) -> UniqueGift {
|
||||
return UniqueGift(
|
||||
id: self.id,
|
||||
giftId: self.giftId,
|
||||
title: self.title,
|
||||
number: self.number,
|
||||
slug: self.slug,
|
||||
|
|
@ -698,13 +742,16 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
giftAddress: self.giftAddress,
|
||||
resellAmounts: resellAmounts,
|
||||
resellForTonOnly: self.resellForTonOnly,
|
||||
releasedBy: self.releasedBy
|
||||
releasedBy: self.releasedBy,
|
||||
valueAmount: self.valueAmount,
|
||||
valueCurrency: self.valueCurrency
|
||||
)
|
||||
}
|
||||
|
||||
public func withResellForTonOnly(_ resellForTonOnly: Bool) -> UniqueGift {
|
||||
return UniqueGift(
|
||||
id: self.id,
|
||||
giftId: self.giftId,
|
||||
title: self.title,
|
||||
number: self.number,
|
||||
slug: self.slug,
|
||||
|
|
@ -714,7 +761,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
giftAddress: self.giftAddress,
|
||||
resellAmounts: self.resellAmounts,
|
||||
resellForTonOnly: resellForTonOnly,
|
||||
releasedBy: self.releasedBy
|
||||
releasedBy: self.releasedBy,
|
||||
valueAmount: self.valueAmount,
|
||||
valueCurrency: self.valueCurrency
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -824,7 +873,7 @@ extension StarGift {
|
|||
return nil
|
||||
}
|
||||
self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId, perUserLimit: perUserLimit))
|
||||
case let .starGiftUnique(flags, id, _, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, _, _):
|
||||
case let .starGiftUnique(flags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency):
|
||||
let owner: StarGift.UniqueGift.Owner
|
||||
if let ownerAddress {
|
||||
owner = .address(ownerAddress)
|
||||
|
|
@ -836,7 +885,7 @@ extension StarGift {
|
|||
return nil
|
||||
}
|
||||
let resellAmounts = resellAmounts?.compactMap { CurrencyAmount(apiAmount: $0) }
|
||||
self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellAmounts: resellAmounts, resellForTonOnly: (flags & (1 << 7)) != 0, releasedBy: releasedBy?.peerId))
|
||||
self = .unique(StarGift.UniqueGift(id: id, giftId: giftId, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellAmounts: resellAmounts, resellForTonOnly: (flags & (1 << 7)) != 0, releasedBy: releasedBy?.peerId, valueAmount: valueAmount, valueCurrency: valueCurrency))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2530,6 +2579,41 @@ func _internal_getUniqueStarGift(account: Account, slug: String) -> Signal<StarG
|
|||
}
|
||||
}
|
||||
|
||||
func _internal_getUniqueStarGiftValueInfo(account: Account, slug: String) -> Signal<StarGift.UniqueGift.ValueInfo?, NoError> {
|
||||
return account.network.request(Api.functions.payments.getUniqueStarGiftValueInfo(slug: slug))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.payments.UniqueStarGiftValueInfo?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> StarGift.UniqueGift.ValueInfo? in
|
||||
if let result {
|
||||
switch result {
|
||||
case let .uniqueStarGiftValueInfo(flags, currency, value, initialSaleDate, initialSaleStars, initialSalePrice, lastSaleDate, lastSalePrice, floorPrice, averagePrice, listedCount, fragmentListedCount, fragmentListedUrl):
|
||||
let _ = listedCount
|
||||
let _ = fragmentListedCount
|
||||
return StarGift.UniqueGift.ValueInfo(
|
||||
isLastSaleOnFragment: flags & (1 << 1) != 0,
|
||||
valueIsAverage: flags & (1 << 6) != 0,
|
||||
value: value,
|
||||
currency: currency,
|
||||
initialSaleDate: initialSaleDate,
|
||||
initialSaleStars: initialSaleStars,
|
||||
initialSalePrice: initialSalePrice,
|
||||
lastSaleDate: lastSaleDate,
|
||||
lastSalePrice: lastSalePrice,
|
||||
floorPrice: floorPrice,
|
||||
averagePrice: averagePrice,
|
||||
listedCount: listedCount,
|
||||
fragmentListedCount: fragmentListedCount,
|
||||
fragmentListedUrl: fragmentListedUrl
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum StarGiftReference: Equatable, Hashable, Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
|
|
|
|||
|
|
@ -145,6 +145,10 @@ public extension TelegramEngine {
|
|||
return _internal_getUniqueStarGift(account: self.account, slug: slug)
|
||||
}
|
||||
|
||||
public func getUniqueStarGiftValueInfo(slug: String) -> Signal<StarGift.UniqueGift.ValueInfo?, NoError> {
|
||||
return _internal_getUniqueStarGiftValueInfo(account: self.account, slug: slug)
|
||||
}
|
||||
|
||||
public func checkStarGiftWithdrawalAvailability(reference: StarGiftReference) -> Signal<Never, RequestStarGiftWithdrawalError> {
|
||||
return _internal_checkStarGiftWithdrawalAvailability(account: self.account, reference: reference)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1134,7 +1134,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = mutableString
|
||||
case .prizeStars:
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor)
|
||||
case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, _, _, peerId, senderId, _):
|
||||
case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, isPrepaidUpgrade, _, peerId, senderId, _, _):
|
||||
if !forAdditionalServiceMessage {
|
||||
if let text {
|
||||
let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage()))
|
||||
|
|
@ -1159,7 +1159,36 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
} else if message.id.peerId == accountPeerId {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Self_Bought(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
} else if message.author?.id == accountPeerId {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentYou(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
if isPrepaidUpgrade, let peerId {
|
||||
peerIds = [(1, peerId)]
|
||||
var peerName = ""
|
||||
if let name = message.peers[peerId].flatMap(EnginePeer.init)?.compactDisplayTitle {
|
||||
peerName = name
|
||||
}
|
||||
let starsPrice = strings.Notification_StarsGift_Stars(Int32(clamping: upgradeStars ?? 0))
|
||||
if let senderId, senderId != accountPeerId {
|
||||
if senderId == peerId {
|
||||
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
|
||||
attributes[0] = boldAttributes
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentUpgradeYouUnknown(starsPrice, peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
} else {
|
||||
var otherPeerName = ""
|
||||
if let name = message.peers[senderId].flatMap(EnginePeer.init)?.compactDisplayTitle {
|
||||
otherPeerName = name
|
||||
}
|
||||
peerIds = [(1, peerId), (2, senderId)]
|
||||
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
|
||||
attributes[0] = boldAttributes
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentUpgradeYouOther(starsPrice, peerName, otherPeerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
} else {
|
||||
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
|
||||
attributes[0] = boldAttributes
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentUpgradeYou(starsPrice, peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
} else {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_SentYou(starsPrice)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
}
|
||||
} else if let peerId {
|
||||
peerIds = [(1, peerId)]
|
||||
var peerName = ""
|
||||
|
|
@ -1181,7 +1210,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, _, peerId, senderId, _, resaleStars, _, _):
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _):
|
||||
if case let .unique(gift) = gift {
|
||||
if !forAdditionalServiceMessage && !"".isEmpty {
|
||||
attributedString = NSAttributedString(string: "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))", font: titleFont, textColor: primaryTextColor)
|
||||
|
|
@ -1195,10 +1224,18 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = NSAttributedString(string: strings.Notification_StarsGift_UpgradeSelf, font: titleFont, textColor: primaryTextColor)
|
||||
} else if message.author?.id == accountPeerId {
|
||||
let attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_UpgradeYou(peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
if isPrepaidUpgrade {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_UpgradePrepaidYou(peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
} else {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_UpgradeYou(peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
} else {
|
||||
let attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: peerIds)
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Upgrade(peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
if isPrepaidUpgrade {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_UpgradePrepaid(peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
} else {
|
||||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Upgrade(peerName)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if message.id.peerId.isTelegramNotifications && senderId == nil {
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
for media in item.message.media {
|
||||
if let action = media as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
|
|
@ -547,7 +547,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View
|
||||
hasServiceMessage = false
|
||||
}
|
||||
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, _, channelPeerId, senderPeerId, _):
|
||||
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, isPrepaidUpgrade, _, channelPeerId, senderPeerId, _, _):
|
||||
if case let .generic(gift) = gift {
|
||||
if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName {
|
||||
creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string
|
||||
|
|
@ -561,10 +561,14 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
if isSelfGift {
|
||||
title = item.presentationData.strings.Notification_StarGift_Self_Title
|
||||
} else {
|
||||
if let senderPeerId, let name = item.message.peers[senderPeerId].flatMap(EnginePeer.init)?.compactDisplayTitle {
|
||||
authorName = name
|
||||
if isPrepaidUpgrade && senderPeerId == channelPeerId {
|
||||
title = item.presentationData.strings.Gift_View_Unknown_Title
|
||||
} else {
|
||||
if let senderPeerId, let name = item.message.peers[senderPeerId].flatMap(EnginePeer.init)?.compactDisplayTitle {
|
||||
authorName = name
|
||||
}
|
||||
title = item.presentationData.strings.Notification_StarGift_Title(authorName).string
|
||||
}
|
||||
title = item.presentationData.strings.Notification_StarGift_Title(authorName).string
|
||||
}
|
||||
if let giftText, !giftText.isEmpty {
|
||||
text = giftText
|
||||
|
|
@ -611,7 +615,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle
|
||||
}
|
||||
} else {
|
||||
if upgradeStars != nil {
|
||||
if isPrepaidUpgrade {
|
||||
text = item.presentationData.strings.Notification_StarGift_Subtitle_Upgrade_Prepaid(peerName).string
|
||||
} else if upgradeStars != nil {
|
||||
text = item.presentationData.strings.Notification_StarGift_Subtitle_Upgrade_Other(peerName).string
|
||||
} else if let convertStars, convertStars > 0 {
|
||||
let starsString = item.presentationData.strings.Notification_StarGift_Subtitle_Other_Stars(Int32(clamping: convertStars)).replacingOccurrences(of: " ", with: "\u{00A0}")
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ public final class GiftItemComponent: Component {
|
|||
case preview
|
||||
case grid
|
||||
case select
|
||||
case buttonIcon
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
|
|
@ -375,6 +376,10 @@ public final class GiftItemComponent: Component {
|
|||
size = availableSize
|
||||
iconSize = CGSize(width: floor(size.width * 0.6), height: floor(size.width * 0.6))
|
||||
cornerRadius = 4.0
|
||||
case .buttonIcon:
|
||||
size = CGSize(width: 26.0, height: 26.0)
|
||||
iconSize = size
|
||||
cornerRadius = 0.0
|
||||
}
|
||||
var backgroundSize = size
|
||||
if case .grid = component.mode {
|
||||
|
|
@ -464,7 +469,7 @@ public final class GiftItemComponent: Component {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let animationFile {
|
||||
emoji = ChatTextInputTextCustomEmojiAttribute(
|
||||
interactivelySelectedFromPackId: nil,
|
||||
|
|
@ -476,6 +481,13 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
if case .buttonIcon = component.mode {
|
||||
backgroundColor = nil
|
||||
secondBackgroundColor = nil
|
||||
patternColor = nil
|
||||
placeholderColor = component.theme.list.mediaPlaceholderColor
|
||||
}
|
||||
|
||||
var animationTransition = transition
|
||||
if self.animationLayer == nil || self.animationFile?.fileId != animationFile?.fileId, let emoji {
|
||||
animationTransition = .immediate
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
|
|||
case let .starGift(gift):
|
||||
media = [
|
||||
TelegramMediaAction(
|
||||
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: gift.upgradeStars != nil, upgradeStars: item.upgradeStars, isRefunded: false, isPrepaidUpgrade: false, upgradeMessageId: nil, peerId: nil, senderId: nil, savedId: nil)
|
||||
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: gift.upgradeStars != nil, upgradeStars: item.upgradeStars, isRefunded: false, isPrepaidUpgrade: false, upgradeMessageId: nil, peerId: nil, senderId: nil, savedId: nil, prepaidUpgradeHash: nil)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import ComponentFlow
|
|||
import Display
|
||||
import TelegramPresentationData
|
||||
import ViewControllerComponent
|
||||
import SheetComponent
|
||||
import AccountContext
|
||||
|
||||
final class GiftPagerComponent: Component {
|
||||
|
|
@ -34,6 +35,7 @@ final class GiftPagerComponent: Component {
|
|||
let items: [Item]
|
||||
let index: Int
|
||||
let itemSpacing: CGFloat
|
||||
let isSwitching: Bool
|
||||
let updated: (CGFloat, Int) -> Void
|
||||
|
||||
public init(
|
||||
|
|
@ -41,12 +43,14 @@ final class GiftPagerComponent: Component {
|
|||
items: [Item],
|
||||
index: Int = 0,
|
||||
itemSpacing: CGFloat = 0.0,
|
||||
isSwitching: Bool = false,
|
||||
updated: @escaping (CGFloat, Int) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.items = items
|
||||
self.index = index
|
||||
self.itemSpacing = itemSpacing
|
||||
self.isSwitching = isSwitching
|
||||
self.updated = updated
|
||||
}
|
||||
|
||||
|
|
@ -60,10 +64,14 @@ final class GiftPagerComponent: Component {
|
|||
if lhs.itemSpacing != rhs.itemSpacing {
|
||||
return false
|
||||
}
|
||||
if lhs.isSwitching != rhs.isSwitching {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let dimView: UIView
|
||||
private let scrollView: UIScrollView
|
||||
private var itemViews: [AnyHashable: ComponentHostView<EnvironmentType>] = [:]
|
||||
|
||||
|
|
@ -71,7 +79,11 @@ final class GiftPagerComponent: Component {
|
|||
private var environment: Environment<EnvironmentType>?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.dimView = UIView()
|
||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
|
||||
|
||||
self.scrollView = UIScrollView(frame: frame)
|
||||
self.scrollView.clipsToBounds = true
|
||||
self.scrollView.isPagingEnabled = true
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
|
|
@ -84,8 +96,9 @@ final class GiftPagerComponent: Component {
|
|||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.scrollView.delegate = self
|
||||
self.addSubview(self.dimView)
|
||||
|
||||
self.scrollView.delegate = self
|
||||
self.addSubview(self.scrollView)
|
||||
}
|
||||
|
||||
|
|
@ -93,6 +106,15 @@ final class GiftPagerComponent: Component {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func animateIn() {
|
||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
|
||||
}
|
||||
|
||||
private func animateOut() {
|
||||
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
private var isSwiping: Bool = false
|
||||
private var lastScrollTime: TimeInterval = 0
|
||||
private let swipeInactiveThreshold: TimeInterval = 0.5
|
||||
|
|
@ -128,33 +150,43 @@ final class GiftPagerComponent: Component {
|
|||
self.ignoreContentOffsetChange = false
|
||||
}
|
||||
|
||||
private var previousIsDisplaying: Bool = false
|
||||
|
||||
private var isUpdating = true
|
||||
func update(component: GiftPagerComponent, availableSize: CGSize, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.environment = environment
|
||||
|
||||
let firstTime = self.itemViews.isEmpty
|
||||
var countDecreased = false
|
||||
if let previousComponent, previousComponent.items.count != component.items.count {
|
||||
countDecreased = true
|
||||
}
|
||||
let firstTime = self.itemViews.isEmpty || countDecreased
|
||||
|
||||
let itemWidth = availableSize.width
|
||||
let totalWidth = itemWidth * CGFloat(component.items.count) + component.itemSpacing * CGFloat(max(0, component.items.count - 1))
|
||||
let totalWidth = itemWidth * CGFloat(component.items.count) + component.itemSpacing * 2.0 * CGFloat(max(0, component.items.count))
|
||||
|
||||
let contentSize = CGSize(width: totalWidth, height: availableSize.height)
|
||||
if self.scrollView.contentSize != contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
let scrollFrame = CGRect(origin: .zero, size: availableSize)
|
||||
let scrollFrame = CGRect(origin: CGPoint(x: -component.itemSpacing / 2.0, y: 0.0), size: CGSize(width: availableSize.width + component.itemSpacing * 2.0, height: availableSize.height))
|
||||
if self.scrollView.frame != scrollFrame {
|
||||
self.scrollView.frame = scrollFrame
|
||||
}
|
||||
|
||||
if firstTime {
|
||||
let initialOffset = CGFloat(component.index) * (itemWidth + component.itemSpacing)
|
||||
let initialOffset = CGFloat(component.index) * (itemWidth + component.itemSpacing * 2.0)
|
||||
self.scrollView.contentOffset = CGPoint(x: initialOffset, y: 0.0)
|
||||
|
||||
var position: CGFloat
|
||||
|
|
@ -172,7 +204,7 @@ final class GiftPagerComponent: Component {
|
|||
|
||||
var i = 0
|
||||
for item in component.items {
|
||||
let itemOriginX = (itemWidth + component.itemSpacing) * CGFloat(i)
|
||||
let itemOriginX = component.itemSpacing * 0.5 + (itemWidth + component.itemSpacing * 2.0) * CGFloat(i)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: itemOriginX, y: 0.0), size: CGSize(width: itemWidth, height: availableSize.height))
|
||||
|
||||
let centerDelta = itemFrame.midX - viewportCenter
|
||||
|
|
@ -216,17 +248,39 @@ final class GiftPagerComponent: Component {
|
|||
itemView.frame = itemFrame
|
||||
}
|
||||
|
||||
var animateTransitionMovement = false
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
itemView.removeFromSuperview()
|
||||
if countDecreased && !transition.animation.isImmediate {
|
||||
self.addSubview(itemView)
|
||||
itemView.center = CGPoint(x: self.scrollView.frame.width / 2.0 - component.itemSpacing, y: self.scrollView.frame.height / 2.0)
|
||||
transition.setPosition(view: itemView, position: CGPoint(x: itemView.center.x - itemView.frame.width, y: itemView.center.y), completion: { _ in
|
||||
itemView.removeFromSuperview()
|
||||
})
|
||||
animateTransitionMovement = true
|
||||
} else {
|
||||
itemView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
|
||||
if animateTransitionMovement {
|
||||
transition.animatePosition(view: self.scrollView, from: CGPoint(x: self.scrollView.frame.width, y: 0.0), to: .zero, additive: true)
|
||||
}
|
||||
|
||||
let viewEnvironment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
if let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) {
|
||||
self.animateIn()
|
||||
} else if self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) {
|
||||
self.animateOut()
|
||||
}
|
||||
self.previousIsDisplaying = viewEnvironment.isVisible
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,31 +135,11 @@ private final class GiftPurchaseAlertContentNode: AlertContentNode {
|
|||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let containerSize = size
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
self.validLayout = size
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
var resellPrice: CurrencyAmount?
|
||||
if let actionNode = self.actionNodes.first {
|
||||
switch self.currency {
|
||||
case .stars:
|
||||
if let resellAmount = self.gift.resellAmounts?.first(where: { $0.currency == .stars }) {
|
||||
resellPrice = resellAmount
|
||||
actionNode.action = TextAlertAction(type: .defaultAction, title: self.strings.Gift_Buy_Confirm_BuyFor(Int32(resellAmount.amount.value)), action: actionNode.action.action)
|
||||
}
|
||||
case .ton:
|
||||
if let resellAmount = self.gift.resellAmounts?.first(where: { $0.currency == .ton }) {
|
||||
resellPrice = resellAmount
|
||||
let valueString = formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
actionNode.action = TextAlertAction(type: .defaultAction, title: self.strings.Gift_Buy_Confirm_BuyForTon(valueString).string, action: actionNode.action.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var origin = CGPoint(x: 0.0, y: 20.0)
|
||||
if self.gift.resellForTonOnly {
|
||||
let headerSize = self.header.update(
|
||||
transition: .immediate,
|
||||
|
|
@ -173,6 +153,7 @@ private final class GiftPurchaseAlertContentNode: AlertContentNode {
|
|||
environment: {},
|
||||
containerSize: CGSize(width: size.width - 32.0, height: size.height)
|
||||
)
|
||||
|
||||
let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - headerSize.width) / 2.0), y: origin.y), size: headerSize)
|
||||
if let view = self.header.view {
|
||||
if view.superview == nil {
|
||||
|
|
@ -226,8 +207,11 @@ private final class GiftPurchaseAlertContentNode: AlertContentNode {
|
|||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width - 16.0 * 2.0, height: 100.0)
|
||||
containerSize: CGSize(width: containerSize.width - 16.0 * 2.0, height: 100.0)
|
||||
)
|
||||
|
||||
size.width = min(containerSize.width, max(270.0, headerSize.width + 32.0))
|
||||
|
||||
let headerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - headerSize.width) / 2.0), y: origin.y), size: headerSize)
|
||||
if let view = self.header.view {
|
||||
if view.superview == nil {
|
||||
|
|
@ -238,6 +222,27 @@ private final class GiftPurchaseAlertContentNode: AlertContentNode {
|
|||
origin.y += headerSize.height + 17.0
|
||||
}
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var resellPrice: CurrencyAmount?
|
||||
if let actionNode = self.actionNodes.first {
|
||||
switch self.currency {
|
||||
case .stars:
|
||||
if let resellAmount = self.gift.resellAmounts?.first(where: { $0.currency == .stars }) {
|
||||
resellPrice = resellAmount
|
||||
actionNode.action = TextAlertAction(type: .defaultAction, title: self.strings.Gift_Buy_Confirm_BuyFor(Int32(resellAmount.amount.value)), action: actionNode.action.action)
|
||||
}
|
||||
case .ton:
|
||||
if let resellAmount = self.gift.resellAmounts?.first(where: { $0.currency == .ton }) {
|
||||
resellPrice = resellAmount
|
||||
let valueString = formatTonAmountText(resellAmount.amount.value, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
actionNode.action = TextAlertAction(type: .defaultAction, title: self.strings.Gift_Buy_Confirm_BuyForTon(valueString).string, action: actionNode.action.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
self.avatarNode.updateSize(size: avatarSize)
|
||||
|
||||
|
|
@ -495,9 +500,13 @@ public func giftPurchaseAlertController(
|
|||
|
||||
if !gift.resellForTonOnly {
|
||||
Queue.mainQueue().after(0.3) {
|
||||
if let headerView = contentNode?.header.view {
|
||||
if let headerView = contentNode?.header.view as? TabSelectorComponent.View {
|
||||
let absoluteFrame = headerView.convert(headerView.bounds, to: nil)
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.minX + floor(absoluteFrame.width * 0.75), y: absoluteFrame.minY - 8.0), size: CGSize())
|
||||
var originX = absoluteFrame.width * 0.75
|
||||
if let itemFrame = headerView.frameForItem(AnyHashable(1)) {
|
||||
originX = itemFrame.midX
|
||||
}
|
||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.minX + floor(originX), y: absoluteFrame.minY - 8.0), size: CGSize())
|
||||
let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: presentationData.strings.Gift_Buy_PayInTon_Tooltip), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,862 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import PresentationDataUtils
|
||||
import ComponentFlow
|
||||
import ViewControllerComponent
|
||||
import SheetComponent
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import BundleIconComponent
|
||||
import ButtonComponent
|
||||
import Markdown
|
||||
import BalancedTextComponent
|
||||
import AvatarNode
|
||||
import TextFormat
|
||||
import TelegramStringFormatting
|
||||
import StarsAvatarComponent
|
||||
import EmojiTextAttachmentView
|
||||
import EmojiStatusComponent
|
||||
import UndoUI
|
||||
import PlainButtonComponent
|
||||
import TooltipUI
|
||||
import GiftAnimationComponent
|
||||
import LottieComponent
|
||||
import ContextUI
|
||||
import TelegramNotices
|
||||
import GiftItemComponent
|
||||
|
||||
private final class GiftValueSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let gift: ProfileGiftsContext.State.StarGift
|
||||
let valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
let animateOut: ActionSlot<Action<()>>
|
||||
let getController: () -> ViewController?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
gift: ProfileGiftsContext.State.StarGift,
|
||||
valueInfo: StarGift.UniqueGift.ValueInfo,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
getController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.context = context
|
||||
self.gift = gift
|
||||
self.valueInfo = valueInfo
|
||||
self.animateOut = animateOut
|
||||
self.getController = getController
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftValueSheetContent, rhs: GiftValueSheetContent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.gift != rhs.gift {
|
||||
return false
|
||||
}
|
||||
if lhs.valueInfo != rhs.valueInfo {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class State: ComponentState {
|
||||
let minimumPriceTag = GenericComponentViewTag()
|
||||
let averagePriceTag = GenericComponentViewTag()
|
||||
|
||||
private let context: AccountContext
|
||||
private let animateOut: ActionSlot<Action<()>>
|
||||
private let getController: () -> ViewController?
|
||||
|
||||
private var disposable: Disposable?
|
||||
var initialized = false
|
||||
|
||||
var starGiftsMap: [Int64: StarGift.Gift] = [:]
|
||||
|
||||
var cachedStarImage: (UIImage, PresentationTheme)?
|
||||
var cachedSmallStarImage: (UIImage, PresentationTheme)?
|
||||
var cachedSubtitleStarImage: (UIImage, PresentationTheme)?
|
||||
var cachedTonImage: (UIImage, PresentationTheme)?
|
||||
|
||||
var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||
var cachedSmallChevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
getController: @escaping () -> ViewController?
|
||||
) {
|
||||
self.context = context
|
||||
self.animateOut = animateOut
|
||||
self.getController = getController
|
||||
|
||||
super.init()
|
||||
|
||||
self.disposable = (context.engine.payments.cachedStarGifts()
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] starGifts in
|
||||
if let strongSelf = self {
|
||||
var starGiftsMap: [Int64: StarGift.Gift] = [:]
|
||||
if let starGifts {
|
||||
for gift in starGifts {
|
||||
if case let .generic(gift) = gift {
|
||||
starGiftsMap[gift.id] = gift
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.starGiftsMap = starGiftsMap
|
||||
strongSelf.updated(transition: .immediate)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
func showAttributeInfo(tag: Any, text: String) {
|
||||
guard let controller = self.getController() as? GiftValueScreen else {
|
||||
return
|
||||
}
|
||||
controller.dismissAllTooltips()
|
||||
|
||||
guard let sourceView = controller.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: controller.view) else {
|
||||
return
|
||||
}
|
||||
|
||||
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
|
||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .markdown(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
})
|
||||
controller.present(tooltipController, in: .current)
|
||||
}
|
||||
|
||||
func openGiftResale(gift: StarGift.Gift) {
|
||||
guard let controller = self.getController() as? GiftValueScreen else {
|
||||
return
|
||||
}
|
||||
let storeController = self.context.sharedContext.makeGiftStoreController(
|
||||
context: self.context,
|
||||
peerId: self.context.account.peerId,
|
||||
gift: gift
|
||||
)
|
||||
controller.push(storeController)
|
||||
|
||||
Queue.mainQueue().after(2.0, {
|
||||
controller.dismiss(animated: false)
|
||||
})
|
||||
}
|
||||
|
||||
func dismiss(animated: Bool) {
|
||||
guard let controller = self.getController() as? GiftValueScreen else {
|
||||
return
|
||||
}
|
||||
if animated {
|
||||
controller.dismissAllTooltips()
|
||||
self.animateOut.invoke(Action { [weak controller] _ in
|
||||
controller?.dismiss(completion: nil)
|
||||
})
|
||||
} else {
|
||||
controller.dismiss(animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
return State(context: self.context, animateOut: self.animateOut, getController: self.getController)
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let buttons = Child(ButtonsComponent.self)
|
||||
let animation = Child(GiftCompositionComponent.self)
|
||||
|
||||
let titleBackground = Child(RoundedRectangle.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
|
||||
let table = Child(TableComponent.self)
|
||||
|
||||
let telegramSaleButton = Child(PlainButtonComponent.self)
|
||||
let fragmentSaleButton = Child(PlainButtonComponent.self)
|
||||
|
||||
let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
|
||||
let component = context.component
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
let dateTimeFormat = environment.dateTimeFormat
|
||||
//let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder
|
||||
//let controller = environment.controller
|
||||
|
||||
let state = context.state
|
||||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
|
||||
let titleString: String = formatCurrencyAmount(component.valueInfo.value, currency: component.valueInfo.currency)
|
||||
var giftTitle: String = ""
|
||||
var giftCollectionTitle: String = ""
|
||||
var animationFile: TelegramMediaFile?
|
||||
var giftIconSubject: GiftItemComponent.Subject?
|
||||
var genericGift: StarGift.Gift?
|
||||
|
||||
switch component.gift.gift {
|
||||
case let .generic(gift):
|
||||
animationFile = gift.file
|
||||
giftIconSubject = .starGift(gift: gift, price: "")
|
||||
case let .unique(gift):
|
||||
for attribute in gift.attributes {
|
||||
if case let .model(_, file, _) = attribute {
|
||||
animationFile = file
|
||||
}
|
||||
}
|
||||
giftCollectionTitle = gift.title
|
||||
giftTitle = "\(gift.title) #\(formatCollectibleNumber(gift.number, dateTimeFormat: dateTimeFormat))"
|
||||
|
||||
if let gift = state.starGiftsMap[gift.giftId] {
|
||||
giftIconSubject = .starGift(gift: gift, price: "")
|
||||
genericGift = gift
|
||||
}
|
||||
}
|
||||
|
||||
let buttons = buttons.update(
|
||||
component: ButtonsComponent(
|
||||
theme: theme,
|
||||
isOverlay: false,
|
||||
showMoreButton: false,
|
||||
closePressed: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.dismiss(animated: true)
|
||||
},
|
||||
morePressed: { _, _ in
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: 30.0, height: 30.0),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
var originY: CGFloat = 0.0
|
||||
|
||||
let headerHeight: CGFloat = 210.0
|
||||
let headerSubject: GiftCompositionComponent.Subject?
|
||||
if let animationFile {
|
||||
headerSubject = .generic(animationFile)
|
||||
} else {
|
||||
headerSubject = nil
|
||||
}
|
||||
|
||||
if let headerSubject {
|
||||
let animation = animation.update(
|
||||
component: GiftCompositionComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
subject: headerSubject,
|
||||
animationOffset: nil,
|
||||
animationScale: nil,
|
||||
displayAnimationStars: false,
|
||||
externalState: giftCompositionExternalState,
|
||||
requestUpdate: { [weak state] in
|
||||
state?.updated()
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: headerHeight),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(animation
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: headerHeight / 2.0))
|
||||
)
|
||||
}
|
||||
originY += headerHeight
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: titleString,
|
||||
font: Font.with(size: 24.0, design: .round, weight: .bold),
|
||||
textColor: theme.list.itemCheckColors.foregroundColor,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
let titleBackground = titleBackground.update(
|
||||
component: RoundedRectangle(color: theme.actionSheet.controlAccentColor, cornerRadius: 24.0),
|
||||
environment: {},
|
||||
availableSize: CGSize(width: title.size.width + 32.0, height: 48.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(titleBackground
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 187.0))
|
||||
)
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 187.0))
|
||||
)
|
||||
|
||||
var descriptionText: String
|
||||
if component.valueInfo.valueIsAverage {
|
||||
descriptionText = "This is the average sale price of **\(giftCollectionTitle)** on Telegram and Fragment over the past month."
|
||||
} else {
|
||||
if component.valueInfo.isLastSaleOnFragment {
|
||||
descriptionText = "This is the last price at which **\(giftTitle)** was last sold on Fragment."
|
||||
} else {
|
||||
descriptionText = "This is the last price at which **\(giftTitle)** was last sold on Telegram."
|
||||
}
|
||||
}
|
||||
if !descriptionText.isEmpty {
|
||||
let linkColor = theme.actionSheet.controlAccentColor
|
||||
if state.cachedSmallStarImage == nil || state.cachedSmallStarImage?.1 !== environment.theme {
|
||||
state.cachedSmallStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/ButtonStar"), color: .white)!, theme)
|
||||
}
|
||||
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
||||
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
|
||||
}
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = theme.list.itemPrimaryTextColor
|
||||
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
descriptionText = descriptionText.replacingOccurrences(of: " >]", with: "\u{00A0}>]")
|
||||
let attributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = attributedString.string.range(of: "*"), let starImage = state.cachedSmallStarImage?.0 {
|
||||
attributedString.addAttribute(.font, value: Font.regular(13.0), range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
|
||||
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
|
||||
let description = description.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(attributedString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 5,
|
||||
lineSpacing: 0.2,
|
||||
highlightColor: linkColor.withAlphaComponent(0.1),
|
||||
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { _, _ in
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(description
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 231.0 + description.size.height / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
originY += description.size.height
|
||||
originY += 42.0
|
||||
} else {
|
||||
originY += 9.0
|
||||
}
|
||||
|
||||
let tableFont = Font.regular(15.0)
|
||||
let tableTextColor = theme.list.itemPrimaryTextColor
|
||||
|
||||
var tableItems: [TableComponent.Item] = []
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "initialDate",
|
||||
title: "Initial Sale",
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: component.valueInfo.initialSaleDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
|
||||
let valueString = "⭐️\(formatStarsAmountText(StarsAmount(value: component.valueInfo.initialSaleStars, nanos: 0), dateTimeFormat: dateTimeFormat)) (≈\(formatCurrencyAmount(component.valueInfo.initialSalePrice, currency: component.valueInfo.currency)))"
|
||||
let valueAttributedString = NSMutableAttributedString(string: valueString, font: tableFont, textColor: tableTextColor)
|
||||
let range = (valueAttributedString.string as NSString).range(of: "⭐️")
|
||||
if range.location != NSNotFound {
|
||||
valueAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
||||
valueAttributedString.addAttribute(.baselineOffset, value: 1.0, range: range)
|
||||
}
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "initialPrice",
|
||||
title: "Initial Price",
|
||||
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: theme.list.mediaPlaceholderColor,
|
||||
text: .plain(valueAttributedString),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
|
||||
))
|
||||
|
||||
if let lastSaleDate = component.valueInfo.lastSaleDate {
|
||||
tableItems.append(.init(
|
||||
id: "lastDate",
|
||||
title: "Last Sale",
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: lastSaleDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
if let lastSalePrice = component.valueInfo.lastSalePrice {
|
||||
let lastSalePriceString = formatCurrencyAmount(lastSalePrice, currency: component.valueInfo.currency)
|
||||
let tag = state.minimumPriceTag
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: lastSalePriceString, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
let percentage = Double(lastSalePrice) / Double(component.valueInfo.initialSalePrice) * 100.0
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: component.context,
|
||||
text: "+\(Int32(percentage - 100))%",
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
state?.showAttributeInfo(tag: tag, text: "**\(lastSalePriceString)** is the last price for \(giftCollectionTitle) gifts listed on Telegram and Fragment.")
|
||||
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
let itemComponent = AnyComponent(
|
||||
HStack(items, spacing: 4.0)
|
||||
)
|
||||
tableItems.append(.init(
|
||||
id: "lastPrice",
|
||||
title: "Last Price",
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
}
|
||||
|
||||
if let floorPrice = component.valueInfo.floorPrice {
|
||||
let floorPriceString = formatCurrencyAmount(floorPrice, currency: component.valueInfo.currency)
|
||||
let tag = state.minimumPriceTag
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: floorPriceString, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
)
|
||||
)
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: component.context,
|
||||
text: "?",
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
state?.showAttributeInfo(tag: tag, text: "**\(floorPriceString)** is the floor price for \(giftCollectionTitle) gifts listed on Telegram and Fragment.")
|
||||
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
let itemComponent = AnyComponent(
|
||||
HStack(items, spacing: 4.0)
|
||||
)
|
||||
tableItems.append(.init(
|
||||
id: "minimumPrice",
|
||||
title: "Minumum Price",
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
}
|
||||
|
||||
if let averagePrice = component.valueInfo.averagePrice {
|
||||
let averagePriceString = formatCurrencyAmount(averagePrice, currency: component.valueInfo.currency)
|
||||
|
||||
let tag = state.averagePriceTag
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: averagePriceString, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
)
|
||||
)
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: component.context,
|
||||
text: "?",
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
state?.showAttributeInfo(tag: tag, text: "**\(averagePriceString)** is the average sale price of \(giftCollectionTitle) on Telegram and Fragment over the past month.")
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
let itemComponent = AnyComponent(
|
||||
HStack(items, spacing: 4.0)
|
||||
)
|
||||
tableItems.append(.init(
|
||||
id: "averagePrice",
|
||||
title: "Average Price",
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
}
|
||||
|
||||
let table = table.update(
|
||||
component: TableComponent(
|
||||
theme: environment.theme,
|
||||
items: tableItems
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(table
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
originY += table.size.height + 23.0
|
||||
|
||||
if component.valueInfo.listedCount != nil || component.valueInfo.fragmentListedCount != nil {
|
||||
originY += 5.0
|
||||
}
|
||||
|
||||
if let listedCount = component.valueInfo.listedCount, let giftIconSubject {
|
||||
let telegramSaleButton = telegramSaleButton.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: "count", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(listedCount, dateTimeFormat.groupingSeparator), font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "spacing", component: AnyComponent(
|
||||
Rectangle(color: .clear, width: 8.0, height: 1.0)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: giftIconSubject,
|
||||
mode: .buttonIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: " for sale on Telegram", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
||||
BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor)
|
||||
))
|
||||
], spacing: 0.0)
|
||||
),
|
||||
action: { [weak state] in
|
||||
guard let state, let genericGift else {
|
||||
return
|
||||
}
|
||||
state.openGiftResale(gift: genericGift)
|
||||
},
|
||||
animateScale: false
|
||||
),
|
||||
environment: {},
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(telegramSaleButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + telegramSaleButton.size.height / 2.0))
|
||||
)
|
||||
originY += telegramSaleButton.size.height
|
||||
originY += 12.0
|
||||
}
|
||||
|
||||
if let listedCount = component.valueInfo.fragmentListedCount, let giftIconSubject {
|
||||
if component.valueInfo.listedCount != nil {
|
||||
originY += 18.0
|
||||
}
|
||||
|
||||
let fragmentSaleButton = fragmentSaleButton.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: "count", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: presentationStringsFormattedNumber(listedCount, dateTimeFormat.groupingSeparator), font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "spacing", component: AnyComponent(
|
||||
Rectangle(color: .clear, width: 8.0, height: 1.0)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: giftIconSubject,
|
||||
mode: .buttonIcon
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: " for sale on Fragment", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
||||
BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor)
|
||||
))
|
||||
], spacing: 0.0)
|
||||
),
|
||||
action: {
|
||||
|
||||
},
|
||||
animateScale: false
|
||||
),
|
||||
environment: {},
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(fragmentSaleButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + fragmentSaleButton.size.height / 2.0))
|
||||
)
|
||||
originY += fragmentSaleButton.size.height
|
||||
originY += 12.0
|
||||
}
|
||||
|
||||
context.add(buttons
|
||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0))
|
||||
)
|
||||
|
||||
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
|
||||
return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class GiftValueSheetComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let gift: ProfileGiftsContext.State.StarGift
|
||||
let valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
gift: ProfileGiftsContext.State.StarGift,
|
||||
valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
) {
|
||||
self.context = context
|
||||
self.gift = gift
|
||||
self.valueInfo = valueInfo
|
||||
}
|
||||
|
||||
static func ==(lhs: GiftValueSheetComponent, rhs: GiftValueSheetComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.gift != rhs.gift {
|
||||
return false
|
||||
}
|
||||
if lhs.valueInfo != rhs.valueInfo {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let sheet = Child(SheetComponent<EnvironmentType>.self)
|
||||
let animateOut = StoredActionSlot(Action<Void>.self)
|
||||
|
||||
let sheetExternalState = SheetComponent<EnvironmentType>.ExternalState()
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[EnvironmentType.self]
|
||||
let controller = environment.controller
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(GiftValueSheetContent(
|
||||
context: context.component.context,
|
||||
gift: context.component.gift,
|
||||
valueInfo: context.component.valueInfo,
|
||||
animateOut: animateOut,
|
||||
getController: controller
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
clipsContent: true,
|
||||
externalState: sheetExternalState,
|
||||
animateOut: animateOut,
|
||||
onPan: {
|
||||
if let controller = controller() as? GiftValueScreen {
|
||||
controller.dismissAllTooltips()
|
||||
}
|
||||
},
|
||||
willDismiss: {
|
||||
}
|
||||
),
|
||||
environment: {
|
||||
environment
|
||||
SheetComponentEnvironment(
|
||||
isDisplaying: environment.value.isVisible,
|
||||
isCentered: environment.metrics.widthClass == .regular,
|
||||
hasInputHeight: !environment.inputHeight.isZero,
|
||||
regularMetricsSize: CGSize(width: 430.0, height: 900.0),
|
||||
dismiss: { animated in
|
||||
if animated {
|
||||
if let controller = controller() as? GiftValueScreen {
|
||||
controller.dismissAllTooltips()
|
||||
animateOut.invoke(Action { _ in
|
||||
controller.dismiss(completion: nil)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if let controller = controller() as? GiftValueScreen {
|
||||
controller.dismissAllTooltips()
|
||||
controller.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
context.add(sheet
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
)
|
||||
|
||||
if let controller = controller(), !controller.automaticallyControlPresentationContextLayout {
|
||||
var sideInset: CGFloat = 0.0
|
||||
var bottomInset: CGFloat = max(environment.safeInsets.bottom, sheetExternalState.contentHeight)
|
||||
if case .regular = environment.metrics.widthClass {
|
||||
sideInset = floor((context.availableSize.width - 430.0) / 2.0) - 12.0
|
||||
bottomInset = (context.availableSize.height - sheetExternalState.contentHeight) / 2.0 + sheetExternalState.contentHeight
|
||||
}
|
||||
|
||||
let layout = ContainerViewLayout(
|
||||
size: context.availableSize,
|
||||
metrics: environment.metrics,
|
||||
deviceMetrics: environment.deviceMetrics,
|
||||
intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: bottomInset, right: 0.0),
|
||||
safeInsets: UIEdgeInsets(top: 0.0, left: max(sideInset, environment.safeInsets.left), bottom: 0.0, right: max(sideInset, environment.safeInsets.right)),
|
||||
additionalInsets: .zero,
|
||||
statusBarHeight: environment.statusBarHeight,
|
||||
inputHeight: nil,
|
||||
inputHeightIsInteractivellyChanging: false,
|
||||
inVoiceOver: false
|
||||
)
|
||||
controller.presentationContext.containerLayoutUpdated(layout, transition: context.transition.containedViewLayoutTransition)
|
||||
}
|
||||
|
||||
return context.availableSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class GiftValueScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private let gift: ProfileGiftsContext.State.StarGift
|
||||
private let valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
gift: ProfileGiftsContext.State.StarGift,
|
||||
valueInfo: StarGift.UniqueGift.ValueInfo
|
||||
) {
|
||||
self.context = context
|
||||
self.gift = gift
|
||||
self.valueInfo = valueInfo
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: GiftValueSheetComponent(
|
||||
context: context,
|
||||
gift: gift,
|
||||
valueInfo: valueInfo
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
statusBarStyle: .ignore,
|
||||
theme: .default
|
||||
)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
public override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
}
|
||||
|
||||
public override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
self.dismissAllTooltips()
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
self.dismissAllTooltips()
|
||||
|
||||
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
|
||||
view.dismissAnimated()
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func dismissAllTooltips() {
|
||||
self.window?.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismiss()
|
||||
}
|
||||
})
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss(inPlace: false)
|
||||
}
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismiss()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ import TelegramNotices
|
|||
import PremiumLockButtonSubtitleComponent
|
||||
import StarsBalanceOverlayComponent
|
||||
import BalanceNeededScreen
|
||||
import GiftItemComponent
|
||||
|
||||
private final class GiftViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -101,6 +102,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
|
||||
var inProgress = false
|
||||
|
||||
var nextGiftToUpgrade: ProfileGiftsContext.State.StarGift?
|
||||
var inUpgradePreview = false
|
||||
var upgradeForm: BotPaymentForm?
|
||||
var upgradeFormDisposable: Disposable?
|
||||
|
|
@ -210,7 +212,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
if let releasedBy = gift.releasedBy {
|
||||
peerIds.append(releasedBy)
|
||||
}
|
||||
if arguments.canUpgrade || arguments.upgradeStars != nil {
|
||||
if arguments.canUpgrade || arguments.upgradeStars != nil || arguments.prepaidUpgradeHash != nil {
|
||||
self.sampleDisposable.add((context.engine.payments.starGiftUpgradePreview(giftId: gift.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] attributes in
|
||||
guard let self else {
|
||||
|
|
@ -630,6 +632,20 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
controller.push(introController)
|
||||
}
|
||||
|
||||
func openValue() {
|
||||
guard let controller = self.getController(), case let .profileGift(_, gift) = self.subject, case let .unique(uniqueGift) = gift.gift else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.payments.getUniqueStarGiftValueInfo(slug: uniqueGift.slug)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] valueInfo in
|
||||
guard let self, let valueInfo else {
|
||||
return
|
||||
}
|
||||
let valueController = GiftValueScreen(context: self.context, gift: gift, valueInfo: valueInfo)
|
||||
controller.push(valueController)
|
||||
})
|
||||
}
|
||||
|
||||
func sendGift(peerId: EnginePeer.Id) {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
|
|
@ -820,7 +836,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
return
|
||||
}
|
||||
|
||||
let giftTitle = "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
||||
let giftTitle = "\(gift.title) #\(formatCollectibleNumber(gift.number, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||
let reference = arguments.reference ?? .slug(slug: gift.slug)
|
||||
|
||||
if let resellStars = gift.resellAmounts?.first, resellStars.amount.value > 0, !update {
|
||||
|
|
@ -1113,6 +1129,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
if animated {
|
||||
controller.dismissAllTooltips()
|
||||
controller.dismissBalanceOverlay()
|
||||
controller.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.3).withUserData(ViewControllerComponentContainer.AnimateOutTransition()))
|
||||
self.animateOut.invoke(Action { [weak controller] _ in
|
||||
controller?.dismiss(completion: nil)
|
||||
})
|
||||
|
|
@ -1154,9 +1171,6 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
func requestUpgradePreview() {
|
||||
guard let arguments = self.subject.arguments, arguments.canUpgrade || arguments.upgradeStars != nil else {
|
||||
return
|
||||
}
|
||||
self.context.starsContext?.load(force: false)
|
||||
|
||||
self.inUpgradePreview = true
|
||||
|
|
@ -1202,7 +1216,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
return
|
||||
}
|
||||
|
||||
let giftTitle = "\(uniqueGift.title) #\(uniqueGift.number)"
|
||||
let giftTitle = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||
let recipientPeerId = self.recipientPeerId ?? self.context.account.peerId
|
||||
|
||||
let action: (CurrencyAmount.Currency) -> Void = { currency in
|
||||
|
|
@ -1485,9 +1499,9 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
|
||||
return
|
||||
}
|
||||
|
||||
let proceed: (Int64?) -> Void = { formId in
|
||||
guard let controller = self.getController() as? GiftViewScreen else {
|
||||
|
||||
let proceed: (Int64?) -> Void = { [weak self] formId in
|
||||
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
||||
return
|
||||
}
|
||||
self.inProgress = true
|
||||
|
|
@ -1528,6 +1542,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
self.inProgress = false
|
||||
self.inUpgradePreview = false
|
||||
|
||||
if let reference = arguments.reference {
|
||||
controller.upgradedGiftReferences.insert(reference)
|
||||
self.nextGiftToUpgrade = controller.nextUpgradableGift
|
||||
}
|
||||
|
||||
self.subject = .profileGift(peerId, result)
|
||||
controller.animateSuccess()
|
||||
self.updated(transition: .spring(duration: 0.4))
|
||||
|
|
@ -1579,6 +1598,100 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
controller.showBalance = true
|
||||
}
|
||||
}
|
||||
|
||||
func commitPrepaidUpgrade() {
|
||||
guard let arguments = self.subject.arguments, let peerId = arguments.peerId, let prepaidUpgradeHash = arguments.prepaidUpgradeHash, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
|
||||
return
|
||||
}
|
||||
guard case let .generic(gift) = arguments.gift else {
|
||||
return
|
||||
}
|
||||
guard let gift = self.starGiftsMap[gift.id], let price = gift.upgradeStars else {
|
||||
return
|
||||
}
|
||||
let context = self.context
|
||||
let proceed: () -> Void = { [weak self, weak starsContext] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.inProgress = true
|
||||
self.updated()
|
||||
|
||||
let source: BotPaymentInvoiceSource = .starGiftPrepaidUpgrade(peerId: peerId, hash: prepaidUpgradeHash)
|
||||
let signal = context.engine.payments.fetchBotPaymentForm(source: source, themeParams: nil)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { paymentForm in
|
||||
if let paymentForm {
|
||||
return context.engine.payments.sendStarsPaymentForm(formId: paymentForm.id, source: source)
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|
||||
self.upgradeDisposable = (signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak starsContext] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.dismiss(animated: true)
|
||||
Queue.mainQueue().after(0.5) {
|
||||
starsContext?.load(force: true)
|
||||
}
|
||||
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
self.openPeer(peer, gifts: false, dismiss: true)
|
||||
})
|
||||
}, error: { _ in
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
if starsState.balance < StarsAmount(value: price, nanos: 0) {
|
||||
let _ = (self.optionsPromise.get()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] options in
|
||||
guard let self, let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
let purchaseController = self.context.sharedContext.makeStarsPurchaseScreen(
|
||||
context: self.context,
|
||||
starsContext: starsContext,
|
||||
options: options ?? [],
|
||||
purpose: .upgradeStarGift(requiredStars: price),
|
||||
completion: { [weak self, weak starsContext] stars in
|
||||
guard let self, let starsContext else {
|
||||
return
|
||||
}
|
||||
self.inProgress = true
|
||||
self.updated()
|
||||
|
||||
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||
let _ = (starsContext.onUpdate
|
||||
|> deliverOnMainQueue).start(next: {
|
||||
proceed()
|
||||
})
|
||||
}
|
||||
)
|
||||
controller.push(purchaseController)
|
||||
})
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
}
|
||||
|
||||
func switchToNextUpgradable() {
|
||||
if let controller = self.getController() as? GiftViewScreen {
|
||||
controller.switchToNextUpgradable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
|
|
@ -1611,6 +1724,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let table = Child(TableComponent.self)
|
||||
let additionalText = Child(MultilineTextComponent.self)
|
||||
let button = Child(ButtonComponent.self)
|
||||
let upgradeNextButton = Child(PlainButtonComponent.self)
|
||||
|
||||
let upgradeTitle = Child(MultilineTextComponent.self)
|
||||
let upgradeDescription = Child(BalancedTextComponent.self)
|
||||
|
|
@ -1661,6 +1775,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
var isChannelGift = false
|
||||
var isMyUniqueGift = false
|
||||
var releasedByPeer: EnginePeer?
|
||||
var canGiftUpgrade = false
|
||||
|
||||
if case let .soldOutGift(gift) = subject {
|
||||
animationFile = gift.file
|
||||
|
|
@ -1703,13 +1818,14 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
uniqueGift = gift
|
||||
}
|
||||
savedToProfile = arguments.savedToProfile
|
||||
if let reference = arguments.reference, case .peer = reference {
|
||||
if let reference = arguments.reference, case let .peer(peerId, _) = reference, peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
isChannelGift = true
|
||||
incoming = true
|
||||
} else {
|
||||
incoming = arguments.incoming || arguments.peerId == component.context.account.peerId
|
||||
}
|
||||
nameHidden = arguments.nameHidden
|
||||
canGiftUpgrade = arguments.prepaidUpgradeHash != nil
|
||||
|
||||
isSelfGift = arguments.messageId?.peerId == component.context.account.peerId
|
||||
|
||||
|
|
@ -1735,7 +1851,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
if !canUpgrade, let gift = state.starGiftsMap[giftId], let _ = gift.upgradeStars {
|
||||
canUpgrade = true
|
||||
}
|
||||
|
||||
|
||||
var showUpgradePreview = false
|
||||
if state.inUpgradePreview, let _ = state.sampleGiftAttributes {
|
||||
showUpgradePreview = true
|
||||
|
|
@ -1837,8 +1953,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
if case .wearPreview = component.subject {
|
||||
giftTitle = uniqueGift.title
|
||||
} else {
|
||||
|
||||
giftTitle = "\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))"
|
||||
giftTitle = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))"
|
||||
}
|
||||
|
||||
let wearTitle = wearTitle.update(
|
||||
|
|
@ -2042,7 +2157,14 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let uniqueText: String
|
||||
let transferableText: String
|
||||
let tradableText: String
|
||||
if case let .upgradePreview(_, name) = component.subject {
|
||||
if !incoming, case let .profileGift(peerId, _) = subject, let peer = state.peerMap[peerId] {
|
||||
let name = peer.compactDisplayTitle
|
||||
title = environment.strings.Gift_Upgrade_GiftTitle
|
||||
description = environment.strings.Gift_Upgrade_GiftDescription(name).string
|
||||
uniqueText = strings.Gift_Upgrade_Unique_GiftDescription(name).string
|
||||
transferableText = strings.Gift_Upgrade_Transferable_GiftDescription(name).string
|
||||
tradableText = strings.Gift_Upgrade_Tradable_GiftDescription(name).string
|
||||
} else if case let .upgradePreview(_, name) = component.subject {
|
||||
title = environment.strings.Gift_Upgrade_IncludeTitle
|
||||
description = environment.strings.Gift_Upgrade_IncludeDescription(name).string
|
||||
uniqueText = strings.Gift_Upgrade_Unique_IncludeDescription
|
||||
|
|
@ -2166,6 +2288,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
|
||||
if case .upgradePreview = component.subject {
|
||||
|
||||
} else if !incoming {
|
||||
} else {
|
||||
let checkTheme = CheckComponent.Theme(
|
||||
backgroundColor: theme.list.itemCheckColors.fillColor,
|
||||
|
|
@ -2222,10 +2345,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
var hasDescriptionButton = false
|
||||
if let uniqueGift {
|
||||
titleString = uniqueGift.title
|
||||
descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))"
|
||||
descriptionText = "\(strings.Gift_Unique_Collectible) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))"
|
||||
|
||||
if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
|
||||
descriptionText = strings.Gift_Unique_CollectibleBy("#\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))", "[@\(addressName)]()").string
|
||||
descriptionText = strings.Gift_Unique_CollectibleBy("#\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))", "[@\(addressName)]()").string
|
||||
hasDescriptionButton = true
|
||||
releasedByPeer = peer
|
||||
}
|
||||
|
|
@ -2775,7 +2898,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
if isWearing {
|
||||
state.commitTakeOff()
|
||||
|
||||
state.showAttributeInfo(tag: state.statusTag, text: strings.Gift_View_TookOff("\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))").string)
|
||||
state.showAttributeInfo(tag: state.statusTag, text: strings.Gift_View_TookOff("\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))").string)
|
||||
} else {
|
||||
if let controller = controller() as? GiftViewScreen {
|
||||
controller.dismissAllTooltips()
|
||||
|
|
@ -2802,7 +2925,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
state.requestWearPreview()
|
||||
} else {
|
||||
state.commitWear(uniqueGift)
|
||||
state.showAttributeInfo(tag: state.statusTag, text: strings.Gift_View_PutOn("\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))").string)
|
||||
state.showAttributeInfo(tag: state.statusTag, text: strings.Gift_View_PutOn("\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))").string)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -3006,6 +3129,36 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Unique_Issued("\(issuedString)/\(totalString)").string, font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
), at: hasOriginalInfo ? tableItems.count - 1 : tableItems.count)
|
||||
|
||||
//TODO:localize
|
||||
if let valueAmount = uniqueGift.valueAmount, let valueCurrency = uniqueGift.valueCurrency {
|
||||
tableItems.insert(.init(
|
||||
id: "fiatValue",
|
||||
title: "Value",
|
||||
component: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "≈\(formatCurrencyAmount(valueAmount, currency: valueCurrency))", font: tableFont, textColor: tableTextColor))))
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(ButtonContentComponent(
|
||||
context: component.context,
|
||||
text: "learn more",
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
state?.openValue()
|
||||
}
|
||||
))
|
||||
)
|
||||
], spacing: 4.0)
|
||||
),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
|
||||
), at: hasOriginalInfo ? tableItems.count - 1 : tableItems.count)
|
||||
}
|
||||
} else {
|
||||
if case let .soldOutGift(gift) = subject, let soldOut = gift.soldOut {
|
||||
tableItems.append(.init(
|
||||
|
|
@ -3129,23 +3282,16 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
if !soldOut && canUpgrade {
|
||||
var items: [AnyComponentWithIdentity<Empty>] = []
|
||||
items.append(
|
||||
AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_View_Status_NonUnique, font: tableFont, textColor: tableTextColor))))
|
||||
)
|
||||
)
|
||||
tableItems.append(.init(
|
||||
id: "status",
|
||||
title: strings.Gift_View_Status,
|
||||
component: AnyComponent(
|
||||
HStack(items, spacing: 4.0)
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_View_Status_NonUnique, font: tableFont, textColor: tableTextColor)))
|
||||
),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
if let text {
|
||||
let attributedText = stringWithAppliedEntities(text, entities: entities ?? [], baseColor: tableTextColor, linkColor: tableLinkColor, baseFont: tableFont, linkFont: tableFont, boldFont: tableBoldFont, italicFont: tableItalicFont, boldItalicFont: tableBoldItalicFont, fixedFont: tableMonospaceFont, blockQuoteFont: tableFont, message: nil)
|
||||
|
||||
|
|
@ -3436,7 +3582,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
state.dismiss(animated: true)
|
||||
} else {
|
||||
Queue.mainQueue().after(0.2) {
|
||||
state.showAttributeInfo(tag: state.statusTag, text: strings.Gift_View_PutOn("\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))").string)
|
||||
state.showAttributeInfo(tag: state.statusTag, text: strings.Gift_View_PutOn("\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))").string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3450,7 +3596,12 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme)
|
||||
}
|
||||
var upgradeString = strings.Gift_Upgrade_Upgrade
|
||||
if let upgradeForm = state.upgradeForm, let price = upgradeForm.invoice.prices.first?.amount {
|
||||
if !incoming {
|
||||
//TODO:localize
|
||||
if let gift = state.starGiftsMap[giftId], let upgradeStars = gift.upgradeStars {
|
||||
upgradeString = "Pay # \(upgradeStars) for Upgrade"
|
||||
}
|
||||
} else if let upgradeForm = state.upgradeForm, let price = upgradeForm.invoice.prices.first?.amount {
|
||||
upgradeString += " # \(presentationStringsFormattedNumber(Int32(price), environment.dateTimeFormat.groupingSeparator))"
|
||||
}
|
||||
let buttonTitle = subject.arguments?.upgradeStars != nil ? strings.Gift_Upgrade_Confirm : upgradeString
|
||||
|
|
@ -3471,7 +3622,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
isEnabled: true,
|
||||
displaysProgress: state.inProgress,
|
||||
action: { [weak state] in
|
||||
state?.commitUpgrade()
|
||||
if canGiftUpgrade {
|
||||
state?.commitPrepaidUpgrade()
|
||||
} else {
|
||||
state?.commitUpgrade()
|
||||
}
|
||||
}),
|
||||
availableSize: buttonSize,
|
||||
transition: context.transition
|
||||
|
|
@ -3495,9 +3650,12 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
availableSize: buttonSize,
|
||||
transition: context.transition
|
||||
)
|
||||
} else if incoming && !converted && !upgraded && canUpgrade {
|
||||
} else if (incoming && !converted && !upgraded && canUpgrade) || canGiftUpgrade {
|
||||
let buttonTitle: String
|
||||
if let upgradeStars, upgradeStars > 0 {
|
||||
if canGiftUpgrade {
|
||||
//TODO:localize
|
||||
buttonTitle = "Gift an Upgrade"
|
||||
} else if let upgradeStars, upgradeStars > 0 {
|
||||
buttonTitle = strings.Gift_View_UpgradeForFree
|
||||
} else {
|
||||
buttonTitle = strings.Gift_View_Upgrade
|
||||
|
|
@ -3641,9 +3799,50 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
)
|
||||
}
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: buttonChild.size)
|
||||
|
||||
var buttonAlpha: CGFloat = 1.0
|
||||
if let nextGiftToUpgrade = state.nextGiftToUpgrade, case let .generic(gift) = nextGiftToUpgrade.gift {
|
||||
buttonAlpha = 0.0
|
||||
|
||||
let upgradeNextButton = upgradeNextButton.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Upgrade Next Gift", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
peer: nil,
|
||||
subject: .starGift(gift: gift, price: ""),
|
||||
mode: .buttonIcon
|
||||
)
|
||||
)),
|
||||
], spacing: 5.0)
|
||||
),
|
||||
action: { [weak state] in
|
||||
state?.switchToNextUpgradable()
|
||||
},
|
||||
animateScale: false
|
||||
),
|
||||
environment: {},
|
||||
availableSize: buttonChild.size,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(upgradeNextButton
|
||||
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
|
||||
.appear(.default(scale: true, alpha: true))
|
||||
.disappear(.default(scale: true, alpha: true))
|
||||
)
|
||||
}
|
||||
|
||||
context.add(buttonChild
|
||||
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
|
||||
.cornerRadius(10.0)
|
||||
.opacity(buttonAlpha)
|
||||
)
|
||||
originY += buttonChild.size.height
|
||||
originY += 7.0
|
||||
|
|
@ -3703,6 +3902,7 @@ final class GiftViewSheetComponent: CombinedComponent {
|
|||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
clipsContent: true,
|
||||
hasDimView: false,
|
||||
externalState: sheetExternalState,
|
||||
animateOut: animateOut,
|
||||
onPan: {
|
||||
|
|
@ -3713,6 +3913,7 @@ final class GiftViewSheetComponent: CombinedComponent {
|
|||
willDismiss: {
|
||||
if let controller = controller() as? GiftViewScreen {
|
||||
controller.dismissBalanceOverlay()
|
||||
controller.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.3).withUserData(ViewControllerComponentContainer.AnimateOutTransition()))
|
||||
}
|
||||
}
|
||||
),
|
||||
|
|
@ -3726,6 +3927,7 @@ final class GiftViewSheetComponent: CombinedComponent {
|
|||
dismiss: { animated in
|
||||
if animated {
|
||||
if let controller = controller() as? GiftViewScreen {
|
||||
controller.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.3).withUserData(ViewControllerComponentContainer.AnimateOutTransition()))
|
||||
controller.dismissAllTooltips()
|
||||
controller.dismissBalanceOverlay()
|
||||
animateOut.invoke(Action { _ in
|
||||
|
|
@ -3787,19 +3989,19 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
case upgradePreview([StarGift.UniqueGift.Attribute], String)
|
||||
case wearPreview(StarGift.UniqueGift)
|
||||
|
||||
var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, refunded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, resellAmounts: [CurrencyAmount]?, canExportDate: Int32?, upgradeMessageId: Int32?, canTransferDate: Int32?, canResaleDate: Int32?)? {
|
||||
var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, refunded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, resellAmounts: [CurrencyAmount]?, canExportDate: Int32?, upgradeMessageId: Int32?, canTransferDate: Int32?, canResaleDate: Int32?, prepaidUpgradeHash: String?)? {
|
||||
switch self {
|
||||
case let .message(message):
|
||||
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, upgradeMessageId, peerId, senderId, savedId):
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash):
|
||||
var reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
} else {
|
||||
reference = .message(messageId: message.id)
|
||||
}
|
||||
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil)
|
||||
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash)
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate):
|
||||
var reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
|
|
@ -3824,13 +4026,13 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
if case let .unique(uniqueGift) = gift {
|
||||
resellAmounts = uniqueGift.resellAmounts
|
||||
}
|
||||
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, false, nil, transferStars, resellAmounts, canExportDate, nil, canTransferDate, canResaleDate)
|
||||
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, false, nil, transferStars, resellAmounts, canExportDate, nil, canTransferDate, canResaleDate, nil)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case let .uniqueGift(gift, _), let .wearPreview(gift):
|
||||
return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, false, nil, nil, gift.resellAmounts, nil, nil, nil, nil)
|
||||
return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, false, nil, nil, gift.resellAmounts, nil, nil, nil, nil, nil)
|
||||
case let .profileGift(peerId, gift):
|
||||
var messageId: EngineMessage.Id?
|
||||
if case let .message(messageIdValue) = gift.reference {
|
||||
|
|
@ -3840,7 +4042,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
if case let .unique(uniqueGift) = gift.gift {
|
||||
resellAmounts = uniqueGift.resellAmounts
|
||||
}
|
||||
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate)
|
||||
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash)
|
||||
case .soldOutGift:
|
||||
return nil
|
||||
case .upgradePreview:
|
||||
|
|
@ -3853,6 +4055,24 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
private let context: AccountContext
|
||||
private let subject: GiftViewScreen.Subject
|
||||
|
||||
private var upgradableGiftsContext: ProfileGiftsContext?
|
||||
fileprivate private(set) var upgradableGifts: [ProfileGiftsContext.State.StarGift]?
|
||||
fileprivate var upgradedGiftReferences = Set<StarGiftReference>()
|
||||
private var upgradableDisposable: Disposable?
|
||||
fileprivate var nextUpgradableGift: ProfileGiftsContext.State.StarGift? {
|
||||
if let upgradableGifts = self.upgradableGifts {
|
||||
return upgradableGifts.first(where: { gift in
|
||||
if let reference = gift.reference {
|
||||
if !self.upgradedGiftReferences.contains(reference) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
fileprivate var showBalance = false {
|
||||
didSet {
|
||||
self.requestLayout(transition: .immediate)
|
||||
|
|
@ -3910,7 +4130,13 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
if let allSubjects, !allSubjects.isEmpty {
|
||||
items.removeAll()
|
||||
for i in 0 ..< allSubjects.count {
|
||||
items.append(GiftPagerComponent.Item(id: i, subject: allSubjects[i]))
|
||||
var id: AnyHashable
|
||||
if case let .profileGift(_, starGift) = allSubjects[i], let reference = starGift.reference {
|
||||
id = reference.stringValue
|
||||
} else {
|
||||
id = i
|
||||
}
|
||||
items.append(GiftPagerComponent.Item(id: id, subject: allSubjects[i]))
|
||||
}
|
||||
}
|
||||
var dismissTooltipsImpl: (() -> Void)?
|
||||
|
|
@ -3920,6 +4146,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
context: context,
|
||||
items: items,
|
||||
index: index ?? 0,
|
||||
itemSpacing: 10.0,
|
||||
updated: { _, _ in
|
||||
dismissTooltipsImpl?()
|
||||
}
|
||||
|
|
@ -3934,6 +4161,18 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
|
||||
self.navigationPresentation = .flatModal
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
|
||||
if "".isEmpty {
|
||||
let upgradableGiftsContext = ProfileGiftsContext(account: context.account, peerId: context.account.peerId, collectionId: nil, sorting: .date, filter: [.displayed, .hidden, .limitedUpgradable])
|
||||
self.upgradableDisposable = (upgradableGiftsContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.upgradableGifts = state.filteredGifts
|
||||
})
|
||||
self.upgradableGiftsContext = upgradableGiftsContext
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
|
@ -3942,6 +4181,50 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
|
||||
deinit {
|
||||
self.disposed()
|
||||
self.upgradableDisposable?.dispose()
|
||||
}
|
||||
|
||||
fileprivate func switchToNextUpgradable() {
|
||||
guard let upgradableGifts = self.upgradableGifts, case let .profileGift(peerId, _) = self.subject else {
|
||||
return
|
||||
}
|
||||
var effectiveUpgradableGifts: [ProfileGiftsContext.State.StarGift] = []
|
||||
for gift in upgradableGifts {
|
||||
if let reference = gift.reference {
|
||||
if !self.upgradedGiftReferences.contains(reference) {
|
||||
effectiveUpgradableGifts.append(gift)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard !effectiveUpgradableGifts.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
var items: [GiftPagerComponent.Item] = []
|
||||
for i in 0 ..< effectiveUpgradableGifts.count {
|
||||
let gift = effectiveUpgradableGifts[i]
|
||||
var id: AnyHashable
|
||||
if let reference = gift.reference {
|
||||
id = reference.stringValue
|
||||
} else {
|
||||
id = i
|
||||
}
|
||||
items.append(GiftPagerComponent.Item(id: id, subject: .profileGift(peerId, gift)))
|
||||
}
|
||||
|
||||
self.updateComponent(
|
||||
component: AnyComponent(GiftPagerComponent(
|
||||
context: self.context,
|
||||
items: items,
|
||||
index: 0,
|
||||
itemSpacing: 10.0,
|
||||
updated: { [weak self] _, _ in
|
||||
self?.dismissAllTooltips()
|
||||
}
|
||||
)),
|
||||
transition: .spring(duration: 0.3)
|
||||
)
|
||||
}
|
||||
|
||||
public override func viewDidLoad() {
|
||||
|
|
@ -4081,12 +4364,10 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
}
|
||||
}
|
||||
|
||||
private func formatPercentage(_ value: Float) -> String {
|
||||
func formatPercentage(_ value: Float) -> String {
|
||||
return String(format: "%0.1f%%", value).replacingOccurrences(of: ".0%", with: "%").replacingOccurrences(of: ",0%", with: "%")
|
||||
}
|
||||
|
||||
|
||||
|
||||
private final class PeerCellComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
|
|
@ -4193,12 +4474,12 @@ private final class PeerCellComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
private final class ButtonContentComponent: Component {
|
||||
final class ButtonContentComponent: Component {
|
||||
let context: AccountContext
|
||||
let text: String
|
||||
let color: UIColor
|
||||
|
||||
public init(
|
||||
init(
|
||||
context: AccountContext,
|
||||
text: String,
|
||||
color: UIColor
|
||||
|
|
@ -4208,7 +4489,7 @@ private final class ButtonContentComponent: Component {
|
|||
self.color = color
|
||||
}
|
||||
|
||||
public static func ==(lhs: ButtonContentComponent, rhs: ButtonContentComponent) -> Bool {
|
||||
static func ==(lhs: ButtonContentComponent, rhs: ButtonContentComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
|
|
@ -4221,7 +4502,7 @@ private final class ButtonContentComponent: Component {
|
|||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
final class View: UIView {
|
||||
private var component: ButtonContentComponent?
|
||||
private weak var componentState: EmptyComponentState?
|
||||
|
||||
|
|
@ -4279,11 +4560,11 @@ private final class ButtonContentComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -4693,3 +4974,16 @@ private struct GiftViewConfiguration {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension StarGiftReference {
|
||||
var stringValue: String {
|
||||
switch self {
|
||||
case let .message(messageId):
|
||||
return "m_\(messageId.id)"
|
||||
case let .peer(peerId, id):
|
||||
return "p_\(peerId.toInt64())_\(id)"
|
||||
case let .slug(slug):
|
||||
return "s_\(slug)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import SwiftSignalKit
|
|||
import ViewControllerComponent
|
||||
import ComponentDisplayAdapters
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
|
|
@ -120,7 +121,7 @@ private final class GiftContextPreviewComponent: Component {
|
|||
let subtitleSize = self.subtitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(
|
||||
NSAttributedString(string: "\(environment.strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))", font: Font.regular(13.0), textColor: vibrantColor)
|
||||
NSAttributedString(string: "\(environment.strings.Gift_Unique_Collectible) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))", font: Font.regular(13.0), textColor: vibrantColor)
|
||||
))),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
|
|
|
|||
|
|
@ -656,7 +656,7 @@ final class GiftsListView: UIView {
|
|||
|
||||
var title = ""
|
||||
if case let .unique(uniqueGift) = product.gift {
|
||||
title = "\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, params.presentationData.dateTimeFormat.groupingSeparator))"
|
||||
title = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: params.presentationData.dateTimeFormat))"
|
||||
}
|
||||
|
||||
if pinnedToTop {
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
var replacingTitle = ""
|
||||
for gift in pinnedGifts {
|
||||
if gift.reference == unpinnedReference, case let .unique(uniqueGift) = gift.gift {
|
||||
replacingTitle = "\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
||||
replacingTitle = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -395,7 +395,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
|
||||
var title = ""
|
||||
if case let .unique(uniqueGift) = gift.gift {
|
||||
title = "\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
||||
title = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||
}
|
||||
|
||||
let _ = self.scrollToTop()
|
||||
|
|
@ -1058,7 +1058,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
case let .generic(gift):
|
||||
giftFile = gift.file
|
||||
case let .unique(uniqueGift):
|
||||
giftTitle = uniqueGift.title + " #\(presentationStringsFormattedNumber(uniqueGift.number, currentParams.presentationData.dateTimeFormat.groupingSeparator))"
|
||||
giftTitle = uniqueGift.title + " #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: currentParams.presentationData.dateTimeFormat))"
|
||||
for attribute in uniqueGift.attributes {
|
||||
if case let .model(_, file, _) = attribute {
|
||||
giftFile = file
|
||||
|
|
@ -1126,7 +1126,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
} else {
|
||||
var title = ""
|
||||
if case let .unique(uniqueGift) = gift.gift {
|
||||
title = "\(uniqueGift.title) #\(presentationStringsFormattedNumber(uniqueGift.number, presentationData.dateTimeFormat.groupingSeparator))"
|
||||
title = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: presentationData.dateTimeFormat))"
|
||||
}
|
||||
toastTitle = strings.PeerInfo_Gifts_ToastPinned_TitleNew(title).string
|
||||
toastText = strings.PeerInfo_Gifts_ToastPinned_Text
|
||||
|
|
@ -1378,7 +1378,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
case let .generic(gift):
|
||||
giftFile = gift.file
|
||||
case let .unique(uniqueGift):
|
||||
giftTitle = uniqueGift.title + " #\(presentationStringsFormattedNumber(uniqueGift.number, currentParams.presentationData.dateTimeFormat.groupingSeparator))"
|
||||
giftTitle = uniqueGift.title + " #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: currentParams.presentationData.dateTimeFormat))"
|
||||
for attribute in uniqueGift.attributes {
|
||||
if case let .model(_, file, _) = attribute {
|
||||
giftFile = file
|
||||
|
|
|
|||
|
|
@ -449,6 +449,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||
if let file = self.cachedIconFiles[fileId], let patternFile = self.cachedIconFiles[patternFileId], let numberString = slugComponents.last, let number = Int32(numberString) {
|
||||
let gift = StarGift.UniqueGift(
|
||||
id: id,
|
||||
giftId: 0,
|
||||
title: title,
|
||||
number: number,
|
||||
slug: slug,
|
||||
|
|
@ -462,7 +463,9 @@ final class UserAppearanceScreenComponent: Component {
|
|||
giftAddress: nil,
|
||||
resellAmounts: nil,
|
||||
resellForTonOnly: false,
|
||||
releasedBy: nil
|
||||
releasedBy: nil,
|
||||
valueAmount: nil,
|
||||
valueCurrency: nil
|
||||
)
|
||||
signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -499,6 +499,13 @@ public final class TabSelectorComponent: Component {
|
|||
self.setContentOffset(CGPoint(x: self.contentSize.width - self.bounds.width, y: 0.0), animated: true)
|
||||
}
|
||||
|
||||
public func frameForItem(_ id: AnyHashable) -> CGRect? {
|
||||
if let item = self.visibleItems[id] {
|
||||
return item.convert(item.bounds, to: self)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func update(component: TabSelectorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let selectionColorUpdated = component.colors.selection != self.component?.colors.selection
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue