Gift improvements

This commit is contained in:
Ilya Laktyushin 2024-10-01 17:32:43 +04:00
parent bbf127b806
commit a2ff1313ca
23 changed files with 584 additions and 271 deletions

View File

@ -12950,7 +12950,7 @@ Sorry for the inconvenience.";
"SharedMedia.GiftCount_any" = "%@ gifts"; "SharedMedia.GiftCount_any" = "%@ gifts";
"Stars.Info.Title" = "What are Stars?"; "Stars.Info.Title" = "What are Stars?";
"Stars.Info.Description" = "Buy packages of Stars on Telegram that let you do following:"; "Stars.Info.Description" = "Telegram Stars allow users to:";
"Stars.Info.Gift.Title" = "Send Gifts to Friends"; "Stars.Info.Gift.Title" = "Send Gifts to Friends";
"Stars.Info.Gift.Text" = "Give your friends gifts that can be kept on their profiles or converted to Stars."; "Stars.Info.Gift.Text" = "Give your friends gifts that can be kept on their profiles or converted to Stars.";
"Stars.Info.Miniapp.Title" = "Use Stars in Miniapps"; "Stars.Info.Miniapp.Title" = "Use Stars in Miniapps";
@ -12976,6 +12976,7 @@ Sorry for the inconvenience.";
"Gift.View.HiddenName" = "Hidden Name"; "Gift.View.HiddenName" = "Hidden Name";
"Gift.View.Date" = "Date"; "Gift.View.Date" = "Date";
"Gift.View.Availability" = "Availability"; "Gift.View.Availability" = "Availability";
"Gift.View.Availability.Of" = "%1$@ of %2$@";
"Gift.View.Hide" = "Hide from My Page"; "Gift.View.Hide" = "Hide from My Page";
"Gift.View.Display" = "Display on My Page"; "Gift.View.Display" = "Display on My Page";
"Gift.View.Convert" = "Convert to %@"; "Gift.View.Convert" = "Convert to %@";
@ -12986,7 +12987,7 @@ Sorry for the inconvenience.";
"Gift.Hidden.Title" = "Gift Removed from Profile"; "Gift.Hidden.Title" = "Gift Removed from Profile";
"Gift.Hidden.Text" = "The gift is no longer displayed in [your profile]()."; "Gift.Hidden.Text" = "The gift is no longer displayed in [your profile]().";
"Gift.Convert.Title" = "Convert Gift to Stars"; "Gift.Convert.Title" = "Convert Gift to Stars";
"Gift.Convert.Text" = "Do you want to convert this gift from **%1$@** to **%2$@**?\n\nThis action cannot be undone."; "Gift.Convert.Text" = "Do you want to convert this gift from **%1$@** to **%2$@**?\n\nThis will permanently destroy the gift.";
"Gift.Convert.Stars_1" = "%@ Star"; "Gift.Convert.Stars_1" = "%@ Star";
"Gift.Convert.Stars_any" = "%@ Stars"; "Gift.Convert.Stars_any" = "%@ Stars";
"Gift.Convert.Convert" = "Convert"; "Gift.Convert.Convert" = "Convert";
@ -13032,7 +13033,8 @@ Sorry for the inconvenience.";
"Gift.Send.Title" = "Send a Gift"; "Gift.Send.Title" = "Send a Gift";
"Gift.Send.Customize.Title" = "CUSTOMIZE YOUR GIFT"; "Gift.Send.Customize.Title" = "CUSTOMIZE YOUR GIFT";
"Gift.Send.Customize.MessagePlaceholder" = "Enter Message"; "Gift.Send.Customize.MessagePlaceholder" = "Enter Message (Optional)";
"Gift.Send.Customize.Info" = "Only %@ will see your message.";
"Gift.Send.HideMyName" = "Hide My Name"; "Gift.Send.HideMyName" = "Hide My Name";
"Gift.Send.HideMyName.Info" = "Hide my name and message from visitors to %1$@'s profile. %2$@ will still see your name and message."; "Gift.Send.HideMyName.Info" = "Hide my name and message from visitors to %1$@'s profile. %2$@ will still see your name and message.";
"Gift.Send.Send" = "Send a Gift for"; "Gift.Send.Send" = "Send a Gift for";
@ -13051,3 +13053,14 @@ Sorry for the inconvenience.";
"Report.Comment.Placeholder.Optional" = "Add Comment (Optional)"; "Report.Comment.Placeholder.Optional" = "Add Comment (Optional)";
"Report.Comment.Info" = "Please help us by telling what is wrong with the message you have selected."; "Report.Comment.Info" = "Please help us by telling what is wrong with the message you have selected.";
"Report.Send" = "Send Report"; "Report.Send" = "Send Report";
"Notification.PremiumGift.MonthsTitle_1" = "%@ Month Premium";
"Notification.PremiumGift.MonthsTitle_any" = "%@ Months Premium";
"Notification.PremiumGift.YearsTitle_1" = "%@ Year Premium";
"Notification.PremiumGift.YearsTitle_any" = "%@ Years Premium";
"Notification.PremiumGift.SubscriptionDescription" = "Subscription for exclusive Telegram features.";
"Notification.StarsGift.Stars_1" = "%@ Star";
"Notification.StarsGift.Stars_any" = "%@ Stars";

View File

@ -143,6 +143,7 @@ public struct PremiumConfiguration {
showPremiumGiftInTextField: false, showPremiumGiftInTextField: false,
giveawayGiftsPurchaseAvailable: false, giveawayGiftsPurchaseAvailable: false,
starsGiftsPurchaseAvailable: false, starsGiftsPurchaseAvailable: false,
starGiftsPurchaseBlocked: true,
boostsPerGiftCount: 3, boostsPerGiftCount: 3,
audioTransciptionTrialMaxDuration: 300, audioTransciptionTrialMaxDuration: 300,
audioTransciptionTrialCount: 2, audioTransciptionTrialCount: 2,
@ -170,6 +171,7 @@ public struct PremiumConfiguration {
public let showPremiumGiftInTextField: Bool public let showPremiumGiftInTextField: Bool
public let giveawayGiftsPurchaseAvailable: Bool public let giveawayGiftsPurchaseAvailable: Bool
public let starsGiftsPurchaseAvailable: Bool public let starsGiftsPurchaseAvailable: Bool
public let starGiftsPurchaseBlocked: Bool
public let boostsPerGiftCount: Int32 public let boostsPerGiftCount: Int32
public let audioTransciptionTrialMaxDuration: Int32 public let audioTransciptionTrialMaxDuration: Int32
public let audioTransciptionTrialCount: Int32 public let audioTransciptionTrialCount: Int32
@ -196,6 +198,7 @@ public struct PremiumConfiguration {
showPremiumGiftInTextField: Bool, showPremiumGiftInTextField: Bool,
giveawayGiftsPurchaseAvailable: Bool, giveawayGiftsPurchaseAvailable: Bool,
starsGiftsPurchaseAvailable: Bool, starsGiftsPurchaseAvailable: Bool,
starGiftsPurchaseBlocked: Bool,
boostsPerGiftCount: Int32, boostsPerGiftCount: Int32,
audioTransciptionTrialMaxDuration: Int32, audioTransciptionTrialMaxDuration: Int32,
audioTransciptionTrialCount: Int32, audioTransciptionTrialCount: Int32,
@ -221,6 +224,7 @@ public struct PremiumConfiguration {
self.showPremiumGiftInTextField = showPremiumGiftInTextField self.showPremiumGiftInTextField = showPremiumGiftInTextField
self.giveawayGiftsPurchaseAvailable = giveawayGiftsPurchaseAvailable self.giveawayGiftsPurchaseAvailable = giveawayGiftsPurchaseAvailable
self.starsGiftsPurchaseAvailable = starsGiftsPurchaseAvailable self.starsGiftsPurchaseAvailable = starsGiftsPurchaseAvailable
self.starGiftsPurchaseBlocked = starGiftsPurchaseBlocked
self.boostsPerGiftCount = boostsPerGiftCount self.boostsPerGiftCount = boostsPerGiftCount
self.audioTransciptionTrialMaxDuration = audioTransciptionTrialMaxDuration self.audioTransciptionTrialMaxDuration = audioTransciptionTrialMaxDuration
self.audioTransciptionTrialCount = audioTransciptionTrialCount self.audioTransciptionTrialCount = audioTransciptionTrialCount
@ -254,6 +258,7 @@ public struct PremiumConfiguration {
showPremiumGiftInTextField: data["premium_gift_text_field_icon"] as? Bool ?? defaultValue.showPremiumGiftInTextField, showPremiumGiftInTextField: data["premium_gift_text_field_icon"] as? Bool ?? defaultValue.showPremiumGiftInTextField,
giveawayGiftsPurchaseAvailable: data["giveaway_gifts_purchase_available"] as? Bool ?? defaultValue.giveawayGiftsPurchaseAvailable, giveawayGiftsPurchaseAvailable: data["giveaway_gifts_purchase_available"] as? Bool ?? defaultValue.giveawayGiftsPurchaseAvailable,
starsGiftsPurchaseAvailable: data["stars_gifts_enabled"] as? Bool ?? defaultValue.starsGiftsPurchaseAvailable, starsGiftsPurchaseAvailable: data["stars_gifts_enabled"] as? Bool ?? defaultValue.starsGiftsPurchaseAvailable,
starGiftsPurchaseBlocked: data["stargifts_blocked"] as? Bool ?? defaultValue.starGiftsPurchaseBlocked,
boostsPerGiftCount: get(data["boosts_per_sent_gift"]) ?? defaultValue.boostsPerGiftCount, boostsPerGiftCount: get(data["boosts_per_sent_gift"]) ?? defaultValue.boostsPerGiftCount,
audioTransciptionTrialMaxDuration: get(data["transcribe_audio_trial_duration_max"]) ?? defaultValue.audioTransciptionTrialMaxDuration, audioTransciptionTrialMaxDuration: get(data["transcribe_audio_trial_duration_max"]) ?? defaultValue.audioTransciptionTrialMaxDuration,
audioTransciptionTrialCount: get(data["transcribe_audio_trial_weekly_number"]) ?? defaultValue.audioTransciptionTrialCount, audioTransciptionTrialCount: get(data["transcribe_audio_trial_weekly_number"]) ?? defaultValue.audioTransciptionTrialCount,

View File

@ -623,6 +623,8 @@ private final class PendingInAppPurchaseState: Codable {
case untilDate case untilDate
case stars case stars
case users case users
case text
case entities
} }
enum PurposeType: Int32 { enum PurposeType: Int32 {
@ -641,7 +643,7 @@ private final class PendingInAppPurchaseState: Codable {
case upgrade case upgrade
case restore case restore
case gift(peerId: EnginePeer.Id) case gift(peerId: EnginePeer.Id)
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?) case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, text: String?, entities: [MessageTextEntity]?)
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32) case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32)
case stars(count: Int64) case stars(count: Int64)
case starsGift(peerId: EnginePeer.Id, count: Int64) case starsGift(peerId: EnginePeer.Id, count: Int64)
@ -665,7 +667,9 @@ private final class PendingInAppPurchaseState: Codable {
case .giftCode: case .giftCode:
self = .giftCode( self = .giftCode(
peerIds: try container.decode([Int64].self, forKey: .peers).map { EnginePeer.Id($0) }, peerIds: try container.decode([Int64].self, forKey: .peers).map { EnginePeer.Id($0) },
boostPeer: try container.decodeIfPresent(Int64.self, forKey: .boostPeer).flatMap({ EnginePeer.Id($0) }) boostPeer: try container.decodeIfPresent(Int64.self, forKey: .boostPeer).flatMap({ EnginePeer.Id($0) }),
text: try container.decodeIfPresent(String.self, forKey: .text),
entities: try container.decodeIfPresent([MessageTextEntity].self, forKey: .entities)
) )
case .giveaway: case .giveaway:
self = .giveaway( self = .giveaway(
@ -718,10 +722,12 @@ private final class PendingInAppPurchaseState: Codable {
case let .gift(peerId): case let .gift(peerId):
try container.encode(PurposeType.gift.rawValue, forKey: .type) try container.encode(PurposeType.gift.rawValue, forKey: .type)
try container.encode(peerId.toInt64(), forKey: .peer) try container.encode(peerId.toInt64(), forKey: .peer)
case let .giftCode(peerIds, boostPeer): case let .giftCode(peerIds, boostPeer, text, entities):
try container.encode(PurposeType.giftCode.rawValue, forKey: .type) try container.encode(PurposeType.giftCode.rawValue, forKey: .type)
try container.encode(peerIds.map { $0.toInt64() }, forKey: .peers) try container.encode(peerIds.map { $0.toInt64() }, forKey: .peers)
try container.encodeIfPresent(boostPeer?.toInt64(), forKey: .boostPeer) try container.encodeIfPresent(boostPeer?.toInt64(), forKey: .boostPeer)
try container.encodeIfPresent(text, forKey: .text)
try container.encodeIfPresent(entities, forKey: .entities)
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate): case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate):
try container.encode(PurposeType.giveaway.rawValue, forKey: .type) try container.encode(PurposeType.giveaway.rawValue, forKey: .type)
try container.encode(boostPeer.toInt64(), forKey: .boostPeer) try container.encode(boostPeer.toInt64(), forKey: .boostPeer)
@ -764,8 +770,8 @@ private final class PendingInAppPurchaseState: Codable {
self = .restore self = .restore
case let .gift(peerId, _, _): case let .gift(peerId, _, _):
self = .gift(peerId: peerId) self = .gift(peerId: peerId)
case let .giftCode(peerIds, boostPeer, _, _): case let .giftCode(peerIds, boostPeer, _, _, text, entities):
self = .giftCode(peerIds: peerIds, boostPeer: boostPeer) self = .giftCode(peerIds: peerIds, boostPeer: boostPeer, text: text, entities: entities)
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, _, _): case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, _, _):
self = .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate) self = .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate)
case let .stars(count, _, _): case let .stars(count, _, _):
@ -788,8 +794,8 @@ private final class PendingInAppPurchaseState: Codable {
return .restore return .restore
case let .gift(peerId): case let .gift(peerId):
return .gift(peerId: peerId, currency: currency, amount: amount) return .gift(peerId: peerId, currency: currency, amount: amount)
case let .giftCode(peerIds, boostPeer): case let .giftCode(peerIds, boostPeer, text, entities):
return .giftCode(peerIds: peerIds, boostPeer: boostPeer, currency: currency, amount: amount) return .giftCode(peerIds: peerIds, boostPeer: boostPeer, currency: currency, amount: amount, text: text, entities: entities)
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate): case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate):
return .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount) return .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
case let .stars(count): case let .stars(count):

View File

@ -1356,7 +1356,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
return return
} }
let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount
purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount) purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount, text: nil, entities: nil)
quantity = Int32(state.peers.count) quantity = Int32(state.peers.count)
storeProduct = selectedProduct.storeProduct storeProduct = selectedProduct.storeProduct
case .starsGiveaway: case .starsGiveaway:

View File

@ -910,7 +910,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
if self.source == .profile || self.source == .attachMenu, let peerId = self.peerIds.first { if self.source == .profile || self.source == .attachMenu, let peerId = self.peerIds.first {
purpose = .gift(peerId: peerId, currency: currency, amount: amount) purpose = .gift(peerId: peerId, currency: currency, amount: amount)
} else { } else {
purpose = .giftCode(peerIds: self.peerIds, boostPeer: nil, currency: currency, amount: amount) purpose = .giftCode(peerIds: self.peerIds, boostPeer: nil, currency: currency, amount: amount, text: nil, entities: nil)
quantity = Int32(self.peerIds.count) quantity = Int32(self.peerIds.count)
} }

View File

@ -467,7 +467,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) } dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) }
dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) } dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) }
dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) } dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) }
dict[-1551868097] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) } dict[-75955309] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) }
dict[369444042] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) } dict[369444042] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) }
dict[-1502273946] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumSubscription($0) } dict[-1502273946] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumSubscription($0) }
dict[494149367] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentStarsGift($0) } dict[494149367] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentStarsGift($0) }
@ -551,8 +551,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1230047312] = { return Api.MessageAction.parse_messageActionEmpty($0) } dict[-1230047312] = { return Api.MessageAction.parse_messageActionEmpty($0) }
dict[-1834538890] = { return Api.MessageAction.parse_messageActionGameScore($0) } dict[-1834538890] = { return Api.MessageAction.parse_messageActionGameScore($0) }
dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) } dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) }
dict[1737240073] = { return Api.MessageAction.parse_messageActionGiftCode($0) } dict[1456486804] = { return Api.MessageAction.parse_messageActionGiftCode($0) }
dict[-935499028] = { return Api.MessageAction.parse_messageActionGiftPremium($0) } dict[1818391802] = { return Api.MessageAction.parse_messageActionGiftPremium($0) }
dict[1171632161] = { return Api.MessageAction.parse_messageActionGiftStars($0) } dict[1171632161] = { return Api.MessageAction.parse_messageActionGiftStars($0) }
dict[-1475391004] = { return Api.MessageAction.parse_messageActionGiveawayLaunch($0) } dict[-1475391004] = { return Api.MessageAction.parse_messageActionGiveawayLaunch($0) }
dict[-2015170219] = { return Api.MessageAction.parse_messageActionGiveawayResults($0) } dict[-2015170219] = { return Api.MessageAction.parse_messageActionGiveawayResults($0) }

View File

@ -711,7 +711,7 @@ public extension Api {
public extension Api { public extension Api {
indirect enum InputStorePaymentPurpose: TypeConstructorDescription { indirect enum InputStorePaymentPurpose: TypeConstructorDescription {
case inputStorePaymentGiftPremium(userId: Api.InputUser, currency: String, amount: Int64) case inputStorePaymentGiftPremium(userId: Api.InputUser, currency: String, amount: Int64)
case inputStorePaymentPremiumGiftCode(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64) case inputStorePaymentPremiumGiftCode(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64, message: Api.TextWithEntities?)
case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64) case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, countriesIso2: [String]?, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
case inputStorePaymentPremiumSubscription(flags: Int32) case inputStorePaymentPremiumSubscription(flags: Int32)
case inputStorePaymentStarsGift(userId: Api.InputUser, stars: Int64, currency: String, amount: Int64) case inputStorePaymentStarsGift(userId: Api.InputUser, stars: Int64, currency: String, amount: Int64)
@ -728,9 +728,9 @@ public extension Api {
serializeString(currency, buffer: buffer, boxed: false) serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false) serializeInt64(amount, buffer: buffer, boxed: false)
break break
case .inputStorePaymentPremiumGiftCode(let flags, let users, let boostPeer, let currency, let amount): case .inputStorePaymentPremiumGiftCode(let flags, let users, let boostPeer, let currency, let amount, let message):
if boxed { if boxed {
buffer.appendInt32(-1551868097) buffer.appendInt32(-75955309)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(481674261) buffer.appendInt32(481674261)
@ -741,6 +741,7 @@ public extension Api {
if Int(flags) & Int(1 << 0) != 0 {boostPeer!.serialize(buffer, true)} if Int(flags) & Int(1 << 0) != 0 {boostPeer!.serialize(buffer, true)}
serializeString(currency, buffer: buffer, boxed: false) serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false) serializeInt64(amount, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)}
break break
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let countriesIso2, let prizeDescription, let randomId, let untilDate, let currency, let amount): case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let countriesIso2, let prizeDescription, let randomId, let untilDate, let currency, let amount):
if boxed { if boxed {
@ -818,8 +819,8 @@ public extension Api {
switch self { switch self {
case .inputStorePaymentGiftPremium(let userId, let currency, let amount): case .inputStorePaymentGiftPremium(let userId, let currency, let amount):
return ("inputStorePaymentGiftPremium", [("userId", userId as Any), ("currency", currency as Any), ("amount", amount as Any)]) return ("inputStorePaymentGiftPremium", [("userId", userId as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentPremiumGiftCode(let flags, let users, let boostPeer, let currency, let amount): case .inputStorePaymentPremiumGiftCode(let flags, let users, let boostPeer, let currency, let amount, let message):
return ("inputStorePaymentPremiumGiftCode", [("flags", flags as Any), ("users", users as Any), ("boostPeer", boostPeer as Any), ("currency", currency as Any), ("amount", amount as Any)]) return ("inputStorePaymentPremiumGiftCode", [("flags", flags as Any), ("users", users as Any), ("boostPeer", boostPeer as Any), ("currency", currency as Any), ("amount", amount as Any), ("message", message as Any)])
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let countriesIso2, let prizeDescription, let randomId, let untilDate, let currency, let amount): case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let countriesIso2, let prizeDescription, let randomId, let untilDate, let currency, let amount):
return ("inputStorePaymentPremiumGiveaway", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("additionalPeers", additionalPeers as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("randomId", randomId as Any), ("untilDate", untilDate as Any), ("currency", currency as Any), ("amount", amount as Any)]) return ("inputStorePaymentPremiumGiveaway", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("additionalPeers", additionalPeers as Any), ("countriesIso2", countriesIso2 as Any), ("prizeDescription", prizeDescription as Any), ("randomId", randomId as Any), ("untilDate", untilDate as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentPremiumSubscription(let flags): case .inputStorePaymentPremiumSubscription(let flags):
@ -867,13 +868,18 @@ public extension Api {
_4 = parseString(reader) _4 = parseString(reader)
var _5: Int64? var _5: Int64?
_5 = reader.readInt64() _5 = reader.readInt64()
var _6: Api.TextWithEntities?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
let _c5 = _5 != nil let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 { let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiftCode(flags: _1!, users: _2!, boostPeer: _3, currency: _4!, amount: _5!) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiftCode(flags: _1!, users: _2!, boostPeer: _3, currency: _4!, amount: _5!, message: _6)
} }
else { else {
return nil return nil

View File

@ -986,8 +986,8 @@ public extension Api {
case messageActionEmpty case messageActionEmpty
case messageActionGameScore(gameId: Int64, score: Int32) case messageActionGameScore(gameId: Int64, score: Int32)
case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32) case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32)
case messageActionGiftCode(flags: Int32, boostPeer: Api.Peer?, months: Int32, slug: String, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?) case messageActionGiftCode(flags: Int32, boostPeer: Api.Peer?, months: Int32, slug: String, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?)
case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?) case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, message: Api.TextWithEntities?)
case messageActionGiftStars(flags: Int32, currency: String, amount: Int64, stars: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) case messageActionGiftStars(flags: Int32, currency: String, amount: Int64, stars: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
case messageActionGiveawayLaunch(flags: Int32, stars: Int64?) case messageActionGiveawayLaunch(flags: Int32, stars: Int64?)
case messageActionGiveawayResults(flags: Int32, winnersCount: Int32, unclaimedCount: Int32) case messageActionGiveawayResults(flags: Int32, winnersCount: Int32, unclaimedCount: Int32)
@ -1141,9 +1141,9 @@ public extension Api {
toId.serialize(buffer, true) toId.serialize(buffer, true)
serializeInt32(distance, buffer: buffer, boxed: false) serializeInt32(distance, buffer: buffer, boxed: false)
break break
case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount): case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount, let message):
if boxed { if boxed {
buffer.appendInt32(1737240073) buffer.appendInt32(1456486804)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {boostPeer!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {boostPeer!.serialize(buffer, true)}
@ -1153,10 +1153,11 @@ public extension Api {
if Int(flags) & Int(1 << 2) != 0 {serializeInt64(amount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt64(amount!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 4) != 0 {message!.serialize(buffer, true)}
break break
case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount): case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount, let message):
if boxed { if boxed {
buffer.appendInt32(-935499028) buffer.appendInt32(1818391802)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(currency, buffer: buffer, boxed: false) serializeString(currency, buffer: buffer, boxed: false)
@ -1164,6 +1165,7 @@ public extension Api {
serializeInt32(months, buffer: buffer, boxed: false) serializeInt32(months, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeString(cryptoCurrency!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt64(cryptoAmount!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)}
break break
case .messageActionGiftStars(let flags, let currency, let amount, let stars, let cryptoCurrency, let cryptoAmount, let transactionId): case .messageActionGiftStars(let flags, let currency, let amount, let stars, let cryptoCurrency, let cryptoAmount, let transactionId):
if boxed { if boxed {
@ -1439,10 +1441,10 @@ public extension Api {
return ("messageActionGameScore", [("gameId", gameId as Any), ("score", score as Any)]) return ("messageActionGameScore", [("gameId", gameId as Any), ("score", score as Any)])
case .messageActionGeoProximityReached(let fromId, let toId, let distance): case .messageActionGeoProximityReached(let fromId, let toId, let distance):
return ("messageActionGeoProximityReached", [("fromId", fromId as Any), ("toId", toId as Any), ("distance", distance as Any)]) return ("messageActionGeoProximityReached", [("fromId", fromId as Any), ("toId", toId as Any), ("distance", distance as Any)])
case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount): case .messageActionGiftCode(let flags, let boostPeer, let months, let slug, let currency, let amount, let cryptoCurrency, let cryptoAmount, let message):
return ("messageActionGiftCode", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("months", months as Any), ("slug", slug as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any)]) return ("messageActionGiftCode", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("months", months as Any), ("slug", slug as Any), ("currency", currency as Any), ("amount", amount as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("message", message as Any)])
case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount): case .messageActionGiftPremium(let flags, let currency, let amount, let months, let cryptoCurrency, let cryptoAmount, let message):
return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("months", months as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any)]) return ("messageActionGiftPremium", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("months", months as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("message", message as Any)])
case .messageActionGiftStars(let flags, let currency, let amount, let stars, let cryptoCurrency, let cryptoAmount, let transactionId): case .messageActionGiftStars(let flags, let currency, let amount, let stars, let cryptoCurrency, let cryptoAmount, let transactionId):
return ("messageActionGiftStars", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("stars", stars as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("transactionId", transactionId as Any)]) return ("messageActionGiftStars", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("stars", stars as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("transactionId", transactionId as Any)])
case .messageActionGiveawayLaunch(let flags, let stars): case .messageActionGiveawayLaunch(let flags, let stars):
@ -1718,6 +1720,10 @@ public extension Api {
if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) } if Int(_1!) & Int(1 << 3) != 0 {_7 = parseString(reader) }
var _8: Int64? var _8: Int64?
if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt64() } if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt64() }
var _9: Api.TextWithEntities?
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -1726,8 +1732,9 @@ public extension Api {
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { let _c9 = (Int(_1!) & Int(1 << 4) == 0) || _9 != nil
return Api.MessageAction.messageActionGiftCode(flags: _1!, boostPeer: _2, months: _3!, slug: _4!, currency: _5, amount: _6, cryptoCurrency: _7, cryptoAmount: _8) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.MessageAction.messageActionGiftCode(flags: _1!, boostPeer: _2, months: _3!, slug: _4!, currency: _5, amount: _6, cryptoCurrency: _7, cryptoAmount: _8, message: _9)
} }
else { else {
return nil return nil
@ -1746,14 +1753,19 @@ public extension Api {
if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) } if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) }
var _6: Int64? var _6: Int64?
if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() } if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt64() }
var _7: Api.TextWithEntities?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.TextWithEntities
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = _4 != nil let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, months: _4!, cryptoCurrency: _5, cryptoAmount: _6) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.MessageAction.messageActionGiftPremium(flags: _1!, currency: _2!, amount: _3!, months: _4!, cryptoCurrency: _5, cryptoAmount: _6, message: _7)
} }
else { else {
return nil return nil

View File

@ -254,7 +254,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
} }
case let .messageActionRequestedPeer(_, peers): case let .messageActionRequestedPeer(_, peers):
result.append(contentsOf: peers.map(\.peerId)) result.append(contentsOf: peers.map(\.peerId))
case let .messageActionGiftCode(_, boostPeer, _, _, _, _, _, _): case let .messageActionGiftCode(_, boostPeer, _, _, _, _, _, _, _):
if let boostPeer = boostPeer { if let boostPeer = boostPeer {
result.append(boostPeer.peerId) result.append(boostPeer.peerId)
} }

View File

@ -100,8 +100,18 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .joinedByRequest) return TelegramMediaAction(action: .joinedByRequest)
case let .messageActionWebViewDataSentMe(text, _), let .messageActionWebViewDataSent(text): case let .messageActionWebViewDataSentMe(text, _), let .messageActionWebViewDataSent(text):
return TelegramMediaAction(action: .webViewData(text)) return TelegramMediaAction(action: .webViewData(text))
case let .messageActionGiftPremium(_, currency, amount, months, cryptoCurrency, cryptoAmount): case let .messageActionGiftPremium(_, currency, amount, months, cryptoCurrency, cryptoAmount, message):
return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount)) let text: String?
let entities: [MessageTextEntity]?
switch message {
case let .textWithEntities(textValue, entitiesValue):
text = textValue
entities = messageTextEntitiesFromApiEntities(entitiesValue)
default:
text = nil
entities = nil
}
return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities))
case let .messageActionGiftStars(_, currency, amount, stars, cryptoCurrency, cryptoAmount, transactionId): case let .messageActionGiftStars(_, currency, amount, stars, cryptoCurrency, cryptoAmount, transactionId):
return TelegramMediaAction(action: .giftStars(currency: currency, amount: amount, count: stars, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, transactionId: transactionId)) return TelegramMediaAction(action: .giftStars(currency: currency, amount: amount, count: stars, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, transactionId: transactionId))
case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId): case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId):
@ -133,8 +143,18 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
} else { } else {
return TelegramMediaAction(action: .setChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper), forBoth: (flags & (1 << 1)) != 0)) return TelegramMediaAction(action: .setChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper), forBoth: (flags & (1 << 1)) != 0))
} }
case let .messageActionGiftCode(flags, boostPeer, months, slug, currency, amount, cryptoCurrency, cryptoAmount): case let .messageActionGiftCode(flags, boostPeer, months, slug, currency, amount, cryptoCurrency, cryptoAmount, message):
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer?.peerId, months: months, currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount)) let text: String?
let entities: [MessageTextEntity]?
switch message {
case let .textWithEntities(textValue, entitiesValue):
text = textValue
entities = messageTextEntitiesFromApiEntities(entitiesValue)
default:
text = nil
entities = nil
}
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer?.peerId, months: months, currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, text: text, entities: entities))
case let .messageActionGiveawayLaunch(_, stars): case let .messageActionGiveawayLaunch(_, stars):
return TelegramMediaAction(action: .giveawayLaunched(stars: stars)) return TelegramMediaAction(action: .giveawayLaunched(stars: stars))
case let .messageActionGiveawayResults(flags, winners, unclaimed): case let .messageActionGiveawayResults(flags, winners, unclaimed):

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 189 return 190
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -114,7 +114,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case setChatTheme(emoji: String) case setChatTheme(emoji: String)
case joinedByRequest case joinedByRequest
case webViewData(String) case webViewData(String)
case giftPremium(currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?) case giftPremium(currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?, text: String?, entities: [MessageTextEntity]?)
case topicCreated(title: String, iconColor: Int32, iconFileId: Int64?) case topicCreated(title: String, iconColor: Int32, iconFileId: Int64?)
case topicEdited(components: [ForumTopicEditComponent]) case topicEdited(components: [ForumTopicEditComponent])
case suggestedProfilePhoto(image: TelegramMediaImage?) case suggestedProfilePhoto(image: TelegramMediaImage?)
@ -122,7 +122,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case requestedPeer(buttonId: Int32, peerIds: [PeerId]) case requestedPeer(buttonId: Int32, peerIds: [PeerId])
case setChatWallpaper(wallpaper: TelegramWallpaper, forBoth: Bool) case setChatWallpaper(wallpaper: TelegramWallpaper, forBoth: Bool)
case setSameChatWallpaper(wallpaper: TelegramWallpaper) case setSameChatWallpaper(wallpaper: TelegramWallpaper)
case giftCode(slug: String, fromGiveaway: Bool, isUnclaimed: Bool, boostPeerId: PeerId?, months: Int32, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?) case giftCode(slug: String, fromGiveaway: Bool, isUnclaimed: Bool, boostPeerId: PeerId?, months: Int32, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?, text: String?, entities: [MessageTextEntity]?)
case giveawayLaunched(stars: Int64?) case giveawayLaunched(stars: Int64?)
case joinedChannel case joinedChannel
case giveawayResults(winners: Int32, unclaimed: Int32, stars: Bool) case giveawayResults(winners: Int32, unclaimed: Int32, stars: Bool)
@ -200,7 +200,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 26: case 26:
self = .webViewData(decoder.decodeStringForKey("t", orElse: "")) self = .webViewData(decoder.decodeStringForKey("t", orElse: ""))
case 27: case 27:
self = .giftPremium(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), months: decoder.decodeInt32ForKey("months", orElse: 0), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount")) self = .giftPremium(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), months: decoder.decodeInt32ForKey("months", orElse: 0), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"))
case 28: case 28:
self = .topicCreated(title: decoder.decodeStringForKey("title", orElse: ""), iconColor: decoder.decodeInt32ForKey("iconColor", orElse: 0), iconFileId: decoder.decodeOptionalInt64ForKey("iconFileId")) self = .topicCreated(title: decoder.decodeStringForKey("title", orElse: ""), iconColor: decoder.decodeInt32ForKey("iconColor", orElse: 0), iconFileId: decoder.decodeOptionalInt64ForKey("iconFileId"))
case 29: case 29:
@ -230,7 +230,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 35: case 35:
self = .botAppAccessGranted(appName: decoder.decodeOptionalStringForKey("app"), type: decoder.decodeOptionalInt32ForKey("atp").flatMap { BotSendMessageAccessGrantedType(rawValue: $0) }) self = .botAppAccessGranted(appName: decoder.decodeOptionalStringForKey("app"), type: decoder.decodeOptionalInt32ForKey("atp").flatMap { BotSendMessageAccessGrantedType(rawValue: $0) })
case 36: case 36:
self = .giftCode(slug: decoder.decodeStringForKey("slug", orElse: ""), fromGiveaway: decoder.decodeBoolForKey("give", orElse: false), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: decoder.decodeOptionalInt64ForKey("pi").flatMap { PeerId($0) }, months: decoder.decodeInt32ForKey("months", orElse: 0), currency: decoder.decodeOptionalStringForKey("currency"), amount: decoder.decodeOptionalInt64ForKey("amount"), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount")) self = .giftCode(slug: decoder.decodeStringForKey("slug", orElse: ""), fromGiveaway: decoder.decodeBoolForKey("give", orElse: false), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: decoder.decodeOptionalInt64ForKey("pi").flatMap { PeerId($0) }, months: decoder.decodeInt32ForKey("months", orElse: 0), currency: decoder.decodeOptionalStringForKey("currency"), amount: decoder.decodeOptionalInt64ForKey("amount"), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"))
case 37: case 37:
self = .giveawayLaunched(stars: decoder.decodeOptionalInt64ForKey("stars")) self = .giveawayLaunched(stars: decoder.decodeOptionalInt64ForKey("stars"))
case 38: case 38:
@ -382,7 +382,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case let .webViewData(text): case let .webViewData(text):
encoder.encodeInt32(26, forKey: "_rawValue") encoder.encodeInt32(26, forKey: "_rawValue")
encoder.encodeString(text, forKey: "t") encoder.encodeString(text, forKey: "t")
case let .giftPremium(currency, amount, months, cryptoCurrency, cryptoAmount): case let .giftPremium(currency, amount, months, cryptoCurrency, cryptoAmount, text, entities):
encoder.encodeInt32(27, forKey: "_rawValue") encoder.encodeInt32(27, forKey: "_rawValue")
encoder.encodeString(currency, forKey: "currency") encoder.encodeString(currency, forKey: "currency")
encoder.encodeInt64(amount, forKey: "amount") encoder.encodeInt64(amount, forKey: "amount")
@ -390,6 +390,16 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
if let cryptoCurrency = cryptoCurrency, let cryptoAmount = cryptoAmount { if let cryptoCurrency = cryptoCurrency, let cryptoAmount = cryptoAmount {
encoder.encodeString(cryptoCurrency, forKey: "cryptoCurrency") encoder.encodeString(cryptoCurrency, forKey: "cryptoCurrency")
encoder.encodeInt64(cryptoAmount, forKey: "cryptoAmount") encoder.encodeInt64(cryptoAmount, forKey: "cryptoAmount")
} else {
encoder.encodeNil(forKey: "cryptoCurrency")
encoder.encodeNil(forKey: "cryptoAmount")
}
if let text, let entities {
encoder.encodeString(text, forKey: "text")
encoder.encodeObjectArray(entities, forKey: "entities")
} else {
encoder.encodeNil(forKey: "text")
encoder.encodeNil(forKey: "entities")
} }
case let .topicCreated(title, iconColor, iconFileId): case let .topicCreated(title, iconColor, iconFileId):
encoder.encodeInt32(28, forKey: "_rawValue") encoder.encodeInt32(28, forKey: "_rawValue")
@ -433,7 +443,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "atp") encoder.encodeNil(forKey: "atp")
} }
case let .giftCode(slug, fromGiveaway, unclaimed, boostPeerId, months, currency, amount, cryptoCurrency, cryptoAmount): case let .giftCode(slug, fromGiveaway, unclaimed, boostPeerId, months, currency, amount, cryptoCurrency, cryptoAmount, text, entities):
encoder.encodeInt32(36, forKey: "_rawValue") encoder.encodeInt32(36, forKey: "_rawValue")
encoder.encodeString(slug, forKey: "slug") encoder.encodeString(slug, forKey: "slug")
encoder.encodeBool(fromGiveaway, forKey: "give") encoder.encodeBool(fromGiveaway, forKey: "give")
@ -464,6 +474,13 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "cryptoAmount") encoder.encodeNil(forKey: "cryptoAmount")
} }
if let text, let entities {
encoder.encodeString(text, forKey: "text")
encoder.encodeObjectArray(entities, forKey: "entities")
} else {
encoder.encodeNil(forKey: "text")
encoder.encodeNil(forKey: "entities")
}
case let .giveawayLaunched(stars): case let .giveawayLaunched(stars):
encoder.encodeInt32(37, forKey: "_rawValue") encoder.encodeInt32(37, forKey: "_rawValue")
if let stars = stars { if let stars = stars {
@ -563,7 +580,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
return peerIds return peerIds
case let .requestedPeer(_, peerIds): case let .requestedPeer(_, peerIds):
return peerIds return peerIds
case let .giftCode(_, _, _, boostPeerId, _, _, _, _, _): case let .giftCode(_, _, _, boostPeerId, _, _, _, _, _, _, _):
return boostPeerId.flatMap { [$0] } ?? [] return boostPeerId.flatMap { [$0] } ?? []
case let .paymentRefunded(peerId, _, _, _, _): case let .paymentRefunded(peerId, _, _, _, _):
return [peerId] return [peerId]

View File

@ -15,7 +15,7 @@ public enum AppStoreTransactionPurpose {
case upgrade case upgrade
case restore case restore
case gift(peerId: EnginePeer.Id, currency: String, amount: Int64) case gift(peerId: EnginePeer.Id, currency: String, amount: Int64)
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64) case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64, text: String?, entities: [MessageTextEntity]?)
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64) case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
case stars(count: Int64, currency: String, amount: Int64) case stars(count: Int64, currency: String, amount: Int64)
case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64) case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64)
@ -43,7 +43,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
} }
return .single(.inputStorePaymentGiftPremium(userId: inputUser, currency: currency, amount: amount)) return .single(.inputStorePaymentGiftPremium(userId: inputUser, currency: currency, amount: amount))
} }
case let .giftCode(peerIds, boostPeerId, currency, amount): case let .giftCode(peerIds, boostPeerId, currency, amount, text, entities):
return account.postbox.transaction { transaction -> Api.InputStorePaymentPurpose in return account.postbox.transaction { transaction -> Api.InputStorePaymentPurpose in
var flags: Int32 = 0 var flags: Int32 = 0
var apiBoostPeer: Api.InputPeer? var apiBoostPeer: Api.InputPeer?
@ -60,7 +60,12 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
flags |= (1 << 0) flags |= (1 << 0)
} }
return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount) var message: Api.TextWithEntities?
if let text {
flags |= (1 << 1)
message = .textWithEntities(text: text, entities: apiEntitiesFromMessageTextEntities(entities ?? [], associatedPeers: SimpleDictionary()))
}
return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount, message: message)
} }
case let .giveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount): case let .giveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount):
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in

View File

@ -293,7 +293,7 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv
} }
} }
let inputPurpose: Api.InputStorePaymentPurpose = .inputStorePaymentPremiumGiftCode(flags: 0, users: inputUsers, boostPeer: nil, currency: currency, amount: amount) let inputPurpose: Api.InputStorePaymentPurpose = .inputStorePaymentPremiumGiftCode(flags: 0, users: inputUsers, boostPeer: nil, currency: currency, amount: amount, message: nil)
var flags: Int32 = 0 var flags: Int32 = 0
if let _ = option.storeProductId { if let _ = option.storeProductId {

View File

@ -735,7 +735,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} }
case let .webViewData(text): case let .webViewData(text):
attributedString = NSAttributedString(string: strings.Notification_WebAppSentData(text).string, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_WebAppSentData(text).string, font: titleFont, textColor: primaryTextColor)
case let .giftPremium(currency, amount, _, _, _): case let .giftPremium(currency, amount, _, _, _, _, _):
let price = formatCurrencyAmount(amount, currency: currency) let price = formatCurrencyAmount(amount, currency: currency)
if message.author?.id == accountPeerId { if message.author?.id == accountPeerId {
attributedString = addAttributesToStringWithRanges(strings.Notification_PremiumGift_SentYou(price)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) attributedString = addAttributesToStringWithRanges(strings.Notification_PremiumGift_SentYou(price)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
@ -952,7 +952,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
let resultTitleString = strings.Notification_ChangedToSameWallpaper(compactAuthorName) let resultTitleString = strings.Notification_ChangedToSameWallpaper(compactAuthorName)
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
} }
case let .giftCode(_, _, _, boostPeerId, _, currency, amount, _, _): case let .giftCode(_, _, _, boostPeerId, _, currency, amount, _, _, _, _):
if boostPeerId == nil, let currency, let amount { if boostPeerId == nil, let currency, let amount {
let price = formatCurrencyAmount(amount, currency: currency) let price = formatCurrencyAmount(amount, currency: currency)
if message.author?.id == accountPeerId { if message.author?.id == accountPeerId {
@ -1058,7 +1058,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
let _ = text let _ = text
let _ = entities let _ = entities
let starsPrice = "\(gift.price) Stars" let starsPrice = strings.Notification_StarsGift_Stars(Int32(gift.price))
var authorName = compactAuthorName var authorName = compactAuthorName
var peerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)] var peerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)]
if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 { if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 {

View File

@ -37,6 +37,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? private var mediaBackgroundContent: WallpaperBubbleBackgroundNode?
private let titleNode: TextNode private let titleNode: TextNode
private let subtitleNode: TextNodeWithEntities private let subtitleNode: TextNodeWithEntities
private let textClippingNode: ASDisplayNode
private var dustNode: InvisibleInkDustNode? private var dustNode: InvisibleInkDustNode?
private let placeholderNode: StickerShimmerEffectNode private let placeholderNode: StickerShimmerEffectNode
private let animationNode: AnimatedStickerNode private let animationNode: AnimatedStickerNode
@ -49,11 +50,17 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private let buttonStarsNode: PremiumStarsNode private let buttonStarsNode: PremiumStarsNode
private let buttonTitleNode: TextNode private let buttonTitleNode: TextNode
private var maskView: UIImageView?
private var maskOverlayView: UIView?
private var cachedMaskBackgroundImage: (CGPoint, UIImage, [CGRect])? private var cachedMaskBackgroundImage: (CGPoint, UIImage, [CGRect])?
private var absoluteRect: (CGRect, CGSize)? private var absoluteRect: (CGRect, CGSize)?
private var isPlaying: Bool = false private var isPlaying: Bool = false
private var isExpanded: Bool = false
private var appliedIsExpanded: Bool = false
private var currentProgressDisposable: Disposable? private var currentProgressDisposable: Disposable?
override public var visibility: ListViewItemNodeVisibility { override public var visibility: ListViewItemNodeVisibility {
@ -105,6 +112,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.subtitleNode.textNode.isUserInteractionEnabled = false self.subtitleNode.textNode.isUserInteractionEnabled = false
self.subtitleNode.textNode.displaysAsynchronously = false self.subtitleNode.textNode.displaysAsynchronously = false
self.textClippingNode = ASDisplayNode()
self.textClippingNode.clipsToBounds = true
self.buttonNode = HighlightTrackingButtonNode() self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true self.buttonNode.clipsToBounds = true
self.buttonNode.cornerRadius = 17.0 self.buttonNode.cornerRadius = 17.0
@ -133,7 +143,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
self.addSubnode(self.labelNode) self.addSubnode(self.labelNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode.textNode) self.addSubnode(self.textClippingNode)
self.textClippingNode.addSubnode(self.subtitleNode.textNode)
self.addSubnode(self.placeholderNode) self.addSubnode(self.placeholderNode)
self.addSubnode(self.animationNode) self.addSubnode(self.animationNode)
@ -251,9 +262,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let makeSubtitleLayout = TextNodeWithEntities.asyncLayout(self.subtitleNode) let makeSubtitleLayout = TextNodeWithEntities.asyncLayout(self.subtitleNode)
let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode) let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode)
let makeRibbonTextLayout = TextNode.asyncLayout(self.ribbonTextNode) let makeRibbonTextLayout = TextNode.asyncLayout(self.ribbonTextNode)
let makeMeasureTextLayout = TextNode.asyncLayout(nil)
let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage
let currentIsExpanded = self.isExpanded
return { item, layoutConstants, _, _, _, _ in return { item, layoutConstants, _, _, _, _ in
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
@ -279,9 +293,19 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
for media in item.message.media { for media in item.message.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
switch action.action { switch action.action {
case let .giftPremium(_, _, monthsValue, _, _): case let .giftPremium(_, _, monthsValue, _, _, giftText, giftEntities):
months = monthsValue months = monthsValue
text = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(months)).string if months == 12 {
title = item.presentationData.strings.Notification_PremiumGift_YearsTitle(1)
} else {
title = item.presentationData.strings.Notification_PremiumGift_MonthsTitle(months)
}
if let giftText, !giftText.isEmpty {
text = giftText
entities = giftEntities ?? []
} else {
text = item.presentationData.strings.Notification_PremiumGift_SubscriptionDescription
}
case let .giftStars(_, _, count, _, _, _): case let .giftStars(_, _, count, _, _, _):
if count <= 1000 { if count <= 1000 {
months = 3 months = 3
@ -310,10 +334,20 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
title = item.presentationData.strings.Notification_StarsGiveaway_Title title = item.presentationData.strings.Notification_StarsGiveaway_Title
text = item.presentationData.strings.Notification_StarsGiveaway_Subtitle(peerName, item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(count))).string text = item.presentationData.strings.Notification_StarsGiveaway_Subtitle(peerName, item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(count))).string
case let .giftCode(_, fromGiveaway, unclaimed, channelId, monthsValue, _, _, _, _): case let .giftCode(_, fromGiveaway, unclaimed, channelId, monthsValue, _, _, _, _, giftText, giftEntities):
if channelId == nil { if channelId == nil {
months = monthsValue months = monthsValue
text = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(months)).string if months == 12 {
title = item.presentationData.strings.Notification_PremiumGift_YearsTitle(1)
} else {
title = item.presentationData.strings.Notification_PremiumGift_MonthsTitle(months)
}
if let giftText, !giftText.isEmpty {
text = giftText
entities = giftEntities ?? []
} else {
text = item.presentationData.strings.Notification_PremiumGift_SubscriptionDescription
}
if item.message.author?.id != item.context.account.peerId { if item.message.author?.id != item.context.account.peerId {
buttonTitle = item.presentationData.strings.Notification_PremiumGift_UseGift buttonTitle = item.presentationData.strings.Notification_PremiumGift_UseGift
} }
@ -419,13 +453,24 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
), textAlignment: .center) ), textAlignment: .center)
} }
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let textConstrainedSize = CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude)
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
var canExpand = false
var clippedTextHeight: CGFloat = subtitleLayout.size.height
if subtitleLayout.numberOfLines > 4 {
let (measuredTextLayout, _) = makeMeasureTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 4, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets()))
canExpand = true
if !currentIsExpanded {
clippedTextHeight = measuredTextLayout.size.height
}
}
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
giftSize.height = titleLayout.size.height + textSpacing + subtitleLayout.size.height + 164.0 giftSize.height = titleLayout.size.height + textSpacing + clippedTextHeight + 164.0
if !buttonTitle.isEmpty { if !buttonTitle.isEmpty {
giftSize.height += 48.0 giftSize.height += 48.0
} }
@ -472,8 +517,18 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
return (backgroundSize.width, { boundingWidth in return (backgroundSize.width, { boundingWidth in
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in return (backgroundSize, { [weak self] animation, synchronousLoads, info in
if let strongSelf = self { if let strongSelf = self {
let isFirstTime = strongSelf.item == nil
var isExpandedUpdated = false
if strongSelf.appliedIsExpanded != currentIsExpanded {
strongSelf.appliedIsExpanded = currentIsExpanded
info?.setInvertOffsetDirection()
isExpandedUpdated = true
}
let _ = isExpandedUpdated
let overlayColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) let overlayColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 16.0 : 0.0), size: giftSize) let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - giftSize.width) / 2.0), y: hasServiceMessage ? labelLayout.size.height + 16.0 : 0.0), size: giftSize)
@ -551,8 +606,21 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size) let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame strongSelf.titleNode.frame = titleFrame
let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY + textSpacing), size: CGSize(width: boundingWidth, height: clippedTextHeight))
let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY + textSpacing), size: subtitleLayout.size) let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY + textSpacing), size: subtitleLayout.size)
strongSelf.subtitleNode.textNode.frame = subtitleFrame strongSelf.subtitleNode.textNode.frame = CGRect(origin: .zero, size: subtitleLayout.size)
if isFirstTime {
strongSelf.textClippingNode.frame = clippingTextFrame
} else {
animation.animator.updateFrame(layer: strongSelf.textClippingNode.layer, frame: clippingTextFrame, completion: nil)
}
if let maskView = strongSelf.maskView, let maskOverlayView = strongSelf.maskOverlayView {
animation.animator.updateFrame(layer: maskView.layer, frame: CGRect(origin: .zero, size: CGSize(width: boundingWidth, height: clippingTextFrame.size.height)), completion: nil)
animation.animator.updateFrame(layer: maskOverlayView.layer, frame: CGRect(origin: .zero, size: CGSize(width: boundingWidth, height: clippingTextFrame.size.height)), completion: nil)
}
if !subtitleLayout.spoilers.isEmpty { if !subtitleLayout.spoilers.isEmpty {
let dustColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText let dustColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText
@ -573,11 +641,11 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.dustNode = nil strongSelf.dustNode = nil
} }
let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: subtitleFrame.maxY + 18.0), size: buttonTitleLayout.size) let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: clippingTextFrame.maxY + 18.0), size: buttonTitleLayout.size)
strongSelf.buttonTitleNode.frame = buttonTitleFrame strongSelf.buttonTitleNode.frame = buttonTitleFrame
let buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) let buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0)
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: subtitleFrame.maxY + 10.0), size: buttonSize) strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: clippingTextFrame.maxY + 10.0), size: buttonSize)
strongSelf.buttonStarsNode.frame = CGRect(origin: .zero, size: buttonSize) strongSelf.buttonStarsNode.frame = CGRect(origin: .zero, size: buttonSize)
if ribbonTextLayout.size.width > 0.0 { if ribbonTextLayout.size.width > 0.0 {
@ -666,6 +734,26 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.updateAbsoluteRect(rect, within: size) strongSelf.updateAbsoluteRect(rect, within: size)
} }
if canExpand {
if strongSelf.maskView?.image == nil {
strongSelf.maskView?.image = generateMaskImage()
}
strongSelf.textClippingNode.view.mask = strongSelf.maskView
// var expandIconFrame: CGRect = .zero
// if let icon = strongSelf.expandIcon.image {
// expandIconFrame = CGRect(origin: CGPoint(x: boundingWidth - icon.size.width - 19.0, y: backgroundFrame.maxY - icon.size.height - 6.0), size: icon.size)
// if wasHidden || isFirstTime {
// strongSelf.expandIcon.position = expandIconFrame.center
// } else {
// animation.animator.updatePosition(layer: strongSelf.expandIcon.layer, position: expandIconFrame.center, completion: nil)
// }
// strongSelf.expandIcon.bounds = CGRect(origin: .zero, size: expandIconFrame.size)
// }
} else {
strongSelf.textClippingNode.view.mask = nil
}
switch strongSelf.visibility { switch strongSelf.visibility {
case .none: case .none:
strongSelf.subtitleNode.visibilityRect = nil strongSelf.subtitleNode.visibilityRect = nil
@ -853,3 +941,22 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
} }
private func generateMaskImage() -> UIImage? {
return generateImage(CGSize(width: 140, height: 30), rotatedContext: { size, context in
context.clear(CGRect(origin: .zero, size: size))
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: .zero, size: size))
var locations: [CGFloat] = [0.0, 0.5, 1.0]
let colors: [CGColor] = [UIColor.white.cgColor, UIColor.white.withAlphaComponent(0.0).cgColor, UIColor.white.withAlphaComponent(0.0).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.setBlendMode(.copy)
context.clip(to: CGRect(origin: CGPoint(x: 10.0, y: 8.0), size: CGSize(width: 130.0, height: 22.0)))
context.drawLinearGradient(gradient, start: CGPoint(x: 10.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 22.0, right: 130.0))
}

View File

@ -306,7 +306,7 @@ final class GiftOptionsScreenComponent: Component {
let giftController = GiftSetupScreen( let giftController = GiftSetupScreen(
context: component.context, context: component.context,
peerId: component.peerId, peerId: component.peerId,
gift: gift, subject: .starGift(gift),
completion: component.completion completion: component.completion
) )
mainController.push(giftController) mainController.push(giftController)
@ -359,94 +359,6 @@ final class GiftOptionsScreenComponent: Component {
} }
} }
private func buyPremium(_ product: PremiumGiftProduct) {
guard let component = self.component, let inAppPurchaseManager = self.component?.context.inAppPurchaseManager, self.inProgressPremiumGift == nil else {
return
}
self.inProgressPremiumGift = product.id
self.state?.updated()
let (currency, amount) = product.storeProduct.priceCurrencyAndAmount
addAppLogEvent(postbox: component.context.account.postbox, type: "premium_gift.promo_screen_accept")
let purpose: AppStoreTransactionPurpose = .giftCode(peerIds: [component.peerId], boostPeer: nil, currency: currency, amount: amount)
let quantity: Int32 = 1
let completion = component.completion
let _ = (component.context.engine.payments.canPurchasePremium(purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] available in
if let strongSelf = self {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
if available {
strongSelf.purchaseDisposable.set((inAppPurchaseManager.buyProduct(product.storeProduct, quantity: quantity, purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let completion {
completion()
} else {
guard let self, case .purchased = status, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else {
return
}
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftOptionsScreen) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) }
var foundController = false
for controller in controllers.reversed() {
if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation {
chatController.hintPlayNextOutgoingGift()
foundController = true
break
}
}
if !foundController {
let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
chatController.hintPlayNextOutgoingGift()
controllers.append(chatController)
}
navigationController.setViewControllers(controllers, animated: true)
}
}, error: { [weak self] error in
guard let self, let controller = self.environment?.controller() else {
return
}
self.inProgressPremiumGift = nil
self.state?.updated(transition: .immediate)
var errorText: String?
switch error {
case .generic:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .network:
errorText = presentationData.strings.Premium_Purchase_ErrorNetwork
case .notAllowed:
errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed
case .cantMakePayments:
errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments
case .assignFailed:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .tryLater:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .cancelled:
break
}
if let errorText {
addAppLogEvent(postbox: component.context.account.postbox, type: "premium_gift.promo_screen_fail")
let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
controller.present(alertController, in: .window(.root))
}
}))
} else {
self?.inProgressPremiumGift = nil
self?.state?.updated(transition: .immediate)
}
}
})
}
func update(component: GiftOptionsScreenComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize { func update(component: GiftOptionsScreenComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true self.isUpdating = true
defer { defer {
@ -707,7 +619,7 @@ final class GiftOptionsScreenComponent: Component {
var validIds: [AnyHashable] = [] var validIds: [AnyHashable] = []
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: premiumOptionSize) var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: premiumOptionSize)
for product in premiumProducts { for product in premiumProducts {
let itemId = AnyHashable(product.storeProduct.id) let itemId = AnyHashable(product.id)
validIds.append(itemId) validIds.append(itemId)
var itemTransition = transition var itemTransition = transition
@ -756,7 +668,23 @@ final class GiftOptionsScreenComponent: Component {
), ),
effectAlignment: .center, effectAlignment: .center,
action: { [weak self] in action: { [weak self] in
self?.buyPremium(product) if let self, let component = self.component {
if let controller = controller() as? GiftOptionsScreen {
let mainController: ViewController
if let parentController = controller.parentController() {
mainController = parentController
} else {
mainController = controller
}
let giftController = GiftSetupScreen(
context: component.context,
peerId: component.peerId,
subject: .premium(product),
completion: component.completion
)
mainController.push(giftController)
}
}
}, },
animateAlpha: false animateAlpha: false
) )
@ -1019,6 +947,25 @@ final class GiftOptionsScreenComponent: Component {
} }
self.peer = peer self.peer = peer
if availableProducts.isEmpty {
var premiumProducts: [PremiumGiftProduct] = []
for option in premiumOptions {
premiumProducts.append(
PremiumGiftProduct(
giftOption: CachedPremiumGiftOption(
months: option.months,
currency: option.currency,
amount: option.amount,
botUrl: "",
storeProductId: option.storeProductId
),
storeProduct: nil,
discount: nil
)
)
}
self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months })
} else {
let shortestOptionPrice: (Int64, NSDecimalNumber) let shortestOptionPrice: (Int64, NSDecimalNumber)
if let product = availableProducts.first(where: { $0.id.hasSuffix(".monthly") }) { if let product = availableProducts.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue) shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
@ -1035,6 +982,7 @@ final class GiftOptionsScreenComponent: Component {
} }
} }
self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months }) self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months })
}
self.starGifts = starGifts self.starGifts = starGifts
@ -1105,25 +1053,3 @@ open class GiftOptionsScreen: ViewControllerComponentContainer, GiftOptionsScree
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
} }
} }
private struct PremiumGiftProduct: Equatable {
let giftOption: CachedPremiumGiftOption
let storeProduct: InAppPurchaseManager.Product
let discount: Int?
var id: String {
return self.storeProduct.id
}
var months: Int32 {
return self.giftOption.months
}
var price: String {
return self.storeProduct.price
}
var pricePerMonth: String {
return self.storeProduct.pricePerMonth(Int(self.months))
}
}

View File

@ -17,6 +17,7 @@ swift_library(
"//submodules/SSignalKit/SwiftSignalKit", "//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData", "//submodules/TelegramPresentationData",
"//submodules/TelegramUIPreferences", "//submodules/TelegramUIPreferences",
"//submodules/TelegramStringFormatting",
"//submodules/AccountContext", "//submodules/AccountContext",
"//submodules/PresentationDataUtils", "//submodules/PresentationDataUtils",
"//submodules/Markdown", "//submodules/Markdown",
@ -41,6 +42,7 @@ swift_library(
"//submodules/BotPaymentsUI", "//submodules/BotPaymentsUI",
"//submodules/TelegramUI/Components/EmojiSuggestionsComponent", "//submodules/TelegramUI/Components/EmojiSuggestionsComponent",
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode", "//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
"//submodules/InAppPurchaseManager",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -14,6 +14,10 @@ import WallpaperBackgroundNode
import ListItemComponentAdaptor import ListItemComponentAdaptor
final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
enum Subject: Equatable {
case premium(months: Int32, amount: Int64, currency: String)
case starGift(gift: StarGift)
}
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let componentTheme: PresentationTheme let componentTheme: PresentationTheme
@ -26,7 +30,7 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd
let nameDisplayOrder: PresentationPersonNameOrder let nameDisplayOrder: PresentationPersonNameOrder
let accountPeer: EnginePeer? let accountPeer: EnginePeer?
let gift: StarGift let subject: ChatGiftPreviewItem.Subject
let text: String let text: String
let entities: [MessageTextEntity] let entities: [MessageTextEntity]
@ -42,7 +46,7 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd
dateTimeFormat: PresentationDateTimeFormat, dateTimeFormat: PresentationDateTimeFormat,
nameDisplayOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder,
accountPeer: EnginePeer?, accountPeer: EnginePeer?,
gift: StarGift, subject: ChatGiftPreviewItem.Subject,
text: String, text: String,
entities: [MessageTextEntity] entities: [MessageTextEntity]
) { ) {
@ -57,7 +61,7 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd
self.dateTimeFormat = dateTimeFormat self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder self.nameDisplayOrder = nameDisplayOrder
self.accountPeer = accountPeer self.accountPeer = accountPeer
self.gift = gift self.subject = subject
self.text = text self.text = text
self.entities = entities self.entities = entities
} }
@ -206,9 +210,22 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
peers[authorPeerId] = item.accountPeer?._asPeer() peers[authorPeerId] = item.accountPeer?._asPeer()
let media: [Media] = [ let media: [Media]
TelegramMediaAction(action: .starGift(gift: item.gift, convertStars: item.gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false)) switch item.subject {
case let .premium(months, amount, currency):
media = [
TelegramMediaAction(
action: .giftPremium(currency: currency, amount: amount, months: months, cryptoCurrency: nil, cryptoAmount: nil, text: item.text, entities: item.entities)
)
] ]
case let .starGift(gift):
media = [
TelegramMediaAction(
action: .starGift(gift: gift, convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false)
)
]
}
let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:]) let message = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: peers[authorPeerId], text: "", attributes: [], media: media, peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false)) items.append(item.context.sharedContext.makeChatMessagePreviewItem(context: item.context, messages: [message], theme: item.componentTheme, strings: item.strings, wallpaper: item.wallpaper, fontSize: item.fontSize, chatBubbleCorners: item.chatBubbleCorners, dateTimeFormat: item.dateTimeFormat, nameOrder: item.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: currentBackgroundNode, availableReactions: nil, accountPeer: nil, isCentered: false, isPreview: true, isStandalone: false))
} }

View File

@ -7,6 +7,7 @@ import Postbox
import TelegramCore import TelegramCore
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
import TelegramStringFormatting
import PresentationDataUtils import PresentationDataUtils
import AccountContext import AccountContext
import ComponentFlow import ComponentFlow
@ -27,24 +28,25 @@ import EmojiSuggestionsComponent
import ChatPresentationInterfaceState import ChatPresentationInterfaceState
import AudioToolbox import AudioToolbox
import TextFormat import TextFormat
import InAppPurchaseManager
final class GiftSetupScreenComponent: Component { final class GiftSetupScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let peerId: EnginePeer.Id let peerId: EnginePeer.Id
let gift: StarGift let subject: GiftSetupScreen.Subject
let completion: (() -> Void)? let completion: (() -> Void)?
init( init(
context: AccountContext, context: AccountContext,
peerId: EnginePeer.Id, peerId: EnginePeer.Id,
gift: StarGift, subject: GiftSetupScreen.Subject,
completion: (() -> Void)? = nil completion: (() -> Void)? = nil
) { ) {
self.context = context self.context = context
self.peerId = peerId self.peerId = peerId
self.gift = gift self.subject = subject
self.completion = completion self.completion = completion
} }
@ -55,7 +57,7 @@ final class GiftSetupScreenComponent: Component {
if lhs.peerId != rhs.peerId { if lhs.peerId != rhs.peerId {
return false return false
} }
if lhs.gift != rhs.gift { if lhs.subject != rhs.subject {
return false return false
} }
return true return true
@ -103,6 +105,7 @@ final class GiftSetupScreenComponent: Component {
private var currentEmojiSuggestionView: ComponentHostView<Empty>? private var currentEmojiSuggestionView: ComponentHostView<Empty>?
private var hideName = false private var hideName = false
private var inProgress = false
private var previousHadInputHeight: Bool = false private var previousHadInputHeight: Bool = false
private var previousInputHeight: CGFloat? private var previousInputHeight: CGFloat?
@ -189,7 +192,108 @@ final class GiftSetupScreenComponent: Component {
} }
func proceed() { func proceed() {
guard let component = self.component, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else { guard let component = self.component else {
return
}
switch component.subject {
case .premium:
self.proceedWithPremiumGift()
case .starGift:
self.proceedWithStarGift()
}
}
func proceedWithPremiumGift() {
guard let component = self.component, case let .premium(product) = component.subject, let storeProduct = product.storeProduct, let inAppPurchaseManager = component.context.inAppPurchaseManager else {
return
}
self.inProgress = true
self.state?.updated()
let (currency, amount) = storeProduct.priceCurrencyAndAmount
addAppLogEvent(postbox: component.context.account.postbox, type: "premium_gift.promo_screen_accept")
let entities = generateChatInputTextEntities(self.textInputState.text)
let purpose: AppStoreTransactionPurpose = .giftCode(peerIds: [component.peerId], boostPeer: nil, currency: currency, amount: amount, text: self.textInputState.text.string, entities: entities)
let quantity: Int32 = 1
let completion = component.completion
let _ = (component.context.engine.payments.canPurchasePremium(purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] available in
guard let self else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
if available {
let _ = (inAppPurchaseManager.buyProduct(storeProduct, quantity: quantity, purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let completion {
completion()
} else {
guard let self, case .purchased = status, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else {
return
}
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) }
var foundController = false
for controller in controllers.reversed() {
if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation {
chatController.hintPlayNextOutgoingGift()
foundController = true
break
}
}
if !foundController {
let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
chatController.hintPlayNextOutgoingGift()
controllers.append(chatController)
}
navigationController.setViewControllers(controllers, animated: true)
}
}, error: { [weak self] error in
guard let self, let controller = self.environment?.controller() else {
return
}
self.state?.updated(transition: .immediate)
var errorText: String?
switch error {
case .generic:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .network:
errorText = presentationData.strings.Premium_Purchase_ErrorNetwork
case .notAllowed:
errorText = presentationData.strings.Premium_Purchase_ErrorNotAllowed
case .cantMakePayments:
errorText = presentationData.strings.Premium_Purchase_ErrorCantMakePayments
case .assignFailed:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .tryLater:
errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
case .cancelled:
break
}
if let errorText {
addAppLogEvent(postbox: component.context.account.postbox, type: "premium_gift.promo_screen_fail")
let alertController = textAlertController(context: component.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
controller.present(alertController, in: .window(.root))
}
})
} else {
self.inProgress = false
self.state?.updated(transition: .immediate)
}
})
}
func proceedWithStarGift() {
guard let component = self.component, case let .starGift(starGift) = component.subject, let starsContext = component.context.starsContext, let starsState = starsContext.currentState else {
return return
} }
@ -198,7 +302,8 @@ final class GiftSetupScreenComponent: Component {
return return
} }
let entities = generateChatInputTextEntities(self.textInputState.text) let entities = generateChatInputTextEntities(self.textInputState.text)
let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, peerId: component.peerId, giftId: component.gift.id, text: self.textInputState.text.string, entities: entities) let source: BotPaymentInvoiceSource = .starGift(hideName: self.hideName, peerId: component.peerId, giftId: starGift.id, text: self.textInputState.text.string, entities: entities)
let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source) let inputData = BotCheckoutController.InputData.fetch(context: component.context, source: source)
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in |> `catch` { _ -> Signal<BotCheckoutController.InputData?, NoError> in
@ -246,7 +351,7 @@ final class GiftSetupScreenComponent: Component {
}) })
} }
if starsState.balance < component.gift.price { if starsState.balance < starGift.price {
let _ = (self.optionsPromise.get() let _ = (self.optionsPromise.get()
|> filter { $0 != nil } |> filter { $0 != nil }
|> take(1) |> take(1)
@ -258,7 +363,7 @@ final class GiftSetupScreenComponent: Component {
context: component.context, context: component.context,
starsContext: starsContext, starsContext: starsContext,
options: options ?? [], options: options ?? [],
purpose: .starGift(peerId: component.peerId, requiredStars: component.gift.price), purpose: .starGift(peerId: component.peerId, requiredStars: starGift.price),
completion: { [weak starsContext] stars in completion: { [weak starsContext] stars in
starsContext?.add(balance: stars) starsContext?.add(balance: stars)
Queue.mainQueue().after(0.1) { Queue.mainQueue().after(0.1) {
@ -522,6 +627,23 @@ final class GiftSetupScreenComponent: Component {
} }
} }
let peerName = self.peerMap[component.peerId]?.compactDisplayTitle ?? ""
let introFooter: AnyComponent<Empty>?
switch component.subject {
case .premium:
introFooter = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.Gift_Send_Customize_Info(peerName).string,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
))
case .starGift:
introFooter = nil
}
let introSectionSize = self.introSection.update( let introSectionSize = self.introSection.update(
transition: transition, transition: transition,
component: AnyComponent(ListSectionComponent( component: AnyComponent(ListSectionComponent(
@ -534,7 +656,7 @@ final class GiftSetupScreenComponent: Component {
)), )),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)), )),
footer: nil, footer: introFooter,
items: introSectionItems items: introSectionItems
)), )),
environment: {}, environment: {},
@ -553,6 +675,15 @@ final class GiftSetupScreenComponent: Component {
let listItemParams = ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true) let listItemParams = ListViewItemLayoutParams(width: availableSize.width - sideInset * 2.0, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true)
if let accountPeer = self.peerMap[component.context.account.peerId] { if let accountPeer = self.peerMap[component.context.account.peerId] {
let subject: ChatGiftPreviewItem.Subject
switch component.subject {
case let .premium(product):
let (currency, amount) = product.storeProduct?.priceCurrencyAndAmount ?? ("USD", 1)
subject = .premium(months: product.months, amount: amount, currency: currency)
case let .starGift(gift):
subject = .starGift(gift: gift)
}
let introContentSize = self.introContent.update( let introContentSize = self.introContent.update(
transition: transition, transition: transition,
component: AnyComponent( component: AnyComponent(
@ -569,7 +700,7 @@ final class GiftSetupScreenComponent: Component {
dateTimeFormat: environment.dateTimeFormat, dateTimeFormat: environment.dateTimeFormat,
nameDisplayOrder: presentationData.nameDisplayOrder, nameDisplayOrder: presentationData.nameDisplayOrder,
accountPeer: accountPeer, accountPeer: accountPeer,
gift: component.gift, subject: subject,
text: self.textInputState.text.string, text: self.textInputState.text.string,
entities: generateChatInputTextEntities(self.textInputState.text) entities: generateChatInputTextEntities(self.textInputState.text)
), ),
@ -589,7 +720,7 @@ final class GiftSetupScreenComponent: Component {
} }
} }
let peerName = self.peerMap[component.peerId]?.compactDisplayTitle ?? "" if case .starGift = component.subject {
let hideSectionSize = self.hideSection.update( let hideSectionSize = self.hideSection.update(
transition: transition, transition: transition,
component: AnyComponent(ListSectionComponent( component: AnyComponent(ListSectionComponent(
@ -638,6 +769,7 @@ final class GiftSetupScreenComponent: Component {
transition.setFrame(view: hideSectionView, frame: hideSectionFrame) transition.setFrame(view: hideSectionView, frame: hideSectionFrame)
} }
contentHeight += hideSectionSize.height contentHeight += hideSectionSize.height
}
contentHeight += bottomContentInset contentHeight += bottomContentInset
@ -647,8 +779,18 @@ final class GiftSetupScreenComponent: Component {
if self.starImage == nil || self.starImage?.1 !== environment.theme { if self.starImage == nil || self.starImage?.1 !== environment.theme {
self.starImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: environment.theme.list.itemCheckColors.foregroundColor)!, environment.theme) self.starImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: environment.theme.list.itemCheckColors.foregroundColor)!, environment.theme)
} }
let amountString = presentationStringsFormattedNumber(Int32(component.gift.price), presentationData.dateTimeFormat.groupingSeparator)
let buttonAttributedString = NSMutableAttributedString(string: "\(environment.strings.Gift_Send_Send) # \(amountString)", font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) let buttonString: String
switch component.subject {
case let .premium(product):
let amountString = product.price
buttonString = "\(environment.strings.Gift_Send_Send) \(amountString)"
case let .starGift(starGift):
let amountString = presentationStringsFormattedNumber(Int32(starGift.price), presentationData.dateTimeFormat.groupingSeparator)
buttonString = "\(environment.strings.Gift_Send_Send) # \(amountString)"
}
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.starImage?.0 { if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.starImage?.0 {
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
buttonAttributedString.addAttribute(.foregroundColor, value: environment.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string)) buttonAttributedString.addAttribute(.foregroundColor, value: environment.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
@ -669,7 +811,7 @@ final class GiftSetupScreenComponent: Component {
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
), ),
isEnabled: true, isEnabled: true,
displaysProgress: false, displaysProgress: self.inProgress,
action: { [weak self] in action: { [weak self] in
self?.proceed() self?.proceed()
} }
@ -1039,12 +1181,17 @@ final class GiftSetupScreenComponent: Component {
} }
public final class GiftSetupScreen: ViewControllerComponentContainer { public final class GiftSetupScreen: ViewControllerComponentContainer {
public enum Subject: Equatable {
case premium(PremiumGiftProduct)
case starGift(StarGift)
}
private let context: AccountContext private let context: AccountContext
public init( public init(
context: AccountContext, context: AccountContext,
peerId: EnginePeer.Id, peerId: EnginePeer.Id,
gift: StarGift, subject: Subject,
completion: (() -> Void)? = nil completion: (() -> Void)? = nil
) { ) {
self.context = context self.context = context
@ -1052,7 +1199,7 @@ public final class GiftSetupScreen: ViewControllerComponentContainer {
super.init(context: context, component: GiftSetupScreenComponent( super.init(context: context, component: GiftSetupScreenComponent(
context: context, context: context,
peerId: peerId, peerId: peerId,
gift: gift, subject: subject,
completion: completion completion: completion
), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil) ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil)
@ -1105,3 +1252,31 @@ private struct GiftConfiguration {
} }
} }
} }
public struct PremiumGiftProduct: Equatable {
public let giftOption: CachedPremiumGiftOption
public let storeProduct: InAppPurchaseManager.Product?
public let discount: Int?
public var id: String {
return self.storeProduct?.id ?? (self.giftOption.storeProductId ?? "")
}
public var months: Int32 {
return self.giftOption.months
}
public var price: String {
return self.storeProduct?.price ?? formatCurrencyAmount(self.giftOption.amount, currency: self.giftOption.currency)
}
public var pricePerMonth: String {
return self.storeProduct?.pricePerMonth(Int(self.months)) ?? ""
}
public init(giftOption: CachedPremiumGiftOption, storeProduct: InAppPurchaseManager.Product?, discount: Int?) {
self.giftOption = giftOption
self.storeProduct = storeProduct
self.discount = discount
}
}

View File

@ -235,11 +235,13 @@ private final class GiftViewSheetContent: CombinedComponent {
descriptionText = modifiedString descriptionText = modifiedString
} }
let formattedAmount = presentationStringsFormattedNumber(abs(Int32(stars)), dateTimeFormat.groupingSeparator) var formattedAmount = presentationStringsFormattedNumber(abs(Int32(stars)), dateTimeFormat.groupingSeparator)
if !incoming && stars > 0 {
formattedAmount = "- \(formattedAmount)"
}
let countFont: UIFont = Font.semibold(17.0) let countFont: UIFont = Font.semibold(17.0)
let amountText = formattedAmount let amountText = formattedAmount
let countColor = theme.list.itemDisclosureActions.constructive.fillColor let countColor = incoming ? theme.list.itemDisclosureActions.constructive.fillColor : theme.list.itemDestructiveColor
let title = title.update( let title = title.update(
component: MultilineTextComponent( component: MultilineTextComponent(
@ -344,7 +346,7 @@ private final class GiftViewSheetContent: CombinedComponent {
id: "availability", id: "availability",
title: strings.Gift_View_Availability, title: strings.Gift_View_Availability,
component: AnyComponent( component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "\(remains) of \(limitTotal)", font: tableFont, textColor: tableTextColor))) MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_View_Availability_Of("\(remains)", "\(limitTotal)").string, font: tableFont, textColor: tableTextColor)))
) )
)) ))
} }

View File

@ -5,7 +5,7 @@ import TelegramPresentationData
import AnimationUI import AnimationUI
import Display import Display
class DynamicIslandMaskNode: ASDisplayNode { final class DynamicIslandMaskNode: ASDisplayNode {
var animationNode: AnimationNode? var animationNode: AnimationNode?
var isForum = false { var isForum = false {
@ -39,7 +39,7 @@ class DynamicIslandMaskNode: ASDisplayNode {
} }
} }
class DynamicIslandBlurNode: ASDisplayNode { final class DynamicIslandBlurNode: ASDisplayNode {
private var effectView: UIVisualEffectView? private var effectView: UIVisualEffectView?
private let fadeNode = ASDisplayNode() private let fadeNode = ASDisplayNode()
let gradientNode = ASImageNode() let gradientNode = ASImageNode()

View File

@ -1172,7 +1172,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
strongSelf.push(wallpaperPreviewController) strongSelf.push(wallpaperPreviewController)
return true return true
case let .giftPremium(_, _, duration, _, _): case let .giftPremium(_, _, duration, _, _, _, _):
strongSelf.chatDisplayNode.dismissInput() strongSelf.chatDisplayNode.dismissInput()
let fromPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? strongSelf.context.account.peerId : message.id.peerId let fromPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? strongSelf.context.account.peerId : message.id.peerId
let toPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? message.id.peerId : strongSelf.context.account.peerId let toPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? message.id.peerId : strongSelf.context.account.peerId
@ -1187,7 +1187,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message)) let controller = strongSelf.context.sharedContext.makeStarsGiftScreen(context: strongSelf.context, message: EngineMessage(message))
strongSelf.push(controller) strongSelf.push(controller)
return true return true
case let .giftCode(slug, _, _, _, _, _, _, _, _): case let .giftCode(slug, _, _, _, _, _, _, _, _, _, _):
strongSelf.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id, progress: params.progress) strongSelf.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id, progress: params.progress)
return true return true
case .prizeStars: case .prizeStars: