Update API

This commit is contained in:
Ilya Laktyushin 2022-07-01 23:00:26 +03:00
parent 6933a189a4
commit 100be3217f
20 changed files with 787 additions and 224 deletions

View File

@ -7814,9 +7814,23 @@ Sorry for the inconvenience.";
"Premium.Gift.Years_1" = "%@ Year";
"Premium.Gift.Years_any" = "%@ Years";
"Premium.GiftedTitle" = "Telegram Premium";
"Premium.GiftedTitle.3Month" = "[%@]() has gifted you a 3-month subscription for Telegram Premium";
"Premium.GiftedTitle.6Month" = "[%@]() has gifted you a 6-month subscription for Telegram Premium";
"Premium.GiftedTitle.12Month" = "[%@]() has gifted you a 12-month subscription for Telegram Premium";
"Premium.GiftedDescription" = "You now have access to additional features.";
"Premium.GiftedTitleYou.3Month" = "You gifted [%@]() a 3-month subscription for Telegram Premium";
"Premium.GiftedTitleYou.6Month" = "You gifted [%@]() a 6-month subscription for Telegram Premium";
"Premium.GiftedTitleYou.12Month" = "You gifted [%@]() a 12-month subscription for Telegram Premium";
"Premium.GiftedDescriptionYou" = "They now have access to additional features.";
"SettingsSearch.DeleteAccount.DeleteMyAccount" = " ";
"Notification.PremiumGift.Sent" = "%1$@ sent you a gift for %2$@";
"Notification.PremiumGift.SentYou" = "You sent a gift for %@";
"Notification.PremiumGift.Title" = "Telegram Premium";
"Notification.PremiumGift.Subtitle" = "for %@";
"Notification.PremiumGift.View" = "View";

View File

@ -37,7 +37,7 @@ public final class InAppPurchaseManager: NSObject {
} else if #available(iOS 11.2, *) {
return self.skProduct.subscriptionPeriod != nil
} else {
return !self.id.contains(".monthly")
return self.id.contains(".monthly")
}
}
@ -89,9 +89,11 @@ public final class InAppPurchaseManager: NSObject {
private final class PaymentTransactionContext {
var state: SKPaymentTransactionState?
var targetPeerId: PeerId?
let subscriber: (TransactionState) -> Void
init(subscriber: @escaping (TransactionState) -> Void) {
init(targetPeerId: PeerId?, subscriber: @escaping (TransactionState) -> Void) {
self.targetPeerId = targetPeerId
self.subscriber = subscriber
}
}
@ -120,7 +122,7 @@ public final class InAppPurchaseManager: NSObject {
public init(engine: TelegramEngine) {
self.engine = engine
super.init()
SKPaymentQueue.default().add(self)
@ -169,10 +171,15 @@ public final class InAppPurchaseManager: NSObject {
}
}
public func buyProduct(_ product: Product) -> Signal<PurchaseState, PurchaseError> {
public func buyProduct(_ product: Product, targetPeerId: PeerId? = nil) -> Signal<PurchaseState, PurchaseError> {
if !self.canMakePayments {
return .fail(.cantMakePayments)
}
if !product.isSubscription && targetPeerId == nil {
return .fail(.cantMakePayments)
}
let accountPeerId = "\(self.engine.account.peerId.toInt64())"
Logger.shared.log("InAppPurchaseManager", "Buying: account \(accountPeerId), product \(product.skProduct.productIdentifier), price \(product.price)")
@ -186,7 +193,7 @@ public final class InAppPurchaseManager: NSObject {
let disposable = MetaDisposable()
self.stateQueue.async {
let paymentContext = PaymentTransactionContext(subscriber: { state in
let paymentContext = PaymentTransactionContext(targetPeerId: targetPeerId, subscriber: { state in
switch state {
case let .purchased(transactionId), let .restored(transactionId):
if let transactionId = transactionId {
@ -264,6 +271,9 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
self.stateQueue.async {
let accountPeerId = "\(self.engine.account.peerId.toInt64())"
let paymentContexts = self.paymentContexts
var transactionsToAssign: [SKPaymentTransaction] = []
for transaction in transactions {
if let applicationUsername = transaction.payment.applicationUsername, applicationUsername != accountPeerId {
@ -306,8 +316,19 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
let transactionIds = transactionsToAssign.compactMap({ $0.transactionIdentifier }).joined(separator: ", ")
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), sending receipt for transactions [\(transactionIds)]")
let transaction = transactionsToAssign.first
let purpose: AppStoreTransactionPurpose
if let productIdentifier = transaction?.payment.productIdentifier, let targetPeerId = paymentContexts[productIdentifier]?.targetPeerId {
purpose = .gift(targetPeerId)
} else {
purpose = .subscription
}
let receiptData = getReceiptData() ?? Data()
self.disposableSet.set(
self.engine.payments.sendAppStoreReceipt(receipt: getReceiptData() ?? Data(), purpose: .subscription).start(error: { [weak self] _ in
self.engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: purpose).start(error: { [weak self] _ in
Logger.shared.log("InAppPurchaseManager", "Account \(accountPeerId), transactions [\(transactionIds)] failed to assign")
for transaction in transactions {
self?.stateQueue.async {

View File

@ -752,9 +752,9 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
let updateInProgress: (Bool) -> Void
let present: (ViewController) -> Void
let push: (ViewController) -> Void
let completion: () -> Void
let completion: (Int32) -> Void
init(context: AccountContext, peerId: PeerId, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
init(context: AccountContext, peerId: PeerId, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void, completion: @escaping (Int32) -> Void) {
self.context = context
self.peerId = peerId
self.updateInProgress = updateInProgress
@ -778,7 +778,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
private let peerId: PeerId
private let updateInProgress: (Bool) -> Void
private let present: (ViewController) -> Void
private let completion: () -> Void
private let completion: (Int32) -> Void
var topContentOffset: CGFloat?
var bottomContentOffset: CGFloat?
@ -795,7 +795,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
private var paymentDisposable = MetaDisposable()
private var activationDisposable = MetaDisposable()
init(context: AccountContext, peerId: PeerId, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
init(context: AccountContext, peerId: PeerId, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping (Int32) -> Void) {
self.context = context
self.peerId = peerId
self.updateInProgress = updateInProgress
@ -844,6 +844,18 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
guard let product = self.products?.first(where: { $0.id == self.selectedProductId }) else {
return
}
let duration: Int32
switch product.id {
case "org.telegram.telegramPremium.twelveMonths":
duration = 86400 * 365
case "org.telegram.telegramPremium.sixMonths":
duration = 86400 * 180
case "org.telegram.telegramPremium.threeMonths":
duration = 86400 * 90
default:
duration = 0
}
// addAppLogEvent(postbox: self.context.account.postbox, type: "premium.promo_screen_accept")
@ -851,90 +863,79 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
self.updateInProgress(true)
self.updated(transition: .immediate)
let _ = (self.context.engine.payments.canPurchasePremium()
|> deliverOnMainQueue).start(next: { [weak self] available in
self.paymentDisposable.set((inAppPurchaseManager.buyProduct(product, targetPeerId: self.peerId)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self, case .purchased = status {
strongSelf.activationDisposable.set((strongSelf.context.account.postbox.peerView(id: strongSelf.context.account.peerId)
|> castError(AssignAppStoreTransactionError.self)
|> take(until: { view in
if let peer = view.peers[view.peerId], peer.isPremium {
return SignalTakeAction(passthrough: false, complete: true)
} else {
return SignalTakeAction(passthrough: false, complete: false)
}
})
|> mapToSignal { _ -> Signal<Never, AssignAppStoreTransactionError> in
return .never()
}
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .fail(.timeout))
|> deliverOnMainQueue).start(error: { [weak self] _ in
if let strongSelf = self {
strongSelf.inProgress = false
strongSelf.updateInProgress(false)
strongSelf.updated(transition: .immediate)
// strongSelf.completion(duration)
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
strongSelf.present(alertController)
}
}, completed: { [weak self] in
if let strongSelf = self {
let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start()
strongSelf.inProgress = false
strongSelf.updateInProgress(false)
strongSelf.updated(transition: .easeInOut(duration: 0.25))
strongSelf.completion(duration)
}
}))
}
}, error: { [weak self] error in
if let strongSelf = self {
if available {
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(product)
|> deliverOnMainQueue).start(next: { [weak self] status in
if let strongSelf = self, case .purchased = status {
strongSelf.activationDisposable.set((strongSelf.context.account.postbox.peerView(id: strongSelf.context.account.peerId)
|> castError(AssignAppStoreTransactionError.self)
|> take(until: { view in
if let peer = view.peers[view.peerId], peer.isPremium {
return SignalTakeAction(passthrough: false, complete: true)
} else {
return SignalTakeAction(passthrough: false, complete: false)
}
})
|> mapToSignal { _ -> Signal<Never, AssignAppStoreTransactionError> in
return .never()
}
|> timeout(15.0, queue: Queue.mainQueue(), alternate: .fail(.timeout))
|> deliverOnMainQueue).start(error: { [weak self] _ in
if let strongSelf = self {
strongSelf.inProgress = false
strongSelf.updateInProgress(false)
strongSelf.updated(transition: .immediate)
strongSelf.completion()
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let errorText = presentationData.strings.Premium_Purchase_ErrorUnknown
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
strongSelf.present(alertController)
}
}, completed: { [weak self] in
if let strongSelf = self {
let _ = updatePremiumPromoConfigurationOnce(account: strongSelf.context.account).start()
strongSelf.inProgress = false
strongSelf.updateInProgress(false)
strongSelf.updated(transition: .easeInOut(duration: 0.25))
strongSelf.completion()
}
}))
}
}, error: { [weak self] error in
if let strongSelf = self {
strongSelf.inProgress = false
strongSelf.updateInProgress(false)
strongSelf.updated(transition: .immediate)
strongSelf.inProgress = false
strongSelf.updateInProgress(false)
strongSelf.updated(transition: .immediate)
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
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 .cancelled:
break
}
if let errorText = errorText {
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
strongSelf.present(alertController)
}
}
}))
} else {
strongSelf.inProgress = false
strongSelf.updateInProgress(false)
strongSelf.updated(transition: .immediate)
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
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 .cancelled:
break
}
if let errorText = errorText {
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail")
let alertController = textAlertController(context: strongSelf.context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
strongSelf.present(alertController)
}
}
})
}))
}
func updateIsFocused(_ isFocused: Bool) {
@ -1216,7 +1217,7 @@ public final class PremiumGiftScreen: ViewControllerComponentContainer {
var updateInProgressImpl: ((Bool) -> Void)?
var pushImpl: ((ViewController) -> Void)?
// var presentImpl: ((ViewController) -> Void)?
var completionImpl: (() -> Void)?
var completionImpl: ((Int32) -> Void)?
super.init(context: context, component: PremiumGiftScreenComponent(
context: context,
peerId: peerId,
@ -1229,8 +1230,8 @@ public final class PremiumGiftScreen: ViewControllerComponentContainer {
push: { c in
pushImpl?(c)
},
completion: {
completionImpl?()
completion: { duration in
completionImpl?(duration)
}
), navigationBarAppearance: .transparent)
@ -1256,9 +1257,15 @@ public final class PremiumGiftScreen: ViewControllerComponentContainer {
self?.push(c)
}
completionImpl = { [weak self] in
completionImpl = { [weak self] duration in
if let strongSelf = self {
strongSelf.view.addSubview(ConfettiView(frame: strongSelf.view.bounds))
let navigationController = strongSelf.navigationController
strongSelf.dismiss()
let introController = PremiumIntroScreen(context: context, source: .gift(from: context.account.peerId, to: peerId, duration: duration))
navigationController?.pushViewController(introController, animated: true)
Queue.mainQueue().after(0.1, {
introController.view.addSubview(ConfettiView(frame: introController.view.bounds))
})
}
}
}

View File

@ -38,8 +38,9 @@ public enum PremiumSource: Equatable {
case appIcons
case deeplink(String?)
case profile(PeerId)
case gift(from: PeerId, to: PeerId, duration: Int32)
var identifier: String {
var identifier: String? {
switch self {
case .settings:
return "settings"
@ -73,6 +74,8 @@ public enum PremiumSource: Equatable {
return "double_limits__about"
case let .profile(id):
return "profile__\(id.id._internalGetInt64Value())"
case .gift:
return nil
case let .deeplink(reference):
if let reference = reference {
return "deeplink_\(reference)"
@ -695,22 +698,24 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
strongSelf.promoConfiguration = promoConfiguration
strongSelf.updated(transition: .immediate)
var jsonString: String = "{"
jsonString += "\"source\": \"\(source.identifier)\","
jsonString += "\"data\": {\"premium_promo_order\":["
var isFirst = true
for perk in strongSelf.configuration.perks {
if !isFirst {
jsonString += ","
if let identifier = source.identifier {
var jsonString: String = "{"
jsonString += "\"source\": \"\(identifier)\","
jsonString += "\"data\": {\"premium_promo_order\":["
var isFirst = true
for perk in strongSelf.configuration.perks {
if !isFirst {
jsonString += ","
}
isFirst = false
jsonString += "\"\(perk.identifier)\""
}
jsonString += "]}}"
if let data = jsonString.data(using: .utf8), let json = JSON(data: data) {
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_show", data: json)
}
isFirst = false
jsonString += "\"\(perk.identifier)\""
}
jsonString += "]}}"
if let data = jsonString.data(using: .utf8), let json = JSON(data: data) {
addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_show", data: json)
}
for (_, video) in promoConfiguration.videos {
@ -815,7 +820,15 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let textString: String
if let _ = context.component.otherPeerName {
textString = strings.Premium_PersonalDescription
if case let .gift(fromId, _, _) = context.component.source {
if fromId == context.component.context.account.peerId {
textString = strings.Premium_GiftedDescriptionYou
} else {
textString = strings.Premium_GiftedDescription
}
} else {
textString = strings.Premium_PersonalDescription
}
} else if context.component.isPremium == true {
textString = strings.Premium_SubscribedDescription
} else {
@ -1036,8 +1049,17 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
return (TelegramTextAttributes.URL, contents)
})
var isGiftView = false
if case let .gift(fromId, _, _) = context.component.source {
if fromId == context.component.context.account.peerId {
isGiftView = true
}
}
let termsString: MultilineTextComponent.TextContent
if let promoConfiguration = context.state.promoConfiguration {
if isGiftView {
termsString = .plain(NSAttributedString())
} else if let promoConfiguration = context.state.promoConfiguration {
let attributedString = stringWithAppliedEntities(promoConfiguration.status, entities: promoConfiguration.statusEntities, baseColor: termsTextColor, linkColor: environment.theme.list.itemAccentColor, baseFont: termsFont, linkFont: termsFont, boldFont: boldTermsFont, italicFont: italicTermsFont, boldItalicFont: boldItalicTermsFont, fixedFont: monospaceTermsFont, blockQuoteFont: termsFont)
termsString = .plain(attributedString)
} else {
@ -1229,7 +1251,14 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
}
let otherPeerName: Signal<String?, NoError>
if case let .profile(peerId) = source {
if case let .gift(fromPeerId, toPeerId, _) = source {
let otherPeerId = fromPeerId != context.account.peerId ? fromPeerId : toPeerId
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: otherPeerId))
|> map { peer -> String? in
return peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
}
} else if case let .profile(peerId) = source {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
otherPeerName = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> map { peer -> String? in
@ -1418,7 +1447,9 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
)
let titleString: String
if state.isPremium == true {
if case .gift = context.component.source {
titleString = environment.strings.Premium_GiftedTitle
} else if state.isPremium == true {
titleString = environment.strings.Premium_SubscribedTitle
} else {
titleString = environment.strings.Premium_Title
@ -1444,9 +1475,43 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: accentColor), linkAttribute: { _ in
return nil
})
let secondaryTitleText: String
if let otherPeerName = state.otherPeerName {
if case .profile = context.component.source {
secondaryTitleText = environment.strings.Premium_PersonalTitle(otherPeerName).string
} else if case let .gift(fromPeerId, _, duration) = context.component.source {
if fromPeerId == context.component.context.account.peerId {
if duration >= 86400 * 365 {
secondaryTitleText = environment.strings.Premium_GiftedTitleYou_12Month(otherPeerName).string
} else if duration >= 86400 * 180 {
secondaryTitleText = environment.strings.Premium_GiftedTitleYou_6Month(otherPeerName).string
} else if duration >= 86400 * 90 {
secondaryTitleText = environment.strings.Premium_GiftedTitleYou_3Month(otherPeerName).string
} else {
secondaryTitleText = ""
}
} else {
if duration >= 86400 * 365 {
secondaryTitleText = environment.strings.Premium_GiftedTitle_12Month(otherPeerName).string
} else if duration >= 86400 * 180 {
secondaryTitleText = environment.strings.Premium_GiftedTitle_6Month(otherPeerName).string
} else if duration >= 86400 * 90 {
secondaryTitleText = environment.strings.Premium_GiftedTitle_3Month(otherPeerName).string
} else {
secondaryTitleText = ""
}
}
} else {
secondaryTitleText = ""
}
} else {
secondaryTitleText = ""
}
let secondaryTitle = secondaryTitle.update(
component: MultilineTextComponent(
text: .markdown(text: state.otherPeerName.flatMap({ environment.strings.Premium_PersonalTitle($0).string }) ?? "", attributes: markdownAttributes),
text: .markdown(text: secondaryTitleText, attributes: markdownAttributes),
horizontalAlignment: .center,
truncationType: .end,
maximumNumberOfLines: 2,
@ -1557,8 +1622,15 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
.scale(titleScale)
.opacity(max(0.0, 1.0 - titleAlpha * 1.8))
)
var isGiftView = false
if case let .gift(fromId, _, _) = context.component.source {
if fromId == context.component.context.account.peerId {
isGiftView = true
}
}
if state.isPremium == true {
if state.isPremium == true || isGiftView {
} else {
let sideInset: CGFloat = 16.0

View File

@ -0,0 +1,78 @@
import Foundation
import AsyncDisplayKit
import Display
import SwiftSignalKit
import AppBundle
private let starsCount = 9
public final class PremiumStarsNode: ASDisplayNode {
private let starNodes: [ASImageNode]
private var timer: SwiftSignalKit.Timer?
public override init() {
let image = UIImage(bundleImageName: "Premium/ReactionsStar")
var starNodes: [ASImageNode] = []
for _ in 0 ..< starsCount {
let node = ASImageNode()
node.isLayerBacked = true
node.alpha = 0.0
node.image = image
node.displaysAsynchronously = false
starNodes.append(node)
}
self.starNodes = starNodes
super.init()
for node in starNodes {
self.addSubnode(node)
}
Queue.mainQueue().async {
self.setup(firstTime: true)
self.timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
self?.setup()
}, queue: Queue.mainQueue())
self.timer?.start()
}
}
deinit {
self.timer?.invalidate()
}
func setup(firstTime: Bool = false) {
let size: CGSize
if self.frame.width > 0.0 {
size = self.frame.size
} else {
size = CGSize(width: 32.0, height: 32.0)
}
let starSize = CGSize(width: 6.0, height: 8.0)
for node in self.starNodes {
if node.layer.animation(forKey: "transform.scale") == nil && node.layer.animation(forKey: "opacity") == nil {
let x = CGFloat.random(in: 0 ..< size.width)
let y = CGFloat.random(in: 0 ..< size.width)
let randomTargetScale = CGFloat.random(in: 0.8 ..< 1.0)
node.bounds = CGRect(origin: .zero, size: starSize)
node.position = CGPoint(x: x, y: y)
node.alpha = 1.0
let duration = CGFloat.random(in: 0.4 ..< 0.65)
let delay = firstTime ? CGFloat.random(in: 0.0 ..< 0.25) : 0.0
node.layer.animateScale(from: 0.001, to: randomTargetScale, duration: duration, delay: delay, removeOnCompletion: false, completion: { [weak self, weak node] _ in
let duration = CGFloat.random(in: 0.3 ..< 0.35)
node?.alpha = 0.0
node?.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak self, weak node] _ in
node?.layer.removeAllAnimations()
self?.setup()
})
})
}
}
}
}

View File

@ -323,71 +323,6 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
}
}
private let starsCount = 7
private final class StarsNode: ASDisplayNode {
private let starNodes: [ASImageNode]
private var timer: SwiftSignalKit.Timer?
override init() {
let image = UIImage(bundleImageName: "Premium/ReactionsStar")
var starNodes: [ASImageNode] = []
for _ in 0 ..< starsCount {
let node = ASImageNode()
node.alpha = 0.0
node.image = image
node.displaysAsynchronously = false
starNodes.append(node)
}
self.starNodes = starNodes
super.init()
for node in starNodes {
self.addSubnode(node)
}
self.setup(firstTime: true)
self.timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
self?.setup()
}, queue: Queue.mainQueue())
self.timer?.start()
}
deinit {
self.timer?.invalidate()
}
func setup(firstTime: Bool = false) {
let size = CGSize(width: 32.0, height: 32.0)
let starSize = CGSize(width: 6.0, height: 8.0)
for node in self.starNodes {
if node.layer.animation(forKey: "transform.scale") == nil && node.layer.animation(forKey: "opacity") == nil {
let x = CGFloat.random(in: 0 ..< size.width)
let y = CGFloat.random(in: 0 ..< size.width)
let randomTargetScale = CGFloat.random(in: 0.8 ..< 1.0)
node.bounds = CGRect(origin: .zero, size: starSize)
node.position = CGPoint(x: x, y: y)
node.alpha = 1.0
let duration = CGFloat.random(in: 0.4 ..< 0.65)
let delay = firstTime ? CGFloat.random(in: 0.0 ..< 0.25) : 0.0
node.layer.animateScale(from: 0.001, to: randomTargetScale, duration: duration, delay: delay, removeOnCompletion: false, completion: { [weak self, weak node] _ in
let duration = CGFloat.random(in: 0.3 ..< 0.35)
node?.alpha = 0.0
node?.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false, completion: { [weak self, weak node] _ in
node?.layer.removeAllAnimations()
self?.setup()
})
})
}
}
}
}
final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
var isExtracted: Bool = false
@ -395,7 +330,7 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
private let backgroundMaskNode: ASImageNode
private let backgroundOverlayNode: ASImageNode
private let imageNode: ASImageNode
private var starsNode: StarsNode?
private var starsNode: PremiumStarsNode?
private let maskContainerNode: ASDisplayNode
private let maskImageNode: ASImageNode
@ -459,7 +394,7 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
self.view.insertSubview(backgroundView, at: 0)
self.backgroundView = backgroundView
let starsNode = StarsNode()
let starsNode = PremiumStarsNode()
starsNode.frame = CGRect(origin: .zero, size: CGSize(width: 32.0, height: 32.0))
self.backgroundView?.contentView.addSubview(starsNode.view)
self.starsNode = starsNode

View File

@ -307,6 +307,7 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat
}
}
var secondaryActionDisabled = false
let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData,
peers,
@ -339,7 +340,10 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat
let footerItem = DeleteAccountFooterItem(theme: presentationData.theme, title: buttonTitle, secondaryTitle: presentationData.strings.DeleteAccount_Continue, action: {
cancelImpl()
}, secondaryAction: {
proceedImpl?()
if !secondaryActionDisabled {
secondaryActionDisabled = true
proceedImpl?()
}
})
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.DeleteAccount_DeleteMyAccountTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
@ -462,7 +466,7 @@ func deleteAccountDataController(context: AccountContext, mode: DeleteAccountDat
let presentGlobalController = context.sharedContext.presentGlobalController
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: true).start(completed: {
Queue.mainQueue().after(0.1) {
presentGlobalController(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.DeleteAccount_Success), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
presentGlobalController(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.DeleteAccount_Success), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
}
})
})

View File

@ -348,7 +348,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) }
dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) }
dict[1147243133] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) }
dict[-764193027] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumSubscription($0) }
dict[-1502273946] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumSubscription($0) }
dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) }
dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) }
dict[-1881255857] = { return Api.InputThemeSettings.parse_inputThemeSettings($0) }

View File

@ -6229,13 +6229,12 @@ public extension Api.functions.messages {
}
}
public extension Api.functions.payments {
static func assignAppStoreTransaction(flags: Int32, receipt: Buffer, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
static func assignAppStoreTransaction(receipt: Buffer, purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(296120783)
serializeInt32(flags, buffer: buffer, boxed: false)
buffer.appendInt32(-2131921795)
serializeBytes(receipt, buffer: buffer, boxed: false)
purpose.serialize(buffer, true)
return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("flags", String(describing: flags)), ("receipt", String(describing: receipt)), ("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
return (FunctionDescription(name: "payments.assignAppStoreTransaction", parameters: [("receipt", String(describing: receipt)), ("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {

View File

@ -457,7 +457,7 @@ public extension Api {
public extension Api {
enum InputStorePaymentPurpose: TypeConstructorDescription {
case inputStorePaymentGiftPremium(userId: Api.InputUser)
case inputStorePaymentPremiumSubscription
case inputStorePaymentPremiumSubscription(flags: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -467,11 +467,11 @@ public extension Api {
}
userId.serialize(buffer, true)
break
case .inputStorePaymentPremiumSubscription:
case .inputStorePaymentPremiumSubscription(let flags):
if boxed {
buffer.appendInt32(-764193027)
buffer.appendInt32(-1502273946)
}
serializeInt32(flags, buffer: buffer, boxed: false)
break
}
}
@ -480,8 +480,8 @@ public extension Api {
switch self {
case .inputStorePaymentGiftPremium(let userId):
return ("inputStorePaymentGiftPremium", [("userId", String(describing: userId))])
case .inputStorePaymentPremiumSubscription:
return ("inputStorePaymentPremiumSubscription", [])
case .inputStorePaymentPremiumSubscription(let flags):
return ("inputStorePaymentPremiumSubscription", [("flags", String(describing: flags))])
}
}
@ -499,7 +499,15 @@ public extension Api {
}
}
public static func parse_inputStorePaymentPremiumSubscription(_ reader: BufferReader) -> InputStorePaymentPurpose? {
return Api.InputStorePaymentPurpose.inputStorePaymentPremiumSubscription
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.InputStorePaymentPurpose.inputStorePaymentPremiumSubscription(flags: _1!)
}
else {
return nil
}
}
}

View File

@ -64,6 +64,7 @@ public final class CachedUserData: CachedPeerData {
public let autoremoveTimeout: CachedPeerAutoremoveTimeout
public let themeEmoticon: String?
public let photo: TelegramMediaImage?
public let giftPremiumUrl: String?
public let peerIds: Set<PeerId>
public let messageIds: Set<MessageId>
@ -84,11 +85,12 @@ public final class CachedUserData: CachedPeerData {
self.autoremoveTimeout = .unknown
self.themeEmoticon = nil
self.photo = nil
self.giftPremiumUrl = nil
self.peerIds = Set()
self.messageIds = Set()
}
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: TelegramMediaImage?) {
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: TelegramMediaImage?, giftPremiumUrl: String?) {
self.about = about
self.botInfo = botInfo
self.peerStatusSettings = peerStatusSettings
@ -103,6 +105,7 @@ public final class CachedUserData: CachedPeerData {
self.autoremoveTimeout = autoremoveTimeout
self.themeEmoticon = themeEmoticon
self.photo = photo
self.giftPremiumUrl = giftPremiumUrl
self.peerIds = Set<PeerId>()
@ -144,6 +147,8 @@ public final class CachedUserData: CachedPeerData {
self.photo = nil
}
self.giftPremiumUrl = decoder.decodeOptionalStringForKey("gpu")
self.peerIds = Set<PeerId>()
var messageIds = Set<MessageId>()
@ -197,6 +202,12 @@ public final class CachedUserData: CachedPeerData {
} else {
encoder.encodeNil(forKey: "ph")
}
if let giftPremiumUrl = self.giftPremiumUrl, !giftPremiumUrl.isEmpty {
encoder.encodeString(giftPremiumUrl, forKey: "gpu")
} else {
encoder.encodeNil(forKey: "gpu")
}
}
public func isEqual(to: CachedPeerData) -> Bool {
@ -215,58 +226,62 @@ public final class CachedUserData: CachedPeerData {
}
public func withUpdatedAbout(_ about: String?) -> CachedUserData {
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo)
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, giftPremiumUrl: self.giftPremiumUrl)
}
public func withUpdatedGiftPremiumUrl(_ giftPremiumUrl: String?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, giftPremiumUrl: giftPremiumUrl)
}
}

View File

@ -17,15 +17,14 @@ public enum AppStoreTransactionPurpose {
}
func _internal_sendAppStoreReceipt(account: Account, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
var flags: Int32 = 0
if case .restore = purpose {
flags |= (1 << 0)
}
var purposeSignal: Signal<Api.InputStorePaymentPurpose, NoError>
switch purpose {
case .subscription, .restore:
purposeSignal = .single(.inputStorePaymentPremiumSubscription)
var flags: Int32 = 0
if case .restore = purpose {
flags |= (1 << 0)
}
purposeSignal = .single(.inputStorePaymentPremiumSubscription(flags: flags))
case let .gift(peerId):
purposeSignal = account.postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
@ -40,7 +39,7 @@ func _internal_sendAppStoreReceipt(account: Account, receipt: Data, purpose: App
return purposeSignal
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
return account.network.request(Api.functions.payments.assignAppStoreTransaction(flags: flags, receipt: Buffer(data: receipt), purpose: purpose))
return account.network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose))
|> mapError { error -> AssignAppStoreTransactionError in
if error.errorCode == 406 {
return .serverProvided

View File

@ -234,7 +234,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
previous = CachedUserData()
}
switch fullUser {
case let .userFull(userFullFlags, _, userFullAbout, userFullSettings, profilePhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, _):
case let .userFull(userFullFlags, _, userFullAbout, userFullSettings, profilePhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, giftPremiumUrl):
let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:))
let isBlocked = (userFullFlags & (1 << 0)) != 0
let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0
@ -256,6 +256,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
.withUpdatedAutoremoveTimeout(autoremoveTimeout)
.withUpdatedThemeEmoticon(userFullThemeEmoticon)
.withUpdatedPhoto(photo)
.withUpdatedGiftPremiumUrl(giftPremiumUrl)
}
})
}

View File

@ -652,8 +652,15 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
case let .webViewData(text):
attributedString = NSAttributedString(string: strings.Notification_WebAppSentData(text).string, font: titleFont, textColor: primaryTextColor)
case .giftPremium:
attributedString = nil
case let .giftPremium(currency, amount, _):
let price = formatCurrencyAmount(amount, currency: currency)
if message.author?.id == accountPeerId {
attributedString = addAttributesToStringWithRanges(strings.Notification_PremiumGift_SentYou(price)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
} else {
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
attributes[1] = boldAttributes
attributedString = addAttributesToStringWithRanges(strings.Notification_PremiumGift_Sent(authorName, price)._tuple, body: bodyAttributes, argumentAttributes: attributes)
}
case .unknown:
attributedString = nil
}

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "gift.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -770,6 +770,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case .setChatTheme:
strongSelf.presentThemeSelection()
return true
case let .giftPremium(_, _, duration):
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 controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration))
strongSelf.push(controller)
return true
default:
break
}

View File

@ -99,6 +99,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
isAction = true
if case .phoneCall = action.action {
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
} else if case .giftPremium = action.action {
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
} else {
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
}

View File

@ -0,0 +1,374 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import AccountContext
import TelegramPresentationData
import TelegramUIPreferences
import TextFormat
import LocalizedPeerData
import UrlEscaping
import TelegramStringFormatting
import WallpaperBackgroundNode
import ReactionSelectionNode
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: EngineMessage(message), accountPeerId: accountPeerId, forChatList: false)
}
class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private let labelNode: TextNode
private var backgroundNode: WallpaperBubbleBackgroundNode?
private var backgroundColorNode: ASDisplayNode
private let backgroundMaskNode: ASImageNode
private var linkHighlightingNode: LinkHighlightingNode?
private let mediaBackgroundNode: NavigationBackgroundNode
private let titleNode: TextNode
private let subtitleNode: TextNode
private let giftNode: ASImageNode
private let buttonNode: HighlightTrackingButtonNode
private let buttonStarsNode: PremiumStarsNode
private let buttonTitleNode: TextNode
private var cachedMaskBackgroundImage: (CGPoint, UIImage, [CGRect])?
private var absoluteRect: (CGRect, CGSize)?
required init() {
self.labelNode = TextNode()
self.labelNode.isUserInteractionEnabled = false
self.labelNode.displaysAsynchronously = false
self.backgroundColorNode = ASDisplayNode()
self.backgroundMaskNode = ASImageNode()
self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear)
self.mediaBackgroundNode.clipsToBounds = true
self.mediaBackgroundNode.cornerRadius = 24.0
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.subtitleNode = TextNode()
self.subtitleNode.isUserInteractionEnabled = false
self.subtitleNode.displaysAsynchronously = false
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true
self.buttonNode.cornerRadius = 17.0
self.giftNode = ASImageNode()
self.giftNode.isUserInteractionEnabled = false
self.giftNode.displaysAsynchronously = false
self.buttonStarsNode = PremiumStarsNode()
self.buttonTitleNode = TextNode()
self.buttonTitleNode.isUserInteractionEnabled = false
self.buttonTitleNode.displaysAsynchronously = false
super.init()
self.addSubnode(self.labelNode)
self.addSubnode(self.mediaBackgroundNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode)
self.addSubnode(self.giftNode)
self.addSubnode(self.buttonNode)
self.buttonNode.addSubnode(self.buttonStarsNode)
self.addSubnode(self.buttonTitleNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity")
strongSelf.buttonNode.alpha = 0.4
strongSelf.buttonTitleNode.layer.removeAnimation(forKey: "opacity")
strongSelf.buttonTitleNode.alpha = 0.4
} else {
strongSelf.buttonNode.alpha = 1.0
strongSelf.buttonNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.buttonTitleNode.alpha = 1.0
strongSelf.buttonTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func buttonPressed() {
guard let item = self.item else {
return
}
let _ = item.controllerInteraction.openMessage(item.message, .default)
}
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode)
let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage
return { item, layoutConstants, _, _, _ in
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
let imageSize = CGSize(width: 220.0, height: 210.0)
let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: item.message, accountPeerId: item.context.account.peerId)
let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText
var duration: String = ""
for media in item.message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case let .giftPremium(_, _, durationValue):
duration = item.presentationData.strings.Notification_PremiumGift_Subtitle(timeIntervalString(strings: item.presentationData.strings, value: durationValue)).string
default:
break
}
}
}
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_Title, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: duration, font: Font.regular(13.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_PremiumGift_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: imageSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
var labelRects = labelLayout.linesRects()
if labelRects.count > 1 {
let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })
for i in 0 ..< sortedIndices.count {
let index = sortedIndices[i]
for j in -1 ... 1 {
if j != 0 && index + j >= 0 && index + j < sortedIndices.count {
if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 {
labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width)
labelRects[index].size.width = labelRects[index + j].size.width
}
}
}
}
}
for i in 0 ..< labelRects.count {
labelRects[i] = labelRects[i].insetBy(dx: -6.0, dy: floor((labelRects[i].height - 20.0) / 2.0))
labelRects[i].size.height = 20.0
labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0)
}
let backgroundMaskImage: (CGPoint, UIImage)?
var backgroundMaskUpdated = false
if let (currentOffset, currentImage, currentRects) = cachedMaskBackgroundImage, currentRects == labelRects {
backgroundMaskImage = (currentOffset, currentImage)
} else {
backgroundMaskImage = LinkHighlightingNode.generateImage(color: .black, inset: 0.0, innerRadius: 10.0, outerRadius: 10.0, rects: labelRects)
backgroundMaskUpdated = true
}
let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + imageSize.height + 18.0)
return (backgroundSize.width, { boundingWidth in
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in
if let strongSelf = self {
strongSelf.item = item
strongSelf.backgroundColorNode.backgroundColor = selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 16.0), size: imageSize)
let mediaBackgroundFrame = imageFrame.insetBy(dx: -2.0, dy: -2.0)
strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame
strongSelf.mediaBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate)
strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate)
strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)
strongSelf.giftNode.image = UIImage(bundleImageName: "Components/Gift")
if let image = strongSelf.giftNode.image {
strongSelf.giftNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - image.size.width) / 2.0), y: mediaBackgroundFrame.minY + 14.0), size: image.size)
}
let _ = labelApply()
let _ = titleApply()
let _ = subtitleApply()
let _ = buttonTitleApply()
let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: 2.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 121.0), size: titleLayout.size)
strongSelf.titleNode.frame = titleFrame
let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY - 1.0), size: subtitleLayout.size)
strongSelf.subtitleNode.frame = subtitleFrame
let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: subtitleFrame.maxY + 18.0), size: buttonTitleLayout.size)
strongSelf.buttonTitleNode.frame = buttonTitleFrame
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.buttonStarsNode.frame = CGRect(origin: .zero, size: buttonSize)
let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0)
if let (offset, image) = backgroundMaskImage {
if strongSelf.backgroundNode == nil {
if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
strongSelf.backgroundNode = backgroundNode
backgroundNode.addSubnode(strongSelf.backgroundColorNode)
strongSelf.insertSubnode(backgroundNode, at: 0)
}
}
if backgroundMaskUpdated, let backgroundNode = strongSelf.backgroundNode {
if labelRects.count == 1 {
backgroundNode.clipsToBounds = true
backgroundNode.cornerRadius = labelRects[0].height / 2.0
backgroundNode.view.mask = nil
} else {
backgroundNode.clipsToBounds = false
backgroundNode.cornerRadius = 0.0
backgroundNode.view.mask = strongSelf.backgroundMaskNode.view
}
}
if let backgroundNode = strongSelf.backgroundNode {
backgroundNode.frame = CGRect(origin: CGPoint(x: baseBackgroundFrame.minX + offset.x, y: baseBackgroundFrame.minY + offset.y), size: image.size)
if let (rect, size) = strongSelf.absoluteRect {
strongSelf.updateAbsoluteRect(rect, within: size)
}
}
strongSelf.backgroundMaskNode.image = image
strongSelf.backgroundMaskNode.frame = CGRect(origin: CGPoint(), size: image.size)
strongSelf.backgroundColorNode.frame = CGRect(origin: CGPoint(), size: image.size)
strongSelf.cachedMaskBackgroundImage = (offset, image, labelRects)
}
}
})
})
})
}
}
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
self.absoluteRect = (rect, containerSize)
if let backgroundNode = self.backgroundNode {
var backgroundFrame = backgroundNode.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundNode.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
}
override func applyAbsoluteOffset(value: CGPoint, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) {
if let backgroundNode = self.backgroundNode {
backgroundNode.offset(value: value, animationCurve: animationCurve, duration: duration)
}
}
override func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) {
if let backgroundNode = self.backgroundNode {
backgroundNode.offsetSpring(value: value, duration: duration, damping: damping)
}
}
override func updateTouchesAtPoint(_ point: CGPoint?) {
if let item = self.item {
var rects: [(CGRect, CGRect)]?
let textNodeFrame = self.labelNode.frame
if let point = point {
if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)) {
let possibleNames: [String] = [
TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention,
TelegramTextAttributes.PeerTextMention,
TelegramTextAttributes.BotCommand,
TelegramTextAttributes.Hashtag
]
for name in possibleNames {
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
rects = self.labelNode.lineAndAttributeRects(name: name, at: index)
break
}
}
}
}
if let rects = rects {
var mappedRects: [CGRect] = []
for i in 0 ..< rects.count {
let lineRect = rects[i].0
var itemRect = rects[i].1
itemRect.origin.x = floor((textNodeFrame.size.width - lineRect.width) / 2.0) + itemRect.origin.x
mappedRects.append(itemRect)
}
let linkHighlightingNode: LinkHighlightingNode
if let current = self.linkHighlightingNode {
linkHighlightingNode = current
} else {
let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
linkHighlightingNode = LinkHighlightingNode(color: serviceColor.linkHighlight)
linkHighlightingNode.inset = 2.5
self.linkHighlightingNode = linkHighlightingNode
self.insertSubnode(linkHighlightingNode, belowSubnode: self.labelNode)
}
linkHighlightingNode.frame = self.labelNode.frame.offsetBy(dx: 0.0, dy: 1.5)
linkHighlightingNode.updateRects(mappedRects)
} else if let linkHighlightingNode = self.linkHighlightingNode {
self.linkHighlightingNode = nil
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
linkHighlightingNode?.removeFromSupernode()
})
}
}
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.labelNode.frame
if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)), gesture == .tap {
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true
if let (attributeText, fullText) = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
}
return .url(url: url, concealed: concealed)
} else if let peerMention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
return .peerMention(peerMention.peerId, peerMention.mention)
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
return .textMention(peerName)
} else if let botCommand = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
return .botCommand(botCommand)
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
return .hashtag(hashtag.peerName, hashtag.hashtag)
}
}
if let backgroundNode = self.backgroundNode, backgroundNode.frame.contains(point) {
return .openMessage
} else if self.mediaBackgroundNode.frame.contains(point) {
return .openMessage
} else {
return .none
}
}
}

View File

@ -4059,7 +4059,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
})))
}
if !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport) {
if !user.isDeleted && user.botInfo == nil && !user.flags.contains(.isSupport), let cachedData = data.cachedData as? CachedUserData, let _ = cachedData.giftPremiumUrl {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_GiftPremium, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in