This commit is contained in:
Isaac 2025-03-13 00:35:11 +01:00
commit 6faefd876a
143 changed files with 3629 additions and 755 deletions

View file

@ -13573,6 +13573,7 @@ Sorry for the inconvenience.";
"FolderLinkPreview.ToastFolderAddedTitleV2" = "Folder {folder} Added";
"Stars.Purchase.UpgradeStarGiftInfo" = "Buy Stars to upgrade your gift into a unique collectible.";
"Stars.Purchase.TransferStarGiftInfo" = "Buy Stars to transfer ownership of your unique collectible.";
"Gift.Send.Upgrade" = "Make Unique for %@";
"Gift.Send.Upgrade.Info" = "Enable this to let %1$@ turn your gift into a unique collectible. [Learn More >]()";
@ -14002,6 +14003,16 @@ Sorry for the inconvenience.";
"Share.VideoStartAt" = "Start at %@";
"SendStarReactions.SubtitleFrom" = "from %@";
"Notification.PaidMessageRefund.Stars_1" = "%@ Star";
"Notification.PaidMessageRefund.Stars_any" = "%@ Stars";
"Notification.PaidMessageRefund" = "%1$@ refunded you %2$@";
"Notification.PaidMessageRefundYou" = "You refunded %1$@ to %2$@";
"Notification.PaidMessagePriceChanged.Stars_1" = "%@ Star";
"Notification.PaidMessagePriceChanged.Stars_any" = "%@ Stars";
"Notification.PaidMessagePriceChanged" = "%1$@ changed price to %2$@ per message";
"Notification.PaidMessagePriceChangedYou" = "You changed price to %1$@ per message";
"Premium.MaxExpiringStoriesTextNumberFormat_1" = "**%d** story";
"Premium.MaxExpiringStoriesTextNumberFormat_any" = "**%d** stories";
"Premium.MaxExpiringStoriesTextPremiumNumberFormat_1" = "**%d** story";

View file

@ -1053,6 +1053,8 @@ public protocol SharedAccountContext: AnyObject {
func makeChatQrCodeScreen(context: AccountContext, peer: Peer, threadId: Int64?, temporary: Bool) -> ViewController
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource, forceDark: Bool, dismissed: (() -> Void)?) -> ViewController
func makePremiumIntroController(sharedContext: SharedAccountContext, engine: TelegramEngineUnauthorized, inAppPurchaseManager: InAppPurchaseManager, source: PremiumIntroSource, proceed: (() -> Void)?) -> ViewController
func makePremiumDemoController(context: AccountContext, subject: PremiumDemoSubject, forceDark: Bool, action: @escaping () -> Void, dismissed: (() -> Void)?) -> ViewController
func makePremiumLimitController(context: AccountContext, subject: PremiumLimitSubject, count: Int32, forceDark: Bool, cancel: @escaping () -> Void, action: @escaping () -> Bool) -> ViewController
@ -1129,6 +1131,8 @@ public protocol SharedAccountContext: AnyObject {
func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
func makeDebugSettingsController(context: AccountContext?) -> ViewController?
func navigateToCurrentCall()
@ -1228,6 +1232,7 @@ public protocol AccountContext: AnyObject {
var availableMessageEffects: Signal<AvailableMessageEffects?, NoError> { get }
var isPremium: Bool { get }
var isFrozen: Bool { get }
var userLimits: EngineConfiguration.UserLimits { get }
var peerNameColors: PeerNameColors { get }

View file

@ -43,6 +43,7 @@ public enum PremiumIntroSource {
case animatedEmoji
case messageEffects
case paidMessages
case auth(String)
}
public enum PremiumGiftSource: Equatable {
@ -136,6 +137,7 @@ public enum StarsPurchasePurpose: Equatable {
case unlockMedia(requiredStars: Int64)
case starGift(peerId: EnginePeer.Id, requiredStars: Int64)
case upgradeStarGift(requiredStars: Int64)
case transferStarGift(requiredStars: Int64)
case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64)
}
@ -294,6 +296,44 @@ public struct PremiumConfiguration {
}
}
public struct AccountFreezeConfiguration {
public static var defaultValue: AccountFreezeConfiguration {
return AccountFreezeConfiguration(
freezeSinceDate: nil,
freezeUntilDate: nil,
freezeAppealUrl: nil
)
}
public let freezeSinceDate: Int32?
public let freezeUntilDate: Int32?
public let freezeAppealUrl: String?
fileprivate init(
freezeSinceDate: Int32?,
freezeUntilDate: Int32?,
freezeAppealUrl: String?
) {
self.freezeSinceDate = freezeSinceDate
self.freezeUntilDate = freezeUntilDate
self.freezeAppealUrl = freezeAppealUrl
}
public static func with(appConfiguration: AppConfiguration) -> AccountFreezeConfiguration {
let defaultValue = self.defaultValue
if let data = appConfiguration.data {
return AccountFreezeConfiguration(
freezeSinceDate: (data["freeze_since_date"] as? Double).flatMap(Int32.init) ?? defaultValue.freezeSinceDate,
freezeUntilDate: (data["freeze_until_date"] as? Double).flatMap(Int32.init) ?? defaultValue.freezeUntilDate,
freezeAppealUrl: data["freeze_appeal_url"] as? String ?? defaultValue.freezeAppealUrl
)
} else {
return defaultValue
}
}
}
public protocol GiftOptionsScreenProtocol {
}

View file

@ -42,6 +42,8 @@ swift_library(
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
"//submodules/MoreButtonNode:MoreButtonNode",
"//submodules/ContextUI:ContextUI",
"//submodules/InAppPurchaseManager",
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
],
visibility = [
"//visibility:public",

View file

@ -20,6 +20,7 @@ import TelegramNotices
import AuthenticationServices
import Markdown
import AlertUI
import InAppPurchaseManager
import ObjectiveC
private var ObjCKey_Delegate: Int?
@ -59,6 +60,8 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
return TelegramEngineUnauthorized(account: self.account)
}
private var inAppPurchaseManager: InAppPurchaseManager!
public init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), presentationData: PresentationData, openUrl: @escaping (String) -> Void, apiId: Int32, apiHash: String, authorizationCompleted: @escaping () -> Void) {
self.sharedContext = sharedContext
self.account = account
@ -79,6 +82,8 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
super.init(mode: .single, theme: NavigationControllerTheme(statusBar: navigationStatusBar, navigationBar: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), emptyAreaColor: .black), isFlat: true)
self.inAppPurchaseManager = InAppPurchaseManager(engine: .unauthorized(self.engine))
self.stateDisposable = (self.engine.auth.state()
|> map { state -> InnerState in
if case .authorized = state {
@ -758,6 +763,17 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
return controller
}
private func paymentController(number: String, phoneCodeHash: String, storeProduct: String) -> AuthorizationSequencePaymentScreen {
let controller = AuthorizationSequencePaymentScreen(sharedContext: self.sharedContext, engine: self.engine, presentationData: self.presentationData, inAppPurchaseManager: self.inAppPurchaseManager, phoneNumber: number, phoneCodeHash: phoneCodeHash, storeProduct: storeProduct, back: { [weak self] in
guard let self else {
return
}
let countryCode = AuthorizationSequenceController.defaultCountryCode()
let _ = self.engine.auth.setState(state: UnauthorizedAccountState(isTestingEnvironment: self.account.testingEnvironment, masterDatacenterId: self.account.masterDatacenterId, contents: .phoneEntry(countryCode: countryCode, number: ""))).startStandalone()
})
return controller
}
@available(iOS 13.0, *)
public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
let lastController = self.viewControllers.last as? ViewController
@ -1285,6 +1301,13 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
}
controllers.append(self.signUpController(firstName: firstName, lastName: lastName, termsOfService: termsOfService, displayCancel: displayCancel))
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
case let .payment(number, codeHash, storeProduct, _):
var controllers: [ViewController] = []
if !self.otherAccountPhoneNumbers.1.isEmpty {
controllers.append(self.splashController())
}
controllers.append(self.paymentController(number: number, phoneCodeHash: codeHash, storeProduct: storeProduct))
self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty)
}
}
}

View file

@ -0,0 +1,602 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting
import PresentationDataUtils
import ComponentFlow
import ViewControllerComponent
import MultilineTextComponent
import BalancedTextComponent
import BundleIconComponent
import ButtonComponent
import TextFormat
import InAppPurchaseManager
import ConfettiEffect
import PremiumCoinComponent
import Markdown
import CountrySelectionUI
import AccountContext
final class AuthorizationSequencePaymentScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let sharedContext: SharedAccountContext
let engine: TelegramEngineUnauthorized
let inAppPurchaseManager: InAppPurchaseManager
let presentationData: PresentationData
let phoneNumber: String
let phoneCodeHash: String
let storeProduct: String
init(
sharedContext: SharedAccountContext,
engine: TelegramEngineUnauthorized,
inAppPurchaseManager: InAppPurchaseManager,
presentationData: PresentationData,
phoneNumber: String,
phoneCodeHash: String,
storeProduct: String
) {
self.sharedContext = sharedContext
self.engine = engine
self.inAppPurchaseManager = inAppPurchaseManager
self.presentationData = presentationData
self.phoneNumber = phoneNumber
self.phoneCodeHash = phoneCodeHash
self.storeProduct = storeProduct
}
static func ==(lhs: AuthorizationSequencePaymentScreenComponent, rhs: AuthorizationSequencePaymentScreenComponent) -> Bool {
if lhs.storeProduct != rhs.storeProduct {
return false
}
return true
}
final class View: UIView {
private let animation = ComponentView<Empty>()
private let title = ComponentView<Empty>()
private let list = ComponentView<Empty>()
private let check = ComponentView<Empty>()
private let button = ComponentView<Empty>()
private var isUpdating: Bool = false
private var component: AuthorizationSequencePaymentScreenComponent?
private(set) weak var state: EmptyComponentState?
private var environment: EnvironmentType?
private var products: [InAppPurchaseManager.Product] = []
private var productsDisposable: Disposable?
private var inProgress = false
override init(frame: CGRect) {
super.init(frame: frame)
self.disablesInteractiveKeyboardGestureRecognizer = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.productsDisposable?.dispose()
}
private func proceed() {
guard let component = self.component, let storeProduct = self.products.first(where: { $0.id == component.storeProduct }) else {
return
}
self.inProgress = true
self.state?.updated()
let (currency, amount) = storeProduct.priceCurrencyAndAmount
let purpose: AppStoreTransactionPurpose = .authCode(restore: false, phoneNumber: component.phoneNumber, phoneCodeHash: component.phoneCodeHash, currency: currency, amount: amount)
let _ = (component.engine.payments.canPurchasePremium(purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] available in
guard let self else {
return
}
let presentationData = component.presentationData
if available {
let _ = (component.inAppPurchaseManager.buyProduct(storeProduct, quantity: 1, purpose: purpose)
|> deliverOnMainQueue).start(next: { [weak self] status in
let _ = status
let _ = self
}, error: { [weak self] error in
guard let self, let controller = self.environment?.controller() else {
return
}
self.inProgress = false
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.engine.account.postbox, type: "premium_gift.promo_screen_fail")
let _ = errorText
let _ = controller
//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 update(component: AuthorizationSequencePaymentScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
let environment = environment[EnvironmentType.self].value
let themeUpdated = self.environment?.theme !== environment.theme
self.environment = environment
self.state = state
if self.component == nil {
self.productsDisposable = (component.inAppPurchaseManager.availableProducts
|> deliverOnMainQueue).start(next: { [weak self] products in
guard let self else {
return
}
self.products = products
if !self.isUpdating {
self.state?.updated()
}
})
}
self.component = component
if themeUpdated {
self.backgroundColor = environment.theme.list.plainBackgroundColor
}
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let animationSize = self.animation.update(
transition: transition,
component: AnyComponent(PremiumCoinComponent(
mode: .business,
isIntro: true,
isVisible: true,
hasIdleAnimations: true
)),
environment: {},
containerSize: CGSize(width: min(414.0, availableSize.width), height: 184.0)
)
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "SMS Fee", font: Font.bold(28.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)))
),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
)
let textColor = environment.theme.list.itemPrimaryTextColor
let secondaryTextColor = environment.theme.list.itemSecondaryTextColor
let linkColor = environment.theme.list.itemAccentColor
var countryName: String = ""
if let (country, _) = AuthorizationSequenceCountrySelectionController.lookupCountryIdByNumber(component.phoneNumber, preferredCountries: [:]) {
countryName = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: environment.strings) ?? country.name
}
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: "cost",
component: AnyComponent(ParagraphComponent(
title: "High SMS Costs",
titleColor: textColor,
text: "Telecom providers in your country (\(countryName)) charge Telegram very high prices for SMS.",
textColor: secondaryTextColor,
iconName: "Premium/Authorization/Cost",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "verification",
component: AnyComponent(ParagraphComponent(
title: "Verification Required",
titleColor: textColor,
text: "Telegram needs to send you an SMS with a verification code to confirm your phone number.",
textColor: secondaryTextColor,
iconName: "Premium/Authorization/Verification",
iconColor: linkColor
))
)
)
items.append(
AnyComponentWithIdentity(
id: "withdrawal",
component: AnyComponent(ParagraphComponent(
title: "Support via [Telegram Premium >]()",
titleColor: textColor,
text: "Sign up for a 1-week Telegram Premium subscription to help cover the SMS costs.",
textColor: secondaryTextColor,
iconName: "Premium/Authorization/Support",
iconColor: linkColor,
action: { [weak self] in
guard let self, let controller = self.environment?.controller(), let product = self.products.first(where: { $0.id == component.storeProduct }) else {
return
}
let introController = component.sharedContext.makePremiumIntroController(
sharedContext: component.sharedContext,
engine: component.engine,
inAppPurchaseManager: component.inAppPurchaseManager,
source: .auth(product.price),
proceed: { [weak self] in
self?.proceed()
}
)
controller.push(introController)
}
))
)
)
let listSize = self.list.update(
transition: transition,
component: AnyComponent(List(items)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
)
let buttonHeight: CGFloat = 50.0
let bottomPanelPadding: CGFloat = 12.0
let titleSpacing: CGFloat = -24.0
let listSpacing: CGFloat = 12.0
let totalHeight = animationSize.height + titleSpacing + titleSize.height + listSpacing + listSize.height
var originY = floor((availableSize.height - buttonHeight - bottomPanelPadding * 2.0 - totalHeight) / 2.0)
if let animationView = self.animation.view {
if animationView.superview == nil {
self.addSubview(animationView)
}
animationView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - animationSize.width) / 2.0), y: originY), size: animationSize)
originY += animationSize.height + titleSpacing
}
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: originY), size: titleSize)
originY += titleSize.height + listSpacing
}
if let listView = self.list.view {
if listView.superview == nil {
self.addSubview(listView)
}
listView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - listSize.width) / 2.0), y: originY), size: listSize)
}
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset
let priceString: String
if let product = self.products.first(where: { $0.id == component.storeProduct }) {
priceString = product.price
} else {
priceString = ""
}
let buttonString = "Sign up for \(priceString)"
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
let buttonSize = self.button.update(
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0
),
content: AnyComponentWithIdentity(
id: AnyHashable(buttonString),
component: AnyComponent(
VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))),
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Get Telegram Premium for 1 week", font: Font.medium(11.0), textColor: environment.theme.list.itemCheckColors.foregroundColor.withAlphaComponent(0.7), paragraphAlignment: .center)))))
], spacing: 1.0)
)
),
isEnabled: true,
displaysProgress: self.inProgress,
action: { [weak self] in
self?.proceed()
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: buttonHeight)
)
if let buttonView = self.button.view {
if buttonView.superview == nil {
self.addSubview(buttonView)
}
buttonView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - buttonSize.width) / 2.0), y: availableSize.height - bottomPanelHeight + bottomPanelPadding), size: buttonSize)
}
return availableSize
}
}
func makeView() -> View {
return View()
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class AuthorizationSequencePaymentScreen: ViewControllerComponentContainer {
public init(
sharedContext: SharedAccountContext,
engine: TelegramEngineUnauthorized,
presentationData: PresentationData,
inAppPurchaseManager: InAppPurchaseManager,
phoneNumber: String,
phoneCodeHash: String,
storeProduct: String,
back: @escaping () -> Void
) {
super.init(component: AuthorizationSequencePaymentScreenComponent(
sharedContext: sharedContext,
engine: engine,
inAppPurchaseManager: inAppPurchaseManager,
presentationData: presentationData,
phoneNumber: phoneNumber,
phoneCodeHash: phoneCodeHash,
storeProduct: storeProduct
), navigationBarAppearance: .transparent, theme: .default, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)))
loadServerCountryCodes(accountManager: sharedContext.accountManager, engine: engine, completion: { [weak self] in
if let strongSelf = self {
strongSelf.requestLayout(forceUpdate: true, transition: .immediate)
}
})
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.attemptNavigation = { _ in
return false
}
self.navigationBar?.backPressed = {
back()
}
}
public override func loadDisplayNode() {
super.loadDisplayNode()
self.displayNode.view.disableAutomaticKeyboardHandling = [.forward, .backward]
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
@objc private func cancelPressed() {
self.dismiss()
}
}
private final class ParagraphComponent: CombinedComponent {
let title: String
let titleColor: UIColor
let text: String
let textColor: UIColor
let iconName: String
let iconColor: UIColor
let action: (() -> Void)?
public init(
title: String,
titleColor: UIColor,
text: String,
textColor: UIColor,
iconName: String,
iconColor: UIColor,
action: (() -> Void)? = nil
) {
self.title = title
self.titleColor = titleColor
self.text = text
self.textColor = textColor
self.iconName = iconName
self.iconColor = iconColor
self.action = action
}
static func ==(lhs: ParagraphComponent, rhs: ParagraphComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
if lhs.titleColor != rhs.titleColor {
return false
}
if lhs.text != rhs.text {
return false
}
if lhs.textColor != rhs.textColor {
return false
}
if lhs.iconName != rhs.iconName {
return false
}
if lhs.iconColor != rhs.iconColor {
return false
}
return true
}
final class State: ComponentState {
var cachedChevronImage: (UIImage, UIColor)?
}
func makeState() -> State {
return State()
}
static var body: Body {
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
let icon = Child(BundleIconComponent.self)
return { context in
let component = context.component
let state = context.state
let leftInset: CGFloat = 64.0
let rightInset: CGFloat = 32.0
let textSideInset: CGFloat = leftInset + 8.0
let spacing: CGFloat = 5.0
let textTopInset: CGFloat = 9.0
let textFont = Font.regular(15.0)
let boldTextFont = Font.semibold(15.0)
let titleColor = component.titleColor
let textColor = component.textColor
let linkColor = component.iconColor
let titleMarkdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: boldTextFont, textColor: titleColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: titleColor),
link: MarkdownAttributeSet(font: boldTextFont, textColor: linkColor),
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== linkColor {
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, linkColor)
}
let titleAttributedString = parseMarkdownIntoAttributedString(component.title, attributes: titleMarkdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = titleAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
titleAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: titleAttributedString.string))
}
let title = title.update(
component: MultilineTextComponent(
text: .plain(titleAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 1,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
component.action?()
}
}
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let textMarkdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: linkColor),
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
let text = text.update(
component: MultilineTextComponent(
text: .markdown(text: component.text, attributes: textMarkdownAttributes),
horizontalAlignment: .natural,
maximumNumberOfLines: 0,
lineSpacing: 0.2,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
component.action?()
}
}
),
availableSize: CGSize(width: context.availableSize.width - leftInset - rightInset, height: context.availableSize.height),
transition: .immediate
)
let icon = icon.update(
component: BundleIconComponent(
name: component.iconName,
tintColor: component.iconColor
),
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
transition: .immediate
)
context.add(title
.position(CGPoint(x: textSideInset + title.size.width / 2.0, y: textTopInset + title.size.height / 2.0))
)
context.add(text
.position(CGPoint(x: textSideInset + text.size.width / 2.0, y: textTopInset + title.size.height + spacing + text.size.height / 2.0))
)
context.add(icon
.position(CGPoint(x: 47.0, y: textTopInset + 18.0))
)
return CGSize(width: context.availableSize.width, height: textTopInset + title.size.height + text.size.height + 18.0)
}
}
}

View file

@ -1208,6 +1208,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
)
}
self.chatListDisplayNode.mainContainerNode.openAccountFreezeInfo = { [weak self] in
guard let self else {
return
}
let controller = self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context)
self.push(controller)
}
self.chatListDisplayNode.mainContainerNode.openPhotoSetup = { [weak self] in
guard let self else {
return
@ -2786,6 +2794,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
private weak var storyCameraTooltip: TooltipScreen?
fileprivate func openStoryCamera(fromList: Bool) {
guard !self.context.isFrozen else {
let controller = self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context)
self.push(controller)
return
}
var reachedCountLimit = false
var premiumNeeded = false
var hasActiveCall = false
@ -4633,6 +4647,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
@objc fileprivate func composePressed() {
guard !self.context.isFrozen else {
let controller = self.context.sharedContext.makeAccountFreezeInfoScreen(context: self.context)
self.push(controller)
return
}
guard let navigationController = self.navigationController as? NavigationController else {
return
}

View file

@ -357,6 +357,9 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
itemNode.listNode.openPhotoSetup = { [weak self] in
self?.openPhotoSetup?()
}
itemNode.listNode.openAccountFreezeInfo = { [weak self] in
self?.openAccountFreezeInfo?()
}
self.currentItemStateValue.set(itemNode.listNode.state |> map { state in
let filterId: Int32?
@ -425,6 +428,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
var openStarsTopup: ((Int64?) -> Void)?
var openWebApp: ((TelegramUser) -> Void)?
var openPhotoSetup: (() -> Void)?
var openAccountFreezeInfo: (() -> Void)?
var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
var didBeginSelectingChats: (() -> Void)?
var canExpandHiddenItems: (() -> Bool)?

View file

@ -3281,6 +3281,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openPhotoSetup: {
}, openAdInfo: { node in
interaction.openAdInfo(node)
}, openAccountFreezeInfo: {
})
chatListInteraction.isSearchMode = true
@ -5263,6 +5264,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
}, openWebApp: { _ in
}, openPhotoSetup: {
}, openAdInfo: { _ in
}, openAccountFreezeInfo: {
})
var isInlineMode = false
if case .topics = key {

View file

@ -162,6 +162,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
}, openWebApp: { _ in
}, openPhotoSetup: {
}, openAdInfo: { _ in
}, openAccountFreezeInfo: {
})
interaction.isInlineMode = isInlineMode

View file

@ -4102,7 +4102,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
strongSelf.secretIconNode = iconNode
}
iconNode.image = currentSecretIconImage
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.origin.y + floor((titleLayout.size.height - currentSecretIconImage.size.height) / 2.0)), size: currentSecretIconImage.size))
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleLeftOffset, y: contentRect.origin.y + floor((titleLayout.size.height - currentSecretIconImage.size.height) / 2.0)), size: currentSecretIconImage.size))
titleOffset += currentSecretIconImage.size.width + 3.0
} else if let secretIconNode = strongSelf.secretIconNode {
strongSelf.secretIconNode = nil

View file

@ -115,6 +115,7 @@ public final class ChatListNodeInteraction {
let openWebApp: (TelegramUser) -> Void
let openPhotoSetup: () -> Void
let openAdInfo: (ASDisplayNode) -> Void
let openAccountFreezeInfo: () -> Void
public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation?
@ -173,7 +174,8 @@ public final class ChatListNodeInteraction {
editPeer: @escaping (ChatListItem) -> Void,
openWebApp: @escaping (TelegramUser) -> Void,
openPhotoSetup: @escaping () -> Void,
openAdInfo: @escaping (ASDisplayNode) -> Void
openAdInfo: @escaping (ASDisplayNode) -> Void,
openAccountFreezeInfo: @escaping () -> Void
) {
self.activateSearch = activateSearch
self.peerSelected = peerSelected
@ -220,6 +222,7 @@ public final class ChatListNodeInteraction {
self.openWebApp = openWebApp
self.openPhotoSetup = openPhotoSetup
self.openAdInfo = openAdInfo
self.openAccountFreezeInfo = openAccountFreezeInfo
}
}
@ -770,6 +773,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto:
nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
}
case .hide:
nodeInteraction?.dismissNotice(notice)
@ -1116,6 +1121,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction?.openStarsTopup(amount.value)
case .setupPhoto:
nodeInteraction?.openPhotoSetup()
case .accountFreeze:
nodeInteraction?.openAccountFreezeInfo()
}
case .hide:
nodeInteraction?.dismissNotice(notice)
@ -1239,6 +1246,7 @@ public final class ChatListNode: ListView {
public var openWebApp: ((TelegramUser) -> Void)?
public var openPhotoSetup: (() -> Void)?
public var openAdInfo: ((ASDisplayNode) -> Void)?
public var openAccountFreezeInfo: (() -> Void)?
private var theme: PresentationTheme
@ -1899,6 +1907,8 @@ public final class ChatListNode: ListView {
self.openPhotoSetup?()
}, openAdInfo: { [weak self] node in
self?.openAdInfo?(node)
}, openAccountFreezeInfo: { [weak self] in
self?.openAccountFreezeInfo?()
})
nodeInteraction.isInlineMode = isInlineMode
@ -1988,6 +1998,16 @@ public final class ChatListNode: ListView {
let twoStepData: Signal<TwoStepVerificationConfiguration?, NoError> = .single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map(Optional.init))
let accountFreezeConfiguration = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> AppConfiguration in
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
return appConfiguration
}
|> distinctUntilChanged
|> map { appConfiguration -> AccountFreezeConfiguration in
return AccountFreezeConfiguration.with(appConfiguration: appConfiguration)
})
let suggestedChatListNoticeSignal: Signal<ChatListNotice?, NoError> = combineLatest(
context.engine.notices.getServerProvidedSuggestions(),
context.engine.notices.getServerDismissedSuggestions(),
@ -1998,11 +2018,12 @@ public final class ChatListNode: ListView {
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
),
context.account.stateManager.contactBirthdays,
starsSubscriptionsContextPromise.get()
starsSubscriptionsContextPromise.get(),
accountFreezeConfiguration
)
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext -> Signal<ChatListNotice?, NoError> in
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext, accountFreezeConfiguration -> Signal<ChatListNotice?, NoError> in
let (accountPeer, birthday) = data
if let newSessionReview = newSessionReviews.first {
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
}
@ -2035,8 +2056,10 @@ public final class ChatListNode: ListView {
if dismissedSuggestions.contains(.todayBirthdays) {
todayBirthdayPeerIds = []
}
if suggestions.contains(.starsSubscriptionLowBalance) {
if let _ = accountFreezeConfiguration.freezeUntilDate {
return .single(.accountFreeze)
} else if suggestions.contains(.starsSubscriptionLowBalance) {
if let starsSubscriptionsContext {
return starsSubscriptionsContext.state
|> map { state in

View file

@ -92,6 +92,7 @@ public enum ChatListNotice: Equatable {
case premiumGrace
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
case setupPhoto(EnginePeer)
case accountFreeze
}
enum ChatListNodeEntry: Comparable, Identifiable {

View file

@ -288,6 +288,10 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
titleString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Title, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)
textString = NSAttributedString(string: item.strings.ChatList_AddPhoto_Text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
avatarPeer = accountPeer
case .accountFreeze:
//TODO:localize
titleString = NSAttributedString(string: "Your account is frozen", font: titleFont, textColor: item.theme.list.itemDestructiveColor)
textString = NSAttributedString(string: "Tap to view details and submit an appeal.", font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
}
var leftInset: CGFloat = sideInset

View file

@ -9,6 +9,7 @@ public final class SolidRoundedButtonComponent: Component {
public typealias Theme = SolidRoundedButtonTheme
public let title: String?
public let subtitle: String?
public let label: String?
public let badge: String?
public let icon: UIImage?
@ -28,6 +29,7 @@ public final class SolidRoundedButtonComponent: Component {
public init(
title: String? = nil,
subtitle: String? = nil,
label: String? = nil,
badge: String? = nil,
icon: UIImage? = nil,
@ -46,6 +48,7 @@ public final class SolidRoundedButtonComponent: Component {
action: @escaping () -> Void
) {
self.title = title
self.subtitle = subtitle
self.label = label
self.badge = badge
self.icon = icon
@ -68,6 +71,9 @@ public final class SolidRoundedButtonComponent: Component {
if lhs.title != rhs.title {
return false
}
if lhs.subtitle != rhs.subtitle {
return false
}
if lhs.label != rhs.label {
return false
}
@ -147,6 +153,7 @@ public final class SolidRoundedButtonComponent: Component {
if let button = self.button {
button.title = component.title
button.subtitle = component.subtitle
button.label = component.label
button.badge = component.badge
button.iconPosition = component.iconPosition

View file

@ -151,8 +151,8 @@ open class ViewControllerComponentContainer: ViewController {
private var currentIsVisible: Bool = false
private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
init(context: AccountContext, controller: ViewControllerComponentContainer, component: AnyComponent<ViewControllerComponentContainer.Environment>, theme: Theme) {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
init(presentationData: PresentationData, controller: ViewControllerComponentContainer, component: AnyComponent<ViewControllerComponentContainer.Environment>, theme: Theme) {
self.presentationData = presentationData
self.controller = controller
@ -234,7 +234,7 @@ open class ViewControllerComponentContainer: ViewController {
return self.displayNode as! Node
}
private let context: AccountContext
private var presentationData: PresentationData
private var theme: Theme
public private(set) var component: AnyComponent<ViewControllerComponentContainer.Environment>
@ -242,6 +242,7 @@ open class ViewControllerComponentContainer: ViewController {
public private(set) var validLayout: ContainerViewLayout?
public var wasDismissed: (() -> Void)?
public var customProceed: (() -> Void)?
public init<C: Component>(
context: AccountContext,
@ -252,17 +253,19 @@ open class ViewControllerComponentContainer: ViewController {
theme: Theme = .default,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil
) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
self.context = context
self.component = AnyComponent(component)
self.theme = theme
let presentationData: PresentationData
var effectiveUpdatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)
if let updatedPresentationData {
presentationData = updatedPresentationData.initial
effectiveUpdatedPresentationData = updatedPresentationData
} else {
presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
effectiveUpdatedPresentationData = (initial: context.sharedContext.currentPresentationData.with { $0 }, signal: context.sharedContext.presentationData)
}
let presentationData = effectiveUpdatedPresentationData.initial
self.presentationData = presentationData
let navigationBarPresentationData: NavigationBarPresentationData?
switch navigationBarAppearance {
case .none:
@ -274,7 +277,47 @@ open class ViewControllerComponentContainer: ViewController {
}
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? self.context.sharedContext.presentationData)
self.setupPresentationData(effectiveUpdatedPresentationData, navigationBarAppearance: navigationBarAppearance, statusBarStyle: statusBarStyle, presentationMode: presentationMode)
}
public init<C: Component>(
component: C,
navigationBarAppearance: NavigationBarAppearance,
statusBarStyle: StatusBarStyle = .default,
presentationMode: PresentationMode = .default,
theme: Theme = .default,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)
) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
self.component = AnyComponent(component)
self.theme = theme
let presentationData = updatedPresentationData.initial
self.presentationData = presentationData
let navigationBarPresentationData: NavigationBarPresentationData?
switch navigationBarAppearance {
case .none:
navigationBarPresentationData = nil
case .transparent:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData, hideBackground: true, hideBadge: false, hideSeparator: true)
case .default:
navigationBarPresentationData = NavigationBarPresentationData(presentationData: presentationData)
}
super.init(navigationBarPresentationData: navigationBarPresentationData)
self.setupPresentationData(updatedPresentationData, navigationBarAppearance: navigationBarAppearance, statusBarStyle: statusBarStyle, presentationMode: presentationMode)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
}
private func setupPresentationData(_ updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), navigationBarAppearance: NavigationBarAppearance, statusBarStyle: StatusBarStyle, presentationMode: PresentationMode) {
self.presentationDataDisposable = (updatedPresentationData.signal
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
var theme = presentationData.theme
@ -329,16 +372,8 @@ open class ViewControllerComponentContainer: ViewController {
}
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.presentationDataDisposable?.dispose()
}
override open func loadDisplayNode() {
self.displayNode = Node(context: self.context, controller: self, component: self.component, theme: self.theme)
self.displayNode = Node(presentationData: self.presentationData, controller: self, component: self.component, theme: self.theme)
self.displayNodeDidLoad()
}

View file

@ -591,7 +591,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
allSelected = false
}
var actionTitle: String?
if peerIds.count > 1 {
if !"".isEmpty, peerIds.count > 1 {
actionTitle = allSelected ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : strings.Premium_Gift_ContactSelection_SelectAll.uppercased()
}
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(title.uppercased(), AnyHashable(10 * sectionId + (allSelected ? 1 : 0))), theme: theme, strings: strings, actionTitle: actionTitle, action: { _ in

View file

@ -129,7 +129,7 @@ public final class TextAlertContentActionNode: HighlightableButtonNode {
if let range = attributedString.string.range(of: "$") {
attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.foregroundColor, value: color, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedString.string))
}
self.setAttributedTitle(attributedString, for: [])

View file

@ -28,6 +28,8 @@ private let productIdentifiers = [
"org.telegram.telegramPremium.sixMonths.code_x10",
"org.telegram.telegramPremium.twelveMonths.code_x10",
"org.telegram.telegramPremium.oneWeek.auth",
"org.telegram.telegramStars.topup.x15",
"org.telegram.telegramStars.topup.x25",
"org.telegram.telegramStars.topup.x50",
@ -216,7 +218,7 @@ public final class InAppPurchaseManager: NSObject {
case deferred
}
private let engine: TelegramEngine
private let engine: SomeTelegramEngine
private var products: [Product] = []
private var productsPromise = Promise<[Product]>([])
@ -231,7 +233,9 @@ public final class InAppPurchaseManager: NSObject {
private let disposableSet = DisposableDict<String>()
public init(engine: TelegramEngine) {
private var lastRequestTimestamp: Double?
public init(engine: SomeTelegramEngine) {
self.engine = engine
super.init()
@ -255,11 +259,15 @@ public final class InAppPurchaseManager: NSObject {
productRequest.start()
self.productRequest = productRequest
self.lastRequestTimestamp = CFAbsoluteTimeGetCurrent()
}
public var availableProducts: Signal<[Product], NoError> {
if self.products.isEmpty && self.productRequest == nil {
self.requestProducts()
if self.products.isEmpty {
if let lastRequestTimestamp, CFAbsoluteTimeGetCurrent() - lastRequestTimestamp > 10.0 {
Logger.shared.log("InAppPurchaseManager", "No available products, rerequest")
self.requestProducts()
}
}
return self.productsPromise.get()
}
@ -287,7 +295,13 @@ public final class InAppPurchaseManager: NSObject {
return .fail(.cantMakePayments)
}
let accountPeerId = "\(self.engine.account.peerId.toInt64())"
let accountPeerId: String
switch self.engine {
case let .authorized(engine):
accountPeerId = "\(engine.account.peerId.toInt64())"
case let .unauthorized(engine):
accountPeerId = "\(engine.account.id.int64)"
}
Logger.shared.log("InAppPurchaseManager", "Buying: account \(accountPeerId), product \(product.skProduct.productIdentifier), price \(product.price)")
@ -399,7 +413,13 @@ private func getReceiptData() -> Data? {
extension InAppPurchaseManager: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
self.stateQueue.async {
let accountPeerId = "\(self.engine.account.peerId.toInt64())"
let accountPeerId: String
switch self.engine {
case let .authorized(engine):
accountPeerId = "\(engine.account.peerId.toInt64())"
case let .unauthorized(engine):
accountPeerId = "\(engine.account.id.int64)"
}
let paymentContexts = self.paymentContexts
@ -519,7 +539,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
(purpose
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
return self.engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: purpose)
switch self.engine {
case let .authorized(engine):
return engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: purpose)
case let .unauthorized(engine):
return 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 {
@ -551,8 +576,15 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
self.onRestoreCompletion = nil
if let receiptData = getReceiptData() {
let signal: Signal<Never, AssignAppStoreTransactionError>
switch self.engine {
case let .authorized(engine):
signal = engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: .restore)
case let .unauthorized(engine):
signal = engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: .restore)
}
self.disposableSet.set(
self.engine.payments.sendAppStoreReceipt(receipt: receiptData, purpose: .restore).start(error: { error in
signal.start(error: { error in
Queue.mainQueue().async {
if case .serverProvided = error {
onRestoreCompletion(.succeed(true))
@ -586,14 +618,17 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
}
private func debugSaveReceipt(receiptData: Data) {
guard case let .authorized(engine) = self.engine else {
return
}
let id = Int64.random(in: Int64.min ... Int64.max)
let fileResource = LocalFileMediaResource(fileId: id, size: Int64(receiptData.count), isSecretRelated: false)
self.engine.account.postbox.mediaBox.storeResourceData(fileResource.id, data: receiptData)
engine.account.postbox.mediaBox.storeResourceData(fileResource.id, data: receiptData)
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: Int64(receiptData.count), attributes: [.FileName(fileName: "Receipt.dat")], alternativeRepresentations: [])
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: file), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
let _ = enqueueMessages(account: self.engine.account, peerId: self.engine.account.peerId, messages: [message]).start()
let _ = enqueueMessages(account: engine.account, peerId: engine.account.peerId, messages: [message]).start()
}
}
@ -625,6 +660,9 @@ private final class PendingInAppPurchaseState: Codable {
case users
case text
case entities
case restore
case phoneNumber
case phoneCodeHash
}
enum PurposeType: Int32 {
@ -637,6 +675,7 @@ private final class PendingInAppPurchaseState: Codable {
case stars
case starsGift
case starsGiveaway
case authCode
}
case subscription
@ -648,6 +687,7 @@ private final class PendingInAppPurchaseState: Codable {
case stars(count: Int64)
case starsGift(peerId: EnginePeer.Id, count: Int64)
case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, users: Int32)
case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String)
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
@ -704,6 +744,12 @@ private final class PendingInAppPurchaseState: Codable {
untilDate: try container.decode(Int32.self, forKey: .untilDate),
users: try container.decode(Int32.self, forKey: .users)
)
case .authCode:
self = .authCode(
restore: try container.decode(Bool.self, forKey: .restore),
phoneNumber: try container.decode(String.self, forKey: .phoneNumber),
phoneCodeHash: try container.decode(String.self, forKey: .phoneCodeHash)
)
default:
throw DecodingError.generic
}
@ -757,6 +803,11 @@ private final class PendingInAppPurchaseState: Codable {
try container.encode(randomId, forKey: .randomId)
try container.encode(untilDate, forKey: .untilDate)
try container.encode(users, forKey: .users)
case let .authCode(restore, phoneNumber, phoneCodeHash):
try container.encode(PurposeType.authCode.rawValue, forKey: .type)
try container.encode(restore, forKey: .restore)
try container.encode(phoneNumber, forKey: .phoneNumber)
try container.encode(phoneCodeHash, forKey: .phoneCodeHash)
}
}
@ -780,6 +831,8 @@ private final class PendingInAppPurchaseState: Codable {
self = .starsGift(peerId: peerId, count: count)
case let .starsGiveaway(stars, boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, _, _, users):
self = .starsGiveaway(stars: stars, boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, users: users)
case let .authCode(restore, phoneNumber, phoneCodeHash, _, _):
self = .authCode(restore: restore, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash)
}
}
@ -804,6 +857,8 @@ private final class PendingInAppPurchaseState: Codable {
return .starsGift(peerId: peerId, count: count, currency: currency, amount: amount)
case let .starsGiveaway(stars, boostPeer, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, users):
return .starsGiveaway(stars: stars, boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, showWinners: showWinners, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users)
case let .authCode(restore, phoneNumber, phoneCodeHash):
return .authCode(restore: restore, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, currency: currency, amount: amount)
}
}
}
@ -831,23 +886,41 @@ private final class PendingInAppPurchaseState: Codable {
}
}
private func pendingInAppPurchaseState(engine: TelegramEngine, productId: String) -> Signal<PendingInAppPurchaseState?, NoError> {
private func pendingInAppPurchaseState(engine: SomeTelegramEngine, productId: String) -> Signal<PendingInAppPurchaseState?, NoError> {
let key = EngineDataBuffer(length: 8)
key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue))
return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key))
|> map { entry -> PendingInAppPurchaseState? in
return entry?.get(PendingInAppPurchaseState.self)
switch engine {
case let .authorized(engine):
return engine.data.get(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key))
|> map { entry -> PendingInAppPurchaseState? in
return entry?.get(PendingInAppPurchaseState.self)
}
case let .unauthorized(engine):
return engine.itemCache.get(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)
|> map { entry -> PendingInAppPurchaseState? in
return entry?.get(PendingInAppPurchaseState.self)
}
}
}
private func updatePendingInAppPurchaseState(engine: TelegramEngine, productId: String, content: PendingInAppPurchaseState?) -> Signal<Never, NoError> {
private func updatePendingInAppPurchaseState(engine: SomeTelegramEngine, productId: String, content: PendingInAppPurchaseState?) -> Signal<Never, NoError> {
let key = EngineDataBuffer(length: 8)
key.setInt64(0, value: Int64(bitPattern: productId.persistentHashValue))
if let content = content {
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key, item: content)
} else {
return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)
switch engine {
case let .authorized(engine):
if let content = content {
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key, item: content)
} else {
return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)
}
case let .unauthorized(engine):
if let content = content {
return engine.itemCache.put(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key, item: content)
} else {
return engine.itemCache.remove(collectionId: ApplicationSpecificItemCacheCollectionId.pendingInAppPurchaseState, id: key)
}
}
}

View file

@ -67,9 +67,7 @@
- (SSignal *)coverImageSignalForItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setCoverImage:(UIImage *)image position:(NSNumber *)position forItem:(id<TGMediaEditableItem>)item;
- (UIImage *)coverImageForItem:(NSObject<TGMediaEditableItem> *)item;
- (NSNumber *)coverPositionForItem:(NSObject<TGMediaEditableItem> *)item;
- (void)setCoverImage:(UIImage *)image position:(NSNumber *)position forItem:(id<TGMediaEditableItem>)item;
- (void)setTemporaryRep:(id)rep forItem:(id<TGMediaEditableItem>)item;

View file

@ -898,7 +898,7 @@
}
restartRequest = true;
}
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
else if ((rpcError.errorCode == 420 && [rpcError.errorDescription rangeOfString:@"FROZEN_METHOD_INVALID"].location == NSNotFound) || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
if (request.errorContext == nil)
request.errorContext = [[MTRequestErrorContext alloc] init];

View file

@ -120,6 +120,7 @@ swift_library(
"//submodules/TelegramUI/Components/EmojiActionIconComponent",
"//submodules/TelegramUI/Components/ScrollComponent",
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
],
visibility = [
"//visibility:public",

View file

@ -11,6 +11,7 @@ import Markdown
import TelegramPresentationData
import BundleIconComponent
import ScrollComponent
import PremiumCoinComponent
private final class HeaderComponent: Component {
let context: AccountContext

View file

@ -64,6 +64,7 @@ class EmojiHeaderComponent: Component {
}
weak var animateFrom: UIView?
var sourceRect: CGRect?
weak var containerView: UIView?
let statusView: ComponentHostView<Empty>
@ -116,8 +117,13 @@ class EmojiHeaderComponent: Component {
let initialPosition = self.statusView.center
let targetPosition = self.statusView.superview!.convert(self.statusView.center, to: containerView)
let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: 0.0, dy: 0.0)
var sourceOffset: CGPoint = .zero
if let sourceRect = self.sourceRect {
sourceOffset = CGPoint(x: sourceRect.center.x - animateFrom.frame.width / 2.0, y: 0.0)
}
let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: sourceOffset.x, dy: sourceOffset.y)
containerView.addSubview(self.statusView)
self.statusView.center = targetPosition
@ -127,6 +133,7 @@ class EmojiHeaderComponent: Component {
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring)
Queue.mainQueue().after(0.55, {
self.statusView.layer.removeAllAnimations()
self.addSubview(self.statusView)
self.statusView.center = initialPosition
})

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,7 @@ import BundleIconComponent
import Markdown
import SolidRoundedButtonNode
import BlurredBackgroundComponent
import PremiumCoinComponent
public class PremiumLimitsListScreen: ViewController {
final class Node: ViewControllerTracingNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {

View file

@ -315,7 +315,7 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati
entries.append(.footerItem(footer))
}
if !peers.isEmpty {
if !peers.isEmpty || state.enableForPremium || state.enableForBots {
entries.append(.deleteItem(presentationData.strings.Privacy_Exceptions_DeleteAllExceptions))
}

View file

@ -233,6 +233,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
}, openWebApp: { _ in
}, openPhotoSetup: {
}, openAdInfo: { _ in
}, openAccountFreezeInfo: {
})
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)

View file

@ -382,6 +382,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
}, openWebApp: { _ in
}, openPhotoSetup: {
}, openAdInfo: { _ in
}, openAccountFreezeInfo: {
})
func makeChatListItem(

View file

@ -1412,7 +1412,7 @@ public final class SolidRoundedButtonView: UIView {
}
self.titleNode.attributedText = titleText
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.medium(11.0), textColor: theme.foregroundColor.withAlphaComponent(0.7))
self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: theme.foregroundColor)
@ -1472,7 +1472,7 @@ public final class SolidRoundedButtonView: UIView {
}
let titleSize = self.titleNode.updateLayout(buttonSize)
let spacingOffset: CGFloat = 9.0
let spacingOffset: CGFloat = 7.0
let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset
let iconSpacing: CGFloat = self.iconSpacing
let badgeSpacing: CGFloat = 6.0
@ -1533,11 +1533,11 @@ public final class SolidRoundedButtonView: UIView {
}
if self.subtitle != self.subtitleNode.attributedText?.string {
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: self.theme.foregroundColor)
self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.medium(11.0), textColor: self.theme.foregroundColor.withAlphaComponent(0.7))
}
let subtitleSize = self.subtitleNode.updateLayout(buttonSize)
let subtitleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - subtitleSize.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - titleSize.height) / 2.0) + spacingOffset + 2.0), size: subtitleSize)
let subtitleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - subtitleSize.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - titleSize.height) / 2.0) + spacingOffset + 7.0), size: subtitleSize)
transition.updateFrame(view: self.subtitleNode, frame: subtitleFrame)
if previousSubtitle == nil && self.subtitle != nil {

View file

@ -84,7 +84,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1778593322] = { return Api.BotApp.parse_botApp($0) }
dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($0) }
dict[-912582320] = { return Api.BotAppSettings.parse_botAppSettings($0) }
dict[-1989921868] = { return Api.BotBusinessConnection.parse_botBusinessConnection($0) }
dict[-1892371723] = { return Api.BotBusinessConnection.parse_botBusinessConnection($0) }
dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) }
dict[-1180016534] = { return Api.BotCommandScope.parse_botCommandScopeChatAdmins($0) }
dict[1877059713] = { return Api.BotCommandScope.parse_botCommandScopeChats($0) }
@ -118,6 +118,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-867328308] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleCustom($0) }
dict[-1007487743] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleOutsideWorkHours($0) }
dict[-1198722189] = { return Api.BusinessBotRecipients.parse_businessBotRecipients($0) }
dict[-1604170505] = { return Api.BusinessBotRights.parse_businessBotRights($0) }
dict[-1263638929] = { return Api.BusinessChatLink.parse_businessChatLink($0) }
dict[-451302485] = { return Api.BusinessGreetingMessage.parse_businessGreetingMessage($0) }
dict[1510606445] = { return Api.BusinessIntro.parse_businessIntro($0) }
@ -224,7 +225,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1713193015] = { return Api.ChatReactions.parse_chatReactionsSome($0) }
dict[-1390068360] = { return Api.CodeSettings.parse_codeSettings($0) }
dict[-870702050] = { return Api.Config.parse_config($0) }
dict[-1123645951] = { return Api.ConnectedBot.parse_connectedBot($0) }
dict[-849058964] = { return Api.ConnectedBot.parse_connectedBot($0) }
dict[429997937] = { return Api.ConnectedBotStarRef.parse_connectedBotStarRef($0) }
dict[341499403] = { return Api.Contact.parse_contact($0) }
dict[496600883] = { return Api.ContactBirthday.parse_contactBirthday($0) }
@ -480,6 +481,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[853188252] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) }
dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) }
dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) }
dict[-1682807955] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentAuthCode($0) }
dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) }
dict[-75955309] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) }
dict[369444042] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) }
@ -575,6 +577,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) }
dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($0) }
dict[1345295095] = { return Api.MessageAction.parse_messageActionInviteToGroupCall($0) }
dict[-1126755303] = { return Api.MessageAction.parse_messageActionPaidMessagesPrice($0) }
dict[-1407246387] = { return Api.MessageAction.parse_messageActionPaidMessagesRefunded($0) }
dict[1102307842] = { return Api.MessageAction.parse_messageActionPaymentRefunded($0) }
dict[-970673810] = { return Api.MessageAction.parse_messageActionPaymentSent($0) }
dict[-6288180] = { return Api.MessageAction.parse_messageActionPaymentSentMe($0) }
@ -1110,6 +1114,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1821035490] = { return Api.Update.parse_updateSavedGifs($0) }
dict[969307186] = { return Api.Update.parse_updateSavedReactionTags($0) }
dict[1960361625] = { return Api.Update.parse_updateSavedRingtones($0) }
dict[1347068303] = { return Api.Update.parse_updateSentPhoneCode($0) }
dict[2103604867] = { return Api.Update.parse_updateSentStoryReaction($0) }
dict[-337352679] = { return Api.Update.parse_updateServiceNotification($0) }
dict[-245208620] = { return Api.Update.parse_updateSmsJob($0) }
@ -1217,6 +1222,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[957176926] = { return Api.auth.LoginToken.parse_loginTokenSuccess($0) }
dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) }
dict[1577067778] = { return Api.auth.SentCode.parse_sentCode($0) }
dict[-674301568] = { return Api.auth.SentCode.parse_sentCodePaymentRequired($0) }
dict[596704836] = { return Api.auth.SentCode.parse_sentCodeSuccess($0) }
dict[1035688326] = { return Api.auth.SentCodeType.parse_sentCodeTypeApp($0) }
dict[1398007207] = { return Api.auth.SentCodeType.parse_sentCodeTypeCall($0) }
@ -1589,6 +1595,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessBotRecipients:
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessBotRights:
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessChatLink:
_1.serialize(buffer, boxed)
case let _1 as Api.BusinessGreetingMessage:

View file

@ -1170,27 +1170,28 @@ public extension Api {
}
public extension Api {
enum BotBusinessConnection: TypeConstructorDescription {
case botBusinessConnection(flags: Int32, connectionId: String, userId: Int64, dcId: Int32, date: Int32)
case botBusinessConnection(flags: Int32, connectionId: String, userId: Int64, dcId: Int32, date: Int32, rights: Api.BusinessBotRights?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date):
case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date, let rights):
if boxed {
buffer.appendInt32(-1989921868)
buffer.appendInt32(-1892371723)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(connectionId, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
serializeInt32(dcId, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {rights!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date):
return ("botBusinessConnection", [("flags", flags as Any), ("connectionId", connectionId as Any), ("userId", userId as Any), ("dcId", dcId as Any), ("date", date as Any)])
case .botBusinessConnection(let flags, let connectionId, let userId, let dcId, let date, let rights):
return ("botBusinessConnection", [("flags", flags as Any), ("connectionId", connectionId as Any), ("userId", userId as Any), ("dcId", dcId as Any), ("date", date as Any), ("rights", rights as Any)])
}
}
@ -1205,13 +1206,18 @@ public extension Api {
_4 = reader.readInt32()
var _5: Int32?
_5 = reader.readInt32()
var _6: Api.BusinessBotRights?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.BusinessBotRights
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.BotBusinessConnection.botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!)
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.BotBusinessConnection.botBusinessConnection(flags: _1!, connectionId: _2!, userId: _3!, dcId: _4!, date: _5!, rights: _6)
}
else {
return nil

View file

@ -116,6 +116,7 @@ public extension Api {
}
public extension Api {
indirect enum InputStorePaymentPurpose: TypeConstructorDescription {
case inputStorePaymentAuthCode(flags: Int32, phoneNumber: String, phoneCodeHash: String, 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, 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)
@ -126,6 +127,16 @@ public extension Api {
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputStorePaymentAuthCode(let flags, let phoneNumber, let phoneCodeHash, let currency, let amount):
if boxed {
buffer.appendInt32(-1682807955)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(phoneNumber, buffer: buffer, boxed: false)
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
break
case .inputStorePaymentGiftPremium(let userId, let currency, let amount):
if boxed {
buffer.appendInt32(1634697192)
@ -223,6 +234,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputStorePaymentAuthCode(let flags, let phoneNumber, let phoneCodeHash, let currency, let amount):
return ("inputStorePaymentAuthCode", [("flags", flags as Any), ("phoneNumber", phoneNumber as Any), ("phoneCodeHash", phoneCodeHash as Any), ("currency", currency as Any), ("amount", amount as Any)])
case .inputStorePaymentGiftPremium(let userId, let currency, let amount):
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, let message):
@ -240,6 +253,29 @@ public extension Api {
}
}
public static func parse_inputStorePaymentAuthCode(_ reader: BufferReader) -> InputStorePaymentPurpose? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: String?
_3 = parseString(reader)
var _4: String?
_4 = parseString(reader)
var _5: Int64?
_5 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.InputStorePaymentPurpose.inputStorePaymentAuthCode(flags: _1!, phoneNumber: _2!, phoneCodeHash: _3!, currency: _4!, amount: _5!)
}
else {
return nil
}
}
public static func parse_inputStorePaymentGiftPremium(_ reader: BufferReader) -> InputStorePaymentPurpose? {
var _1: Api.InputUser?
if let signature = reader.readInt32() {

View file

@ -363,6 +363,8 @@ public extension Api {
case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32)
case messageActionHistoryClear
case messageActionInviteToGroupCall(call: Api.InputGroupCall, users: [Int64])
case messageActionPaidMessagesPrice(stars: Int64)
case messageActionPaidMessagesRefunded(count: Int32, stars: Int64)
case messageActionPaymentRefunded(flags: Int32, peer: Api.Peer, currency: String, totalAmount: Int64, payload: Buffer?, charge: Api.PaymentCharge)
case messageActionPaymentSent(flags: Int32, currency: String, totalAmount: Int64, invoiceSlug: String?, subscriptionUntilDate: Int32?)
case messageActionPaymentSentMe(flags: Int32, currency: String, totalAmount: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, charge: Api.PaymentCharge, subscriptionUntilDate: Int32?)
@ -595,6 +597,19 @@ public extension Api {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
case .messageActionPaidMessagesPrice(let stars):
if boxed {
buffer.appendInt32(-1126755303)
}
serializeInt64(stars, buffer: buffer, boxed: false)
break
case .messageActionPaidMessagesRefunded(let count, let stars):
if boxed {
buffer.appendInt32(-1407246387)
}
serializeInt32(count, buffer: buffer, boxed: false)
serializeInt64(stars, buffer: buffer, boxed: false)
break
case .messageActionPaymentRefunded(let flags, let peer, let currency, let totalAmount, let payload, let charge):
if boxed {
buffer.appendInt32(1102307842)
@ -847,6 +862,10 @@ public extension Api {
return ("messageActionHistoryClear", [])
case .messageActionInviteToGroupCall(let call, let users):
return ("messageActionInviteToGroupCall", [("call", call as Any), ("users", users as Any)])
case .messageActionPaidMessagesPrice(let stars):
return ("messageActionPaidMessagesPrice", [("stars", stars as Any)])
case .messageActionPaidMessagesRefunded(let count, let stars):
return ("messageActionPaidMessagesRefunded", [("count", count as Any), ("stars", stars as Any)])
case .messageActionPaymentRefunded(let flags, let peer, let currency, let totalAmount, let payload, let charge):
return ("messageActionPaymentRefunded", [("flags", flags as Any), ("peer", peer as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("payload", payload as Any), ("charge", charge as Any)])
case .messageActionPaymentSent(let flags, let currency, let totalAmount, let invoiceSlug, let subscriptionUntilDate):
@ -1277,6 +1296,31 @@ public extension Api {
return nil
}
}
public static func parse_messageActionPaidMessagesPrice(_ reader: BufferReader) -> MessageAction? {
var _1: Int64?
_1 = reader.readInt64()
let _c1 = _1 != nil
if _c1 {
return Api.MessageAction.messageActionPaidMessagesPrice(stars: _1!)
}
else {
return nil
}
}
public static func parse_messageActionPaidMessagesRefunded(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageAction.messageActionPaidMessagesRefunded(count: _1!, stars: _2!)
}
else {
return nil
}
}
public static func parse_messageActionPaymentRefunded(_ reader: BufferReader) -> MessageAction? {
var _1: Int32?
_1 = reader.readInt32()

View file

@ -1103,6 +1103,7 @@ public extension Api {
case updateSavedGifs
case updateSavedReactionTags
case updateSavedRingtones
case updateSentPhoneCode(sentCode: Api.auth.SentCode)
case updateSentStoryReaction(peer: Api.Peer, storyId: Int32, reaction: Api.Reaction)
case updateServiceNotification(flags: Int32, inboxDate: Int32?, type: String, message: String, media: Api.MessageMedia, entities: [Api.MessageEntity])
case updateSmsJob(jobId: String)
@ -2198,6 +2199,12 @@ public extension Api {
buffer.appendInt32(1960361625)
}
break
case .updateSentPhoneCode(let sentCode):
if boxed {
buffer.appendInt32(1347068303)
}
sentCode.serialize(buffer, true)
break
case .updateSentStoryReaction(let peer, let storyId, let reaction):
if boxed {
@ -2602,6 +2609,8 @@ public extension Api {
return ("updateSavedReactionTags", [])
case .updateSavedRingtones:
return ("updateSavedRingtones", [])
case .updateSentPhoneCode(let sentCode):
return ("updateSentPhoneCode", [("sentCode", sentCode as Any)])
case .updateSentStoryReaction(let peer, let storyId, let reaction):
return ("updateSentStoryReaction", [("peer", peer as Any), ("storyId", storyId as Any), ("reaction", reaction as Any)])
case .updateServiceNotification(let flags, let inboxDate, let type, let message, let media, let entities):
@ -4803,6 +4812,19 @@ public extension Api {
public static func parse_updateSavedRingtones(_ reader: BufferReader) -> Update? {
return Api.Update.updateSavedRingtones
}
public static func parse_updateSentPhoneCode(_ reader: BufferReader) -> Update? {
var _1: Api.auth.SentCode?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.auth.SentCode
}
let _c1 = _1 != nil
if _c1 {
return Api.Update.updateSentPhoneCode(sentCode: _1!)
}
else {
return nil
}
}
public static func parse_updateSentStoryReaction(_ reader: BufferReader) -> Update? {
var _1: Api.Peer?
if let signature = reader.readInt32() {

View file

@ -567,6 +567,7 @@ public extension Api.auth {
public extension Api.auth {
enum SentCode: TypeConstructorDescription {
case sentCode(flags: Int32, type: Api.auth.SentCodeType, phoneCodeHash: String, nextType: Api.auth.CodeType?, timeout: Int32?)
case sentCodePaymentRequired(storeProduct: String, phoneCodeHash: String)
case sentCodeSuccess(authorization: Api.auth.Authorization)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
@ -581,6 +582,13 @@ public extension Api.auth {
if Int(flags) & Int(1 << 1) != 0 {nextType!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
break
case .sentCodePaymentRequired(let storeProduct, let phoneCodeHash):
if boxed {
buffer.appendInt32(-674301568)
}
serializeString(storeProduct, buffer: buffer, boxed: false)
serializeString(phoneCodeHash, buffer: buffer, boxed: false)
break
case .sentCodeSuccess(let authorization):
if boxed {
buffer.appendInt32(596704836)
@ -594,6 +602,8 @@ public extension Api.auth {
switch self {
case .sentCode(let flags, let type, let phoneCodeHash, let nextType, let timeout):
return ("sentCode", [("flags", flags as Any), ("type", type as Any), ("phoneCodeHash", phoneCodeHash as Any), ("nextType", nextType as Any), ("timeout", timeout as Any)])
case .sentCodePaymentRequired(let storeProduct, let phoneCodeHash):
return ("sentCodePaymentRequired", [("storeProduct", storeProduct as Any), ("phoneCodeHash", phoneCodeHash as Any)])
case .sentCodeSuccess(let authorization):
return ("sentCodeSuccess", [("authorization", authorization as Any)])
}
@ -626,6 +636,20 @@ public extension Api.auth {
return nil
}
}
public static func parse_sentCodePaymentRequired(_ reader: BufferReader) -> SentCode? {
var _1: String?
_1 = parseString(reader)
var _2: String?
_2 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.auth.SentCode.sentCodePaymentRequired(storeProduct: _1!, phoneCodeHash: _2!)
}
else {
return nil
}
}
public static func parse_sentCodeSuccess(_ reader: BufferReader) -> SentCode? {
var _1: Api.auth.Authorization?
if let signature = reader.readInt32() {

View file

@ -54,6 +54,42 @@ public extension Api {
}
}
public extension Api {
enum BusinessBotRights: TypeConstructorDescription {
case businessBotRights(flags: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .businessBotRights(let flags):
if boxed {
buffer.appendInt32(-1604170505)
}
serializeInt32(flags, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .businessBotRights(let flags):
return ("businessBotRights", [("flags", flags as Any)])
}
}
public static func parse_businessBotRights(_ reader: BufferReader) -> BusinessBotRights? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.BusinessBotRights.businessBotRights(flags: _1!)
}
else {
return nil
}
}
}
}
public extension Api {
enum BusinessChatLink: TypeConstructorDescription {
case businessChatLink(flags: Int32, link: String, message: String, entities: [Api.MessageEntity]?, title: String?, views: Int32)

View file

@ -1604,13 +1604,14 @@ public extension Api.functions.account {
}
}
public extension Api.functions.account {
static func updateConnectedBot(flags: Int32, bot: Api.InputUser, recipients: Api.InputBusinessBotRecipients) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
static func updateConnectedBot(flags: Int32, rights: Api.BusinessBotRights?, bot: Api.InputUser, recipients: Api.InputBusinessBotRecipients) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1138250269)
buffer.appendInt32(1721797758)
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {rights!.serialize(buffer, true)}
bot.serialize(buffer, true)
recipients.serialize(buffer, true)
return (FunctionDescription(name: "account.updateConnectedBot", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("recipients", String(describing: recipients))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
return (FunctionDescription(name: "account.updateConnectedBot", parameters: [("flags", String(describing: flags)), ("rights", String(describing: rights)), ("bot", String(describing: bot)), ("recipients", String(describing: recipients))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
@ -9063,11 +9064,11 @@ public extension Api.functions.payments {
}
}
public extension Api.functions.payments {
static func canPurchasePremium(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
static func canPurchaseStore(purpose: Api.InputStorePaymentPurpose) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(-1614700874)
buffer.appendInt32(1339842215)
purpose.serialize(buffer, true)
return (FunctionDescription(name: "payments.canPurchasePremium", parameters: [("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
return (FunctionDescription(name: "payments.canPurchaseStore", parameters: [("purpose", String(describing: purpose))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {

View file

@ -664,25 +664,26 @@ public extension Api {
}
public extension Api {
enum ConnectedBot: TypeConstructorDescription {
case connectedBot(flags: Int32, botId: Int64, recipients: Api.BusinessBotRecipients)
case connectedBot(flags: Int32, botId: Int64, recipients: Api.BusinessBotRecipients, rights: Api.BusinessBotRights)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .connectedBot(let flags, let botId, let recipients):
case .connectedBot(let flags, let botId, let recipients, let rights):
if boxed {
buffer.appendInt32(-1123645951)
buffer.appendInt32(-849058964)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false)
recipients.serialize(buffer, true)
rights.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .connectedBot(let flags, let botId, let recipients):
return ("connectedBot", [("flags", flags as Any), ("botId", botId as Any), ("recipients", recipients as Any)])
case .connectedBot(let flags, let botId, let recipients, let rights):
return ("connectedBot", [("flags", flags as Any), ("botId", botId as Any), ("recipients", recipients as Any), ("rights", rights as Any)])
}
}
@ -695,11 +696,16 @@ public extension Api {
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.BusinessBotRecipients
}
var _4: Api.BusinessBotRights?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.BusinessBotRights
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.ConnectedBot.connectedBot(flags: _1!, botId: _2!, recipients: _3!)
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.ConnectedBot.connectedBot(flags: _1!, botId: _2!, recipients: _3!, rights: _4!)
}
else {
return nil

View file

@ -73,13 +73,13 @@ public class UnauthorizedAccount {
public let testingEnvironment: Bool
public let postbox: Postbox
public let network: Network
private let stateManager: UnauthorizedAccountStateManager
let stateManager: UnauthorizedAccountStateManager
private let updateLoginTokenPipe = ValuePipe<Void>()
public var updateLoginTokenEvents: Signal<Void, NoError> {
return self.updateLoginTokenPipe.signal()
}
private let serviceNotificationPipe = ValuePipe<String>()
public var serviceNotificationEvents: Signal<String, NoError> {
return self.serviceNotificationPipe.signal()
@ -91,7 +91,7 @@ public class UnauthorizedAccount {
public let shouldBeServiceTaskMaster = Promise<AccountServiceTaskMasterMode>()
init(networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) {
init(accountManager: AccountManager<TelegramAccountManagerTypes>, networkArguments: NetworkInitializationArguments, id: AccountRecordId, rootPath: String, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, shouldKeepAutoConnection: Bool = true) {
self.networkArguments = networkArguments
self.id = id
self.rootPath = rootPath
@ -101,11 +101,84 @@ public class UnauthorizedAccount {
self.network = network
let updateLoginTokenPipe = self.updateLoginTokenPipe
let serviceNotificationPipe = self.serviceNotificationPipe
self.stateManager = UnauthorizedAccountStateManager(network: network, updateLoginToken: {
updateLoginTokenPipe.putNext(Void())
}, displayServiceNotification: { text in
serviceNotificationPipe.putNext(text)
})
let masterDatacenterId = Int32(network.mtProto.datacenterId)
var updateSentCodeImpl: ((Api.auth.SentCode) -> Void)?
self.stateManager = UnauthorizedAccountStateManager(
network: network,
updateLoginToken: {
updateLoginTokenPipe.putNext(Void())
},
updateSentCode: { sentCode in
updateSentCodeImpl?(sentCode)
},
displayServiceNotification: { text in
serviceNotificationPipe.putNext(text)
}
)
updateSentCodeImpl = { [weak self] sentCode in
switch sentCode {
case .sentCodePaymentRequired:
break
case let .sentCode(_, type, phoneCodeHash, nextType, codeTimeout):
let _ = postbox.transaction({ transaction in
var parsedNextType: AuthorizationCodeNextType?
if let nextType = nextType {
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(phoneNumber, _, _, syncContacts) = state.contents {
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
}
}).start()
case let .sentCodeSuccess(authorization):
switch authorization {
case let .authorization(_, _, _, futureAuthToken, user):
let _ = postbox.transaction({ [weak self] transaction in
var syncContacts = true
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(_, _, _, syncContactsValue) = state.contents {
syncContacts = syncContactsValue
}
if let futureAuthToken = futureAuthToken {
storeFutureLoginToken(accountManager: accountManager, token: futureAuthToken.makeData())
}
let user = TelegramUser(user: user)
var isSupportUser = false
if let phone = user.phone, phone.hasPrefix("42"), phone.count <= 5 {
isSupportUser = true
}
let state = AuthorizedAccountState(isTestingEnvironment: testingEnvironment, masterDatacenterId: masterDatacenterId, peerId: user.id, state: nil, invalidatedChannels: [])
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { [weak self] transaction -> SendAuthorizationCodeResult in
if let self {
switchToAuthorizedAccount(transaction: transaction, account: self, isSupportUser: isSupportUser)
}
return .loggedIn
}
}).start()
case let .authorizationSignUpRequired(_, termsOfService):
let _ = postbox.transaction({ [weak self] transaction in
if let self {
if let state = transaction.getState() as? UnauthorizedAccountState, case let .payment(number, codeHash, _, syncContacts) = state.contents {
let _ = beginSignUp(
account: self,
data: AuthorizationSignUpData(
number: number,
codeHash: codeHash,
code: .phoneCode(""),
termsOfService: termsOfService.flatMap(UnauthorizedAccountTermsOfService.init(apiTermsOfService:)),
syncContacts: syncContacts
)
).start()
}
}
}).start()
}
}
}
network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get()
|> map { mode -> Bool in
@ -152,7 +225,7 @@ public class UnauthorizedAccount {
|> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<UnauthorizedAccount, NoError> in
return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false, appConfiguration: appConfiguration)
|> map { network in
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
let updated = UnauthorizedAccount(accountManager: accountManager, networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
return updated
}
@ -250,7 +323,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
case let unauthorizedState as UnauthorizedAccountState:
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
return .unauthorized(UnauthorizedAccount(accountManager: accountManager, networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
}
case let authorizedState as AuthorizedAccountState:
return postbox.transaction { transaction -> String? in
@ -269,7 +342,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
return .unauthorized(UnauthorizedAccount(accountManager: accountManager, networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
}
}
}

View file

@ -227,7 +227,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
}
switch action {
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique:
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice:
break
case let .messageActionChannelMigrateFrom(_, chatId):
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)))

View file

@ -191,6 +191,10 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return nil
}
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId))
case let .messageActionPaidMessagesRefunded(count, stars):
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))
case let .messageActionPaidMessagesPrice(stars):
return TelegramMediaAction(action: .paidMessagesPriceEdited(stars: stars))
}
}

View file

@ -356,6 +356,8 @@ public func sendAuthorizationCode(accountManager: AccountManager<TelegramAccount
}
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
case .sentCodePaymentRequired:
return .never()
case let .sentCodeSuccess(authorization):
switch authorization {
case let .authorization(_, otherwiseReloginDays, _, futureAuthToken, user):
@ -515,6 +517,9 @@ private func internalResendAuthorizationCode(accountManager: AccountManager<Tele
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
return .single(.sentCode(account))
case let .sentCodePaymentRequired(storeProduct, codeHash):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
return .single(.sentCode(account))
case .sentCodeSuccess:
return .single(.loggedIn)
@ -621,6 +626,8 @@ public func resendAuthorizationCode(accountManager: AccountManager<TelegramAccou
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: number, type: parsedType, hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: previousCodeEntry, usePrevious: false)))
case let .sentCodePaymentRequired(storeProduct, codeHash):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: number, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
case .sentCodeSuccess:
break
}
@ -898,6 +905,8 @@ public func verifyLoginEmailSetup(account: UnauthorizedAccount, code: Authorizat
}
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
case let .sentCodePaymentRequired(storeProduct, codeHash):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
case .sentCodeSuccess:
break
}
@ -960,6 +969,9 @@ public func resetLoginEmail(account: UnauthorizedAccount, phoneNumber: String, p
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .confirmationCodeEntry(number: phoneNumber, type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType, syncContacts: syncContacts, previousCodeEntry: nil, usePrevious: false)))
return .complete()
case let .sentCodePaymentRequired(storeProduct, codeHash):
transaction.setState(UnauthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, contents: .payment(number: phoneNumber, codeHash: codeHash, storeProduct: storeProduct, syncContacts: syncContacts)))
return .complete()
case .sentCodeSuccess:
return .complete()

View file

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

View file

@ -53,11 +53,18 @@ final class UnauthorizedAccountStateManager {
private var updateService: UnauthorizedUpdateMessageService?
private let updateServiceDisposable = MetaDisposable()
private let updateLoginToken: () -> Void
private let updateSentCode: (Api.auth.SentCode) -> Void
private let displayServiceNotification: (String) -> Void
init(network: Network, updateLoginToken: @escaping () -> Void, displayServiceNotification: @escaping (String) -> Void) {
init(
network: Network,
updateLoginToken: @escaping () -> Void,
updateSentCode: @escaping (Api.auth.SentCode) -> Void,
displayServiceNotification: @escaping (String) -> Void
) {
self.network = network
self.updateLoginToken = updateLoginToken
self.updateSentCode = updateSentCode
self.displayServiceNotification = displayServiceNotification
}
@ -65,11 +72,18 @@ final class UnauthorizedAccountStateManager {
self.updateServiceDisposable.dispose()
}
func addUpdates(_ updates: Api.Updates) {
self.queue.async {
self.updateService?.addUpdates(updates)
}
}
func reset() {
self.queue.async {
if self.updateService == nil {
self.updateService = UnauthorizedUpdateMessageService()
let updateLoginToken = self.updateLoginToken
let updateSentCode = self.updateSentCode
let displayServiceNotification = self.displayServiceNotification
self.updateServiceDisposable.set(self.updateService!.pipe.signal().start(next: { updates in
for update in updates {
@ -81,6 +95,8 @@ final class UnauthorizedAccountStateManager {
if popup {
displayServiceNotification(message)
}
case let .updateSentPhoneCode(sentCode):
updateSentCode(sentCode)
default:
break
}

View file

@ -132,6 +132,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
case paidMessagesRefunded(count: Int32, stars: Int64)
case paidMessagesPriceEdited(stars: Int64)
public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -256,6 +258,10 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"))
case 45:
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"))
case 46:
self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0))
case 47:
self = .paidMessagesPriceEdited(stars: decoder.decodeInt64ForKey("stars", orElse: 0))
default:
self = .unknown
}
@ -626,6 +632,13 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else {
encoder.encodeNil(forKey: "savedId")
}
case let .paidMessagesRefunded(count, stars):
encoder.encodeInt32(46, forKey: "_rawValue")
encoder.encodeInt32(count, forKey: "count")
encoder.encodeInt64(stars, forKey: "stars")
case let .paidMessagesPriceEdited(stars):
encoder.encodeInt32(47, forKey: "_rawValue")
encoder.encodeInt64(stars, forKey: "stars")
}
}

View file

@ -139,6 +139,7 @@ private enum UnauthorizedAccountStateContentsValue: Int32 {
case signUp = 5
case passwordRecovery = 6
case awaitingAccountReset = 7
case payment = 8
}
public struct UnauthorizedAccountTermsOfService: PostboxCoding, Equatable {
@ -181,6 +182,7 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
case passwordRecovery(hint: String, number: String?, code: AuthorizationCode?, emailPattern: String, syncContacts: Bool)
case awaitingAccountReset(protectedUntil: Int32, number: String?, syncContacts: Bool)
case signUp(number: String, codeHash: String, firstName: String, lastName: String, termsOfService: UnauthorizedAccountTermsOfService?, syncContacts: Bool)
case payment(number: String, codeHash: String, storeProduct: String, syncContacts: Bool)
public init(decoder: PostboxDecoder) {
switch decoder.decodeInt32ForKey("v", orElse: 0) {
@ -214,6 +216,8 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
self = .awaitingAccountReset(protectedUntil: decoder.decodeInt32ForKey("protectedUntil", orElse: 0), number: decoder.decodeOptionalStringForKey("number"), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
case UnauthorizedAccountStateContentsValue.signUp.rawValue:
self = .signUp(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), firstName: decoder.decodeStringForKey("f", orElse: ""), lastName: decoder.decodeStringForKey("l", orElse: ""), termsOfService: decoder.decodeObjectForKey("tos", decoder: { UnauthorizedAccountTermsOfService(decoder: $0) }) as? UnauthorizedAccountTermsOfService, syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
case UnauthorizedAccountStateContentsValue.payment.rawValue:
self = .payment(number: decoder.decodeStringForKey("n", orElse: ""), codeHash: decoder.decodeStringForKey("h", orElse: ""), storeProduct: decoder.decodeStringForKey("storeProduct", orElse: ""), syncContacts: decoder.decodeInt32ForKey("syncContacts", orElse: 1) != 0)
default:
assertionFailure()
self = .empty
@ -303,6 +307,12 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
encoder.encodeNil(forKey: "tos")
}
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
case let .payment(number, codeHash, storeProduct, syncContacts):
encoder.encodeInt32(UnauthorizedAccountStateContentsValue.payment.rawValue, forKey: "v")
encoder.encodeString(number, forKey: "n")
encoder.encodeString(codeHash, forKey: "h")
encoder.encodeString(storeProduct, forKey: "storeProduct")
encoder.encodeInt32(syncContacts ? 1 : 0, forKey: "syncContacts")
}
}
@ -374,6 +384,12 @@ public indirect enum UnauthorizedAccountStateContents: PostboxCoding, Equatable
} else {
return false
}
case let .payment(number, codeHash, storeProduct, syncContacts):
if case .payment(number, codeHash, storeProduct, syncContacts) = rhs {
return true
} else {
return false
}
}
}
}

View file

@ -113,7 +113,7 @@ func _internal_requestChangeAccountPhoneNumberVerification(account: Account, api
} else {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
}
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
@ -188,7 +188,7 @@ private func internalResendChangeAccountPhoneNumberVerification(account: Account
} else {
return .single(ChangeAccountPhoneNumberData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: codeTimeout, nextType: parsedNextType))
}
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}

View file

@ -34,7 +34,7 @@ func _internal_requestCancelAccountResetData(network: Network, hash: String) ->
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}
@ -57,7 +57,7 @@ func _internal_requestNextCancelAccountResetOption(network: Network, phoneNumber
parsedNextType = AuthorizationCodeNextType(apiType: nextType)
}
return .single(CancelAccountResetData(type: SentAuthorizationCodeType(apiType: type), hash: phoneCodeHash, timeout: timeout, nextType: parsedNextType))
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}

View file

@ -38,3 +38,44 @@ public extension TelegramEngine {
}
}
}
public extension TelegramEngineUnauthorized {
final class ItemCache {
private let account: UnauthorizedAccount
init(account: UnauthorizedAccount) {
self.account = account
}
public func put<T: Codable>(collectionId: Int8, id: EngineDataBuffer, item: T) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
if let entry = CodableEntry(item) {
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: collectionId, key: id), entry: entry)
}
}
|> ignoreValues
}
public func get(collectionId: Int8, id: EngineDataBuffer) -> Signal<CodableEntry?, NoError> {
return self.account.postbox.transaction { transaction -> CodableEntry? in
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: collectionId, key: id))
}
}
public func remove(collectionId: Int8, id: EngineDataBuffer) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
transaction.removeItemCacheEntry(id: ItemCacheEntryId(collectionId: collectionId, key: id))
}
|> ignoreValues
}
public func clear(collectionIds: [Int8]) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in
for id in collectionIds {
transaction.clearItemCacheCollection(collectionId: id)
}
}
|> ignoreValues
}
}
}

View file

@ -1072,7 +1072,7 @@ public func _internal_setAccountConnectedBot(account: Account, bot: TelegramAcco
flags |= 1 << 1
}
return account.network.request(Api.functions.account.updateConnectedBot(flags: flags, bot: mappedBot, recipients: mappedRecipients))
return account.network.request(Api.functions.account.updateConnectedBot(flags: flags, rights: nil, bot: mappedBot, recipients: mappedRecipients))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)

View file

@ -109,7 +109,14 @@ func _internal_peerSendAsAvailablePeers(accountPeerId: PeerId, network: Network,
return .single([])
}
if let channel = peer as? TelegramChannel, case .group = channel.info {
if let channel = peer as? TelegramChannel {
if case .group = channel.info {
} else if channel.adminRights != nil || channel.flags.contains(.isCreator) {
} else {
return .single([])
}
} else {
return .single([])
}

View file

@ -20,9 +20,10 @@ public enum AppStoreTransactionPurpose {
case stars(count: Int64, currency: String, amount: Int64)
case starsGift(peerId: EnginePeer.Id, count: Int64, currency: String, amount: Int64)
case starsGiveaway(stars: Int64, boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, showWinners: Bool, prizeDescription: String?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64, users: Int32)
case authCode(restore: Bool, phoneNumber: String, phoneCodeHash: String, currency: String, amount: Int64)
}
private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
private func apiInputStorePaymentPurpose(postbox: Postbox, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
switch purpose {
case .subscription, .upgrade, .restore:
var flags: Int32 = 0
@ -36,7 +37,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
}
return .single(.inputStorePaymentPremiumSubscription(flags: flags))
case let .gift(peerId, currency, amount):
return account.postbox.loadedPeerWithId(peerId)
return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let inputUser = apiInputUser(peer) else {
return .complete()
@ -44,7 +45,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .single(.inputStorePaymentGiftPremium(userId: inputUser, currency: currency, amount: amount))
}
case let .giftCode(peerIds, boostPeerId, currency, amount, text, entities):
return account.postbox.transaction { transaction -> Api.InputStorePaymentPurpose in
return postbox.transaction { transaction -> Api.InputStorePaymentPurpose in
var flags: Int32 = 0
var apiBoostPeer: Api.InputPeer?
var apiInputUsers: [Api.InputUser] = []
@ -69,7 +70,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
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):
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
return postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return .complete()
}
@ -101,7 +102,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
case let .stars(count, currency, amount):
return .single(.inputStorePaymentStarsTopup(stars: count, currency: currency, amount: amount))
case let .starsGift(peerId, count, currency, amount):
return account.postbox.loadedPeerWithId(peerId)
return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let inputUser = apiInputUser(peer) else {
return .complete()
@ -109,7 +110,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .single(.inputStorePaymentStarsGift(userId: inputUser, stars: count, currency: currency, amount: amount))
}
case let .starsGiveaway(stars, boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, showWinners, prizeDescription, randomId, untilDate, currency, amount, users):
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
return postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
return .complete()
}
@ -138,14 +139,20 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
return .single(.inputStorePaymentStarsGiveaway(flags: flags, stars: stars, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, prizeDescription: prizeDescription, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount, users: users))
}
|> switchToLatest
case let .authCode(restore, phoneNumber, phoneCodeHash, currency, amount):
var flags: Int32 = 0
if restore {
flags |= (1 << 0)
}
return .single(.inputStorePaymentAuthCode(flags: flags, phoneNumber: phoneNumber, phoneCodeHash: phoneCodeHash, currency: currency, amount: amount))
}
}
func _internal_sendAppStoreReceipt(account: Account, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return apiInputStorePaymentPurpose(account: account, purpose: purpose)
func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: AccountStateManager, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
return account.network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose))
return network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose))
|> mapError { error -> AssignAppStoreTransactionError in
if error.errorCode == 406 {
return .serverProvided
@ -154,7 +161,26 @@ func _internal_sendAppStoreReceipt(account: Account, receipt: Data, purpose: App
}
}
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
account.stateManager.addUpdates(updates)
stateManager.addUpdates(updates)
return .complete()
}
}
}
func _internal_sendAppStoreReceipt(postbox: Postbox, network: Network, stateManager: UnauthorizedAccountStateManager, receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> castError(AssignAppStoreTransactionError.self)
|> mapToSignal { purpose -> Signal<Never, AssignAppStoreTransactionError> in
return network.request(Api.functions.payments.assignAppStoreTransaction(receipt: Buffer(data: receipt), purpose: purpose))
|> mapError { error -> AssignAppStoreTransactionError in
if error.errorCode == 406 {
return .serverProvided
} else {
return .generic
}
}
|> mapToSignal { updates -> Signal<Never, AssignAppStoreTransactionError> in
stateManager.addUpdates(updates)
return .complete()
}
}
@ -164,10 +190,10 @@ public enum RestoreAppStoreReceiptError {
case generic
}
func _internal_canPurchasePremium(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return apiInputStorePaymentPurpose(account: account, purpose: purpose)
func _internal_canPurchasePremium(postbox: Postbox, network: Network, purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return apiInputStorePaymentPurpose(postbox: postbox, purpose: purpose)
|> mapToSignal { purpose -> Signal<Bool, NoError> in
return account.network.request(Api.functions.payments.canPurchasePremium(purpose: purpose))
return network.request(Api.functions.payments.canPurchaseStore(purpose: purpose))
|> map { result -> Bool in
switch result {
case .boolTrue:

View file

@ -395,7 +395,7 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv
var flags: Int32 = 0
var message: Api.TextWithEntities?
if let text, !text.isEmpty {
flags |= (1 << 1)
flags |= (1 << 0)
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
}
return .inputInvoicePremiumGiftStars(flags: flags, userId: inputUser, months: option.months, message: message)

View file

@ -568,6 +568,21 @@ private final class StarsContextImpl {
self._state = state
self._statePromise.set(.single(state))
}
var onUpdate: Signal<Void, NoError> {
return self._statePromise.get()
|> take(until: { value in
if let value {
if !value.flags.contains(.isPendingBalance) {
return SignalTakeAction(passthrough: true, complete: true)
}
}
return SignalTakeAction(passthrough: false, complete: false)
})
|> map { _ in
return Void()
}
}
}
private extension StarsContext.State.Transaction {
@ -1011,6 +1026,17 @@ public final class StarsContext {
}
}
public var onUpdate: Signal<Void, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.onUpdate.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
init(account: Account) {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {

View file

@ -39,11 +39,11 @@ public extension TelegramEngine {
}
public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return _internal_sendAppStoreReceipt(account: self.account, receipt: receipt, purpose: purpose)
return _internal_sendAppStoreReceipt(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, receipt: receipt, purpose: purpose)
}
public func canPurchasePremium(purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return _internal_canPurchasePremium(account: self.account, purpose: purpose)
return _internal_canPurchasePremium(postbox: self.account.postbox, network: self.account.network, purpose: purpose)
}
public func checkPremiumGiftCode(slug: String) -> Signal<PremiumGiftCodeInfo?, NoError> {
@ -150,3 +150,21 @@ public extension TelegramEngine {
}
}
}
public extension TelegramEngineUnauthorized {
final class Payments {
private let account: UnauthorizedAccount
init(account: UnauthorizedAccount) {
self.account = account
}
public func canPurchasePremium(purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
return _internal_canPurchasePremium(postbox: self.account.postbox, network: self.account.network, purpose: purpose)
}
public func sendAppStoreReceipt(receipt: Data, purpose: AppStoreTransactionPurpose) -> Signal<Never, AssignAppStoreTransactionError> {
return _internal_sendAppStoreReceipt(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, receipt: receipt, purpose: purpose)
}
}
}

View file

@ -246,7 +246,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
if let apiBot = connectedBots.first {
switch apiBot {
case let .connectedBot(flags, botId, recipients):
case let .connectedBot(flags, botId, recipients, _):
mappedConnectedBot = TelegramAccountConnectedBot(
id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)),
recipients: TelegramBusinessRecipients(apiValue: recipients),

View file

@ -30,7 +30,7 @@ public func secureIdPreparePhoneVerification(network: Network, value: SecureIdPh
switch sentCode {
case let .sentCode(_, type, phoneCodeHash, nextType, timeout):
return .single(SecureIdPreparePhoneVerificationPayload(type: SentAuthorizationCodeType(apiType: type), nextType: nextType.flatMap(AuthorizationCodeNextType.init), timeout: timeout, phone: value.phone, phoneCodeHash: phoneCodeHash))
case .sentCodeSuccess:
case .sentCodeSuccess, .sentCodePaymentRequired:
return .never()
}
}

View file

@ -107,6 +107,14 @@ public final class TelegramEngineUnauthorized {
public lazy var localization: Localization = {
return Localization(account: self.account)
}()
public lazy var payments: Payments = {
return Payments(account: self.account)
}()
public lazy var itemCache: ItemCache = {
return ItemCache(account: self.account)
}()
}
public enum SomeTelegramEngine {

View file

@ -170,6 +170,12 @@ public final class PrincipalThemeEssentialGraphics {
public let outgoingDateAndStatusRepliesIcon: UIImage
public let mediaRepliesIcon: UIImage
public let freeRepliesIcon: UIImage
public let incomingDateAndStatusStarsIcon: UIImage
public let outgoingDateAndStatusStarsIcon: UIImage
public let mediaStarsIcon: UIImage
public let freeStarsIcon: UIImage
public let incomingDateAndStatusPinnedIcon: UIImage
public let outgoingDateAndStatusPinnedIcon: UIImage
public let mediaPinnedIcon: UIImage
@ -358,6 +364,12 @@ public final class PrincipalThemeEssentialGraphics {
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
let starsImage = UIImage(bundleImageName: "Chat/Message/StarsCount")!
self.incomingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
@ -479,6 +491,12 @@ public final class PrincipalThemeEssentialGraphics {
self.mediaRepliesIcon = generateTintedImage(image: repliesImage, color: .white)!
self.freeRepliesIcon = generateTintedImage(image: repliesImage, color: serviceColor.primaryText)!
let starsImage = UIImage(bundleImageName: "Chat/Message/StarsCount")!
self.incomingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!

View file

@ -1196,6 +1196,33 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
}
}
case let .paidMessagesRefunded(_, stars):
let starsString = strings.Notification_PaidMessageRefund_Stars(Int32(stars))
if message.author?.id == accountPeerId, let messagePeer = message.peers[message.id.peerId] {
let peerName = EnginePeer(messagePeer).compactDisplayTitle
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(1, messagePeer.id)])
attributes[0] = boldAttributes
let resultString = strings.Notification_PaidMessageRefundYou(starsString, peerName)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
} else {
let peerName = message.author?.compactDisplayTitle ?? ""
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
attributes[1] = boldAttributes
let resultString = strings.Notification_PaidMessageRefund(peerName, starsString)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
}
case let .paidMessagesPriceEdited(stars):
let starsString = strings.Notification_PaidMessagePriceChanged_Stars(Int32(stars))
if message.author?.id == accountPeerId {
let resultString = strings.Notification_PaidMessagePriceChangedYou(starsString)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
} else {
let peerName = message.author?.compactDisplayTitle ?? ""
var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])
attributes[1] = boldAttributes
let resultString = strings.Notification_PaidMessagePriceChanged(peerName, starsString)
attributedString = addAttributesToStringWithRanges(resultString._tuple, body: bodyAttributes, argumentAttributes: attributes)
}
case .unknown:
attributedString = nil
}

View file

@ -81,7 +81,7 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
public func formatStarsAmountText(_ amount: StarsAmount, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String {
var balanceText = presentationStringsFormattedNumber(Int32(amount.value), dateTimeFormat.groupingSeparator)
let fraction = Double(amount.nanos) / 10e6
let fraction = abs(Double(amount.nanos)) / 10e6
if fraction > 0.0 {
balanceText.append(dateTimeFormat.decimalSeparator)
balanceText.append("\(Int32(fraction))")

View file

@ -466,6 +466,7 @@ swift_library(
"//submodules/TelegramUI/Components/ContentReportScreen",
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
"//submodules/TelegramUI/Components/Settings/AccountFreezeInfoScreen",
"//third-party/recaptcha:RecaptchaEnterprise",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,

View file

@ -193,6 +193,7 @@ private final class ScrollContent: CombinedComponent {
adsText = strings.AdsInfo_Bot_Ads_Text
infoRawText = strings.AdsInfo_Bot_Launch_Text
case .search:
//TODO:localize
respectText = "Ads like this do not use your personal information and are based on the search query you entered."
adsText = strings.AdsInfo_Bot_Ads_Text
infoRawText = "Anyone can create an ad to display in search results for any query. Check out the Telegram Ad Platform for details. [Learn More >]()"

View file

@ -1213,9 +1213,9 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
private var currentTheme: PresentationTheme?
private var currentStrings: PresentationStrings?
private let stars: StarsAmount?
private let stars: Int64?
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?, stars: StarsAmount?) {
public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?, stars: Int64?) {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled
self.stars = stars
@ -1295,7 +1295,7 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
}
)
if let amount = self.stars {
let starsString = presentationStringsFormattedNumber(Int32(amount.value), interfaceState.dateTimeFormat.groupingSeparator)
let starsString = presentationStringsFormattedNumber(Int32(amount), interfaceState.dateTimeFormat.groupingSeparator)
let rawText: String
if self.isPremiumDisabled {
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
@ -1426,7 +1426,7 @@ private enum ChatEmptyNodeContentType: Equatable {
case greeting
case topic
case premiumRequired
case starsRequired
case starsRequired(Int64)
}
private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
@ -1787,7 +1787,7 @@ public final class ChatEmptyNode: ASDisplayNode {
isScheduledMessages = true
}
let contentType: ChatEmptyNodeContentType
var contentType: ChatEmptyNodeContentType
var displayAttachedDescription = false
switch subject {
case .detailsPlaceholder:
@ -1815,8 +1815,8 @@ public final class ChatEmptyNode: ASDisplayNode {
} else if let _ = interfaceState.peerNearbyData {
contentType = .peerNearby
} else if let peer = peer as? TelegramUser {
if let _ = interfaceState.sendPaidMessageStars {
contentType = .starsRequired
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.businessIntro == nil {
contentType = .starsRequired(sendPaidMessageStars.value)
} else if interfaceState.isPremiumRequiredForMessaging {
contentType = .premiumRequired
} else {
@ -1881,8 +1881,8 @@ public final class ChatEmptyNode: ASDisplayNode {
node = ChatEmptyNodeTopicChatContent(context: self.context)
case .premiumRequired:
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil)
case .starsRequired:
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: interfaceState.sendPaidMessageStars)
case let .starsRequired(stars):
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars)
}
self.content = (contentType, node)
self.addSubnode(node)
@ -1893,8 +1893,13 @@ public final class ChatEmptyNode: ASDisplayNode {
node.layer.animateScale(from: 0.0, to: 1.0, duration: duration, timingFunction: curve.timingFunction)
}
}
self.isUserInteractionEnabled = [.peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud].contains(contentType)
switch contentType {
case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud:
self.isUserInteractionEnabled = true
default:
self.isUserInteractionEnabled = false
}
let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
var contentSize = CGSize()

View file

@ -687,6 +687,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
openPhotoSetup: {
},
openAdInfo: { _ in
},
openAccountFreezeInfo: {
}
)
self.chatListNodeInteraction = chatListNodeInteraction

View file

@ -1044,6 +1044,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var edited = false
var viewCount: Int? = nil
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -1057,6 +1058,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -1086,6 +1089,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: messageEffect,
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.message),

View file

@ -675,6 +675,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: context.account.peerId, accountPeer: associatedData.accountPeer, message: message)
if message.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) || presentationData.isPreview {
dateReactionsAndPeers = ([], [])
@ -688,6 +689,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -747,6 +750,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId),
messageEffect: message.messageEffect(availableMessageEffects: associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: message.tags.contains(.pinned) && !associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: message),

View file

@ -130,7 +130,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
result.append((message, ChatMessageRestrictedBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false
break outer
} else if let _ = attribute as? PaidStarsMessageAttribute, !addedPriceInfo {
} else if let _ = attribute as? PaidStarsMessageAttribute, !addedPriceInfo, message.id.peerId.namespace == Namespaces.Peer.CloudUser {
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
addedPriceInfo = true
}
@ -299,7 +299,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
}
if isMediaInverted {
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: 0)
result.insert((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)), at: addedPriceInfo ? 1 : 0)
} else {
result.append((message, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: isFile ? .condensed : .default)))
needReactions = false
@ -327,7 +327,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
}
if let attribute = message.attributes.first(where: { $0 is WebpagePreviewMessageAttribute }) as? WebpagePreviewMessageAttribute, attribute.leadingPreview {
result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0)
result.insert((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: addedPriceInfo ? 1 : 0)
} else {
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
}
@ -362,7 +362,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
if result.isEmpty {
needReactions = false
}
result.insert((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: 0)
result.insert((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)), at: addedPriceInfo ? 1 : 0)
} else {
result.append((messageWithCaptionToAdd, ChatMessageTextBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false
@ -2276,6 +2276,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: message)
if message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -2289,6 +2290,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -2337,6 +2340,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: message),

View file

@ -233,6 +233,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -246,6 +247,8 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -300,6 +303,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: messageEffect,
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View file

@ -195,6 +195,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
var areReactionsTags: Bool
var messageEffect: AvailableMessageEffects.MessageEffect?
var replyCount: Int
var starsCount: Int64?
var isPinned: Bool
var hasAutoremove: Bool
var canViewReactionList: Bool
@ -218,6 +219,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
areReactionsTags: Bool,
messageEffect: AvailableMessageEffects.MessageEffect?,
replyCount: Int,
starsCount: Int64?,
isPinned: Bool,
hasAutoremove: Bool,
canViewReactionList: Bool,
@ -240,6 +242,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
self.areReactionsTags = areReactionsTags
self.messageEffect = messageEffect
self.replyCount = replyCount
self.starsCount = starsCount
self.isPinned = isPinned
self.hasAutoremove = hasAutoremove
self.canViewReactionList = canViewReactionList
@ -262,7 +265,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
private var repliesIcon: ASImageNode?
private var selfExpiringIcon: ASImageNode?
private var replyCountNode: TextNode?
private var starsIcon: ASImageNode?
private var starsCountNode: TextNode?
private var type: ChatMessageDateAndStatusType?
private var theme: ChatPresentationThemeData?
private var layoutSize: CGSize?
@ -316,12 +321,14 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
var currentBackgroundNode = self.backgroundNode
var currentImpressionIcon = self.impressionIcon
var currentRepliesIcon = self.repliesIcon
var currentStarsIcon = self.starsIcon
let currentType = self.type
let currentTheme = self.theme
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
let makeStarsCountLayout = TextNode.asyncLayout(self.starsCountNode)
let reactionButtonsContainer = self.reactionButtonsContainer
return { [weak self] arguments in
@ -337,7 +344,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
let clockMinImage: UIImage?
var impressionImage: UIImage?
var repliesImage: UIImage?
var starsImage: UIImage?
let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType
let graphics = PresentationResourcesChat.principalGraphics(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, bubbleCorners: arguments.presentationData.chatBubbleCorners)
@ -404,6 +412,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.incomingDateAndStatusPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.incomingDateAndStatusStarsIcon
}
case let .BubbleOutgoing(status):
dateColor = arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
outgoingStatus = status
@ -420,6 +431,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.outgoingDateAndStatusPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.outgoingDateAndStatusStarsIcon
}
case .ImageIncoming:
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
backgroundImage = graphics.dateAndStatusMediaBackground
@ -436,6 +450,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.mediaPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.mediaStarsIcon
}
case let .ImageOutgoing(status):
dateColor = arguments.presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor
outgoingStatus = status
@ -453,6 +470,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.mediaPinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.mediaStarsIcon
}
case .FreeIncoming:
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
dateColor = serviceColor.primaryText
@ -471,6 +491,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.freePinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.freeStarsIcon
}
case let .FreeOutgoing(status):
let serviceColor = serviceMessageColorComponents(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper)
dateColor = serviceColor.primaryText
@ -489,6 +512,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else if arguments.isPinned {
repliesImage = graphics.freePinnedIcon
}
if (arguments.starsCount ?? 0) != 0 {
starsImage = graphics.freeStarsIcon
}
}
var updatedDateText = arguments.dateText
@ -541,6 +567,20 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
currentRepliesIcon = nil
}
var starsIconSize = CGSize()
if let starsImage = starsImage {
if currentStarsIcon == nil {
let iconNode = ASImageNode()
iconNode.isLayerBacked = true
iconNode.displayWithoutProcessing = true
iconNode.displaysAsynchronously = false
currentStarsIcon = iconNode
}
starsIconSize = starsImage.size
} else {
currentStarsIcon = nil
}
if let outgoingStatus = outgoingStatus {
switch outgoingStatus {
case .Sending:
@ -652,7 +692,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
}
var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
var starsCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
let reactionSize: CGFloat = 8.0
let reactionSpacing: CGFloat = 2.0
let reactionTrailingSpacing: CGFloat = 6.0
@ -671,11 +712,29 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
if arguments.starsCount != nil {
reactionInset += 3.0
}
replyCountLayoutAndApply = layoutAndApply
} else if arguments.isPinned {
reactionInset += 12.0
}
if let starsCount = arguments.starsCount, starsCount > 0 {
let countString: String
if starsCount > 1000000 {
countString = "\(starsCount / 1000000)M"
} else if starsCount > 1000 {
countString = "\(starsCount / 1000)K"
} else {
countString = "\(starsCount)"
}
let layoutAndApply = makeStarsCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
starsCountLayoutAndApply = layoutAndApply
}
if arguments.messageEffect != nil {
reactionInset += 13.0
}
@ -1227,6 +1286,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
let replyCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
animation.animator.updateFrame(layer: node.layer, frame: replyCountFrame, completion: nil)
reactionOffset += 4.0 + layout.size.width
if currentStarsIcon != nil {
reactionOffset += 8.0
}
} else if let replyCountNode = strongSelf.replyCountNode {
strongSelf.replyCountNode = nil
if animation.isAnimated {
@ -1237,6 +1299,56 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
replyCountNode.removeFromSupernode()
}
}
if let currentStarsIcon = currentStarsIcon {
currentStarsIcon.displaysAsynchronously = false
if currentStarsIcon.image !== starsImage {
currentStarsIcon.image = starsImage
}
if currentStarsIcon.supernode == nil {
strongSelf.starsIcon = currentStarsIcon
strongSelf.addSubnode(currentStarsIcon)
if animation.isAnimated {
currentStarsIcon.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
let starsIconFrame = CGRect(origin: CGPoint(x: reactionOffset - 2.0, y: backgroundInsets.top + offset + verticalInset + floor((date.size.height - starsIconSize.height) / 2.0)), size: starsIconSize)
animation.animator.updateFrame(layer: currentStarsIcon.layer, frame: starsIconFrame, completion: nil)
reactionOffset += 9.0
} else if let starsIcon = strongSelf.starsIcon {
strongSelf.starsIcon = nil
if animation.isAnimated {
starsIcon.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak starsIcon] _ in
starsIcon?.removeFromSupernode()
})
} else {
starsIcon.removeFromSupernode()
}
}
if let (layout, apply) = starsCountLayoutAndApply {
let node = apply()
if strongSelf.starsCountNode !== node {
strongSelf.starsCountNode?.removeFromSupernode()
strongSelf.addSubnode(node)
strongSelf.starsCountNode = node
if animation.isAnimated {
node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
}
let starsCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
animation.animator.updateFrame(layer: node.layer, frame: starsCountFrame, completion: nil)
reactionOffset += 4.0 + layout.size.width
} else if let starsCountNode = strongSelf.starsCountNode {
strongSelf.starsCountNode = nil
if animation.isAnimated {
starsCountNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak starsCountNode] _ in
starsCountNode?.removeFromSupernode()
})
} else {
starsCountNode.removeFromSupernode()
}
}
}
})
})

View file

@ -295,6 +295,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
var rawText = ""
var rawEntities: [MessageTextEntity] = []
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -311,6 +312,8 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -447,6 +450,7 @@ public class ChatMessageFactCheckBubbleContentNode: ChatMessageBubbleContentNode
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View file

@ -578,6 +578,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: nil,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View file

@ -898,6 +898,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: arguments.context.account.peerId, accountPeer: arguments.associatedData.accountPeer, message: arguments.topMessage)
if arguments.topMessage.isRestricted(platform: "ios", contentSettings: arguments.context.currentContentSettings.with { $0 }) || arguments.presentationData.isPreview {
dateReactionsAndPeers = ([], [])
@ -911,6 +912,8 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
if let channel = arguments.message.peers[arguments.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, arguments.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
if arguments.forcedIsEdited {
@ -956,6 +959,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode {
areReactionsTags: arguments.message.areReactionsTags(accountPeerId: arguments.context.account.peerId),
messageEffect: arguments.message.messageEffect(availableMessageEffects: arguments.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: arguments.isPinned && !arguments.associatedData.isInPinnedListMode,
hasAutoremove: arguments.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: arguments.topMessage),

View file

@ -524,6 +524,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
let sentViaBot = false
var viewCount: Int? = nil
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -537,6 +538,8 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -583,6 +586,7 @@ public class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: messageEffect,
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View file

@ -83,6 +83,7 @@ public struct ChatMessageDateAndStatus {
public var dateReactions: [MessageReaction]
public var dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)]
public var dateReplies: Int
public var starsCount: Int64?
public var isPinned: Bool
public var dateText: String
@ -93,6 +94,7 @@ public struct ChatMessageDateAndStatus {
dateReactions: [MessageReaction],
dateReactionPeers: [(MessageReaction.Reaction, EnginePeer)],
dateReplies: Int,
starsCount: Int64?,
isPinned: Bool,
dateText: String
) {
@ -102,6 +104,7 @@ public struct ChatMessageDateAndStatus {
self.dateReactions = dateReactions
self.dateReactionPeers = dateReactionPeers
self.dateReplies = dateReplies
self.starsCount = starsCount
self.isPinned = isPinned
self.dateText = dateText
}
@ -1118,6 +1121,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
areReactionsTags: message.areReactionsTags(accountPeerId: context.account.peerId),
messageEffect: messageEffect,
replyCount: dateAndStatus.dateReplies,
starsCount: dateAndStatus.starsCount,
isPinned: dateAndStatus.isPinned,
hasAutoremove: message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: message),

View file

@ -192,6 +192,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -205,6 +206,8 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -284,6 +287,7 @@ public class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View file

@ -307,6 +307,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -323,6 +324,8 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -373,6 +376,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
dateReactions: dateReactionsAndPeers.reactions,
dateReactionPeers: dateReactionsAndPeers.peers,
dateReplies: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
dateText: dateText
)

View file

@ -317,18 +317,20 @@ private final class ChatMessagePaymentAlertContentNode: AlertContentNode, ASGest
}
}
private class ChatMessagePaymentAlertController: AlertController {
public class ChatMessagePaymentAlertController: AlertController {
private let context: AccountContext?
private let presentationData: PresentationData
private weak var parentNavigationController: NavigationController?
private let showBalance: Bool
private let balance = ComponentView<Empty>()
init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?) {
public init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?, showBalance: Bool = true) {
self.context = context
self.presentationData = presentationData
self.parentNavigationController = navigationController
self.showBalance = showBalance
super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
self.willDismiss = { [weak self] in
@ -350,16 +352,16 @@ private class ChatMessagePaymentAlertController: AlertController {
}
}
override func dismissAnimated() {
public override func dismissAnimated() {
super.dismissAnimated()
self.animateOut()
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
if let context = self.context, let _ = self.parentNavigationController {
if let context = self.context, let _ = self.parentNavigationController, self.showBalance {
let insets = layout.insets(options: .statusBar)
let balanceSize = self.balance.update(
transition: .immediate,

View file

@ -1054,6 +1054,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -1067,6 +1068,8 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -1125,6 +1128,7 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View file

@ -56,6 +56,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
var viewCount: Int?
var rawText = ""
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -71,6 +72,8 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -140,6 +143,7 @@ public class ChatMessageRestrictedBubbleContentNode: ChatMessageBubbleContentNod
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View file

@ -602,6 +602,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
var edited = false
var viewCount: Int? = nil
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.message)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -615,6 +616,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -648,6 +651,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
areReactionsTags: item.message.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.message.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && !item.associatedData.isInPinnedListMode && !isReplyThread,
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.message),

View file

@ -264,6 +264,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
}
var viewCount: Int?
var dateReplies = 0
var starsCount: Int64?
var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeerId: item.context.account.peerId, accountPeer: item.associatedData.accountPeer, message: item.topMessage)
if item.message.isRestricted(platform: "ios", contentSettings: item.context.currentContentSettings.with { $0 }) {
dateReactionsAndPeers = ([], [])
@ -278,6 +279,8 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .group = channel.info {
dateReplies = Int(attribute.count)
}
} else if let attribute = attribute as? PaidStarsMessageAttribute, item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
starsCount = attribute.stars.value
}
}
@ -647,6 +650,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
areReactionsTags: item.topMessage.areReactionsTags(accountPeerId: item.context.account.peerId),
messageEffect: item.topMessage.messageEffect(availableMessageEffects: item.associatedData.availableMessageEffects),
replyCount: dateReplies,
starsCount: starsCount,
isPinned: item.message.tags.contains(.pinned) && (!item.associatedData.isInPinnedListMode || isReplyThread),
hasAutoremove: item.message.isSelfExpiring,
canViewReactionList: canViewMessageReactionList(message: item.topMessage),

View file

@ -393,7 +393,7 @@ public final class ChatUserInfoItemNode: ListViewItemNode, ASGestureRecognizerDe
let disclaimerText: NSMutableAttributedString
if let verification = item.verification {
disclaimerText = NSMutableAttributedString(string: " # \(verification.description)", font: Font.regular(13.0), textColor: subtitleColor)
disclaimerText = NSMutableAttributedString(string: " # \(verification.description)", font: Font.regular(13.0), textColor: subtitleColor)
if let range = disclaimerText.string.range(of: "#") {
disclaimerText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: verification.iconFileId, file: nil), range: NSRange(range, in: disclaimerText.string))
disclaimerText.addAttribute(.foregroundColor, value: subtitleColor, range: NSRange(range, in: disclaimerText.string))

View file

@ -216,6 +216,8 @@ final class GiftOptionsScreenComponent: Component {
private var starsStateDisposable: Disposable?
private var starsState: StarsContext.State?
private let optionsPromise = Promise<[StarsTopUpOption]?>(nil)
private var component: GiftOptionsScreenComponent?
private(set) weak var state: State?
private var environment: EnvironmentType?
@ -508,59 +510,94 @@ final class GiftOptionsScreenComponent: Component {
gift: transferGift,
peer: peer,
transferStars: gift.transferStars ?? 0,
commit: { [weak controller] in
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|> deliverOnMainQueue).start()
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
return
}
if peer.id.namespace == Namespaces.Peer.CloudChannel {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
var foundController = false
for controller in controllers.reversed() {
if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId {
foundController = true
break
}
navigationController: controller.navigationController as? NavigationController,
commit: { [weak self, weak controller] in
let proceed: (Bool) -> Void = { waitForTopUp in
if waitForTopUp, let starsContext = context.starsContext {
let _ = (starsContext.onUpdate
|> deliverOnMainQueue).start(next: {
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|> deliverOnMainQueue).start()
})
} else {
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|> deliverOnMainQueue).start()
}
if !foundController {
if let controller = context.sharedContext.makePeerInfoController(
context: context,
updatedPresentationData: nil,
peer: peer._asPeer(),
mode: .gifts,
avatarInitiallyExpanded: false,
fromChat: false,
requestsContext: nil
) {
controllers.append(controller)
}
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
return
}
navigationController.setViewControllers(controllers, animated: true)
} else {
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 {
if peer.id.namespace == Namespaces.Peer.CloudChannel {
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
var foundController = false
for controller in controllers.reversed() {
if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId {
foundController = true
break
}
}
if !foundController {
if let controller = context.sharedContext.makePeerInfoController(
context: context,
updatedPresentationData: nil,
peer: peer._asPeer(),
mode: .gifts,
avatarInitiallyExpanded: false,
fromChat: false,
requestsContext: nil
) {
controllers.append(controller)
}
}
navigationController.setViewControllers(controllers, animated: true)
} else {
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()
foundController = true
break
controllers.append(chatController)
}
navigationController.setViewControllers(controllers, animated: true)
}
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)
if let completion = component.completion {
completion()
}
navigationController.setViewControllers(controllers, animated: true)
}
if let completion = component.completion {
completion()
if let self, let transferStars = gift.transferStars, transferStars > 0, let starsContext = context.starsContext, let starsState = self.starsState {
if starsState.balance < StarsAmount(value: transferStars, nanos: 0) {
let _ = (self.optionsPromise.get()
|> filter { $0 != nil }
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak controller] options in
let purchaseController = context.sharedContext.makeStarsPurchaseScreen(
context: context,
starsContext: starsContext,
options: options ?? [],
purpose: .transferStarGift(requiredStars: transferStars),
completion: { stars in
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
proceed(true)
}
)
controller?.push(purchaseController)
})
} else {
proceed(false)
}
} else {
proceed(false)
}
}
)
@ -590,6 +627,11 @@ final class GiftOptionsScreenComponent: Component {
self.state?.updated()
}
})
if let state = component.starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
self.optionsPromise.set(component.context.engine.payments.starsTopUpOptions()
|> map(Optional.init))
}
}
self.component = component
@ -1237,6 +1279,10 @@ final class GiftOptionsScreenComponent: Component {
if availableProducts.isEmpty {
var premiumProducts: [PremiumGiftProduct] = []
for option in premiumOptions {
if option.currency == "XTR" {
continue
}
let starsGiftOption = premiumOptions.first(where: { $0.currency == "XTR" && $0.months == option.months })
premiumProducts.append(
PremiumGiftProduct(
giftOption: CachedPremiumGiftOption(
@ -1246,7 +1292,7 @@ final class GiftOptionsScreenComponent: Component {
botUrl: "",
storeProductId: option.storeProductId
),
starsGiftOption: nil,
starsGiftOption: starsGiftOption,
storeProduct: nil,
discount: nil
)

View file

@ -498,16 +498,8 @@ final class GiftSetupScreenComponent: Component {
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
let _ = (starsContext.state
|> take(until: { value in
if let value {
if !value.flags.contains(.isPendingBalance) {
return SignalTakeAction(passthrough: true, complete: true)
}
}
return SignalTakeAction(passthrough: false, complete: false)
})
|> deliverOnMainQueue).start(next: { _ in
let _ = (starsContext.onUpdate
|> deliverOnMainQueue).start(next: {
proceed()
})
}

View file

@ -49,6 +49,7 @@ swift_library(
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
"//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent",
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
],
visibility = [
"//visibility:public",

View file

@ -12,6 +12,7 @@ import AppBundle
import AvatarNode
import Markdown
import GiftItemComponent
import ChatMessagePaymentAlertController
private final class GiftTransferAlertContentNode: AlertContentNode {
private let context: AccountContext
@ -251,7 +252,14 @@ private final class GiftTransferAlertContentNode: AlertContentNode {
}
}
public func giftTransferAlertController(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, transferStars: Int64, commit: @escaping () -> Void) -> AlertController {
public func giftTransferAlertController(
context: AccountContext,
gift: StarGift.UniqueGift,
peer: EnginePeer,
transferStars: Int64,
navigationController: NavigationController?,
commit: @escaping () -> Void
) -> AlertController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
@ -267,7 +275,6 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift.
}
var dismissImpl: ((Bool) -> Void)?
var contentNode: GiftTransferAlertContentNode?
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: {
dismissImpl?(true)
commit()
@ -275,9 +282,9 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift.
dismissImpl?(true)
})]
contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
let contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode, navigationController: navigationController, showBalance: transferStars > 0)
dismissImpl = { [weak controller] animated in
if animated {
controller?.dismissAnimated()

View file

@ -147,13 +147,7 @@ private final class GiftViewSheetContent: CombinedComponent {
var keepOriginalInfo = false
private var optionsDisposable: Disposable?
private(set) var options: [StarsTopUpOption] = [] {
didSet {
self.optionsPromise.set(self.options)
}
}
private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil)
private let optionsPromise = Promise<[StarsTopUpOption]?>(nil)
init(
context: AccountContext,
@ -269,13 +263,8 @@ private final class GiftViewSheetContent: CombinedComponent {
}
if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
self.optionsDisposable = (context.engine.payments.starsTopUpOptions()
|> deliverOnMainQueue).start(next: { [weak self] options in
guard let self else {
return
}
self.options = options
})
self.optionsPromise.set(context.engine.payments.starsTopUpOptions()
|> map(Optional.init))
}
}
@ -351,8 +340,12 @@ private final class GiftViewSheetContent: CombinedComponent {
self.inProgress = true
self.updated()
if let controller = self.getController() as? GiftViewScreen {
controller.showBalance = false
}
self.upgradeDisposable = (self.upgradeGift(formId, self.keepOriginalInfo)
|> deliverOnMainQueue).start(next: { [weak self] result in
|> deliverOnMainQueue).start(next: { [weak self, weak starsContext] result in
guard let self, let controller = self.getController() as? GiftViewScreen else {
return
}
@ -363,6 +356,10 @@ private final class GiftViewSheetContent: CombinedComponent {
controller.subject = self.subject
controller.animateSuccess()
self.updated(transition: .spring(duration: 0.4))
Queue.mainQueue().after(0.5) {
starsContext?.load(force: true)
}
})
}
@ -382,11 +379,18 @@ private final class GiftViewSheetContent: CombinedComponent {
starsContext: starsContext,
options: options ?? [],
purpose: .upgradeStarGift(requiredStars: price),
completion: { [weak starsContext] stars in
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
Queue.mainQueue().after(2.0) {
proceed(upgradeForm.id)
completion: { [weak self, weak starsContext] stars in
guard let self, let starsContext else {
return
}
self.inProgress = true
self.updated()
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
let _ = (starsContext.onUpdate
|> deliverOnMainQueue).start(next: {
proceed(upgradeForm.id)
})
}
)
controller.push(purchaseController)
@ -2226,9 +2230,8 @@ private final class GiftViewSheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0))
)
let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom)
return contentSize
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset)
}
}
}

View file

@ -28,7 +28,6 @@ swift_library(
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListItemSliderSelectorComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/PremiumUI",
"//submodules/Components/BlurredBackgroundComponent",
"//submodules/Markdown",
"//submodules/PresentationDataUtils",
@ -38,6 +37,7 @@ swift_library(
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/ToastComponent",
"//submodules/AvatarNode",
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
],
visibility = [
"//visibility:public",

View file

@ -19,13 +19,13 @@ import ListItemSliderSelectorComponent
import ListActionItemComponent
import Markdown
import BlurredBackgroundComponent
import PremiumUI
import PresentationDataUtils
import PeerListItemComponent
import TelegramStringFormatting
import ContextUI
import BalancedTextComponent
import AlertComponent
import PremiumCoinComponent
private func textForTimeout(value: Int32) -> String {
if value < 3600 {

View file

@ -111,6 +111,7 @@ public final class PeerInfoCoverComponent: Component {
public let files: [Int64: TelegramMediaFile]
public let isDark: Bool
public let avatarCenter: CGPoint
public let avatarSize: CGSize
public let avatarScale: CGFloat
public let defaultHeight: CGFloat
public let gradientOnTop: Bool
@ -124,6 +125,7 @@ public final class PeerInfoCoverComponent: Component {
files: [Int64: TelegramMediaFile],
isDark: Bool,
avatarCenter: CGPoint,
avatarSize: CGSize = CGSize(width: 100.0, height: 100.0),
avatarScale: CGFloat,
defaultHeight: CGFloat,
gradientOnTop: Bool = false,
@ -136,6 +138,7 @@ public final class PeerInfoCoverComponent: Component {
self.files = files
self.isDark = isDark
self.avatarCenter = avatarCenter
self.avatarSize = avatarSize
self.avatarScale = avatarScale
self.defaultHeight = defaultHeight
self.gradientOnTop = gradientOnTop
@ -160,6 +163,9 @@ public final class PeerInfoCoverComponent: Component {
if lhs.avatarCenter != rhs.avatarCenter {
return false
}
if lhs.avatarSize != rhs.avatarSize {
return false
}
if lhs.avatarScale != rhs.avatarScale {
return false
}
@ -492,7 +498,7 @@ public final class PeerInfoCoverComponent: Component {
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame)
transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction)
var baseDistance: CGFloat = 72.0
var baseDistance: CGFloat = component.avatarSize.width / 2.0 + 22.0
var baseRowDistance: CGFloat = 28.0
var baseItemSize: CGFloat = 26.0
if availableSize.width <= 60.0 {
@ -516,7 +522,7 @@ public final class PeerInfoCoverComponent: Component {
let baseItemDistance: CGFloat = baseDistance + CGFloat(row) * baseRowDistance
let itemDistanceFraction = max(0.0, min(1.0, baseItemDistance / (baseDistance * 2.0)))
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction, t: itemDistanceFraction, reverse: false)
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction * 1.6, t: itemDistanceFraction, reverse: false)
let itemDistance = baseItemDistance * (1.0 - itemScaleFraction) + 20.0 * itemScaleFraction
var itemAngle: CGFloat

View file

@ -19,6 +19,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
public let giftsContext: ProfileGiftsContext
public let hasBackground: Bool
public let avatarCenter: CGPoint
public let avatarSize: CGSize
public let defaultHeight: CGFloat
public let avatarTransitionFraction: CGFloat
public let statusBarHeight: CGFloat
@ -34,6 +35,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
giftsContext: ProfileGiftsContext,
hasBackground: Bool,
avatarCenter: CGPoint,
avatarSize: CGSize,
defaultHeight: CGFloat,
avatarTransitionFraction: CGFloat,
statusBarHeight: CGFloat,
@ -48,6 +50,7 @@ public final class PeerInfoGiftsCoverComponent: Component {
self.giftsContext = giftsContext
self.hasBackground = hasBackground
self.avatarCenter = avatarCenter
self.avatarSize = avatarSize
self.defaultHeight = defaultHeight
self.avatarTransitionFraction = avatarTransitionFraction
self.statusBarHeight = statusBarHeight
@ -71,6 +74,9 @@ public final class PeerInfoGiftsCoverComponent: Component {
if lhs.avatarCenter != rhs.avatarCenter {
return false
}
if lhs.avatarSize != rhs.avatarSize {
return false
}
if lhs.defaultHeight != rhs.defaultHeight {
return false
}
@ -213,14 +219,14 @@ public final class PeerInfoGiftsCoverComponent: Component {
}
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.statusBarHeight), size: component.topLeftButtonsSize))
excludeRects.append(CGRect(origin: CGPoint(x: availableSize.width - component.topRightButtonsSize.width, y: component.statusBarHeight), size: component.topRightButtonsSize))
excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: avatarCenter.y + 56.0), size: CGSize(width: component.titleWidth, height: 72.0)))
excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: avatarCenter.y + component.avatarSize.height / 2.0 + 6.0), size: CGSize(width: component.titleWidth, height: 100.0)))
if component.bottomHeight > 0.0 {
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.defaultHeight - component.bottomHeight), size: CGSize(width: availableSize.width, height: component.bottomHeight)))
}
let positionGenerator = PositionGenerator(
containerSize: CGSize(width: availableSize.width, height: component.defaultHeight),
centerFrame: CGSize(width: 100, height: 100).centered(around: avatarCenter),
centerFrame: component.avatarSize.centered(around: avatarCenter),
exclusionZones: excludeRects,
minimumDistance: 42.0,
edgePadding: 5.0,
@ -298,26 +304,28 @@ public final class PeerInfoGiftsCoverComponent: Component {
}
iconLayer.glowing = component.hasBackground
let centerPosition = component.avatarCenter
let finalPosition = iconPosition.center.offsetBy(dx: component.avatarCenter.x, dy: component.avatarCenter.y)
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction, t: 0.0, reverse: false)
let itemDistanceFraction = max(0.0, min(0.5, (iconPosition.distance - component.avatarSize.width / 2.0) / 144.0))
let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.33), t: itemDistanceFraction, reverse: false)
func interpolateRect(from: CGPoint, to: CGPoint, t: CGFloat) -> CGPoint {
func interpolatePosition(from: PositionGenerator.Position, to: PositionGenerator.Position, t: CGFloat) -> PositionGenerator.Position {
let clampedT = max(0, min(1, t))
let interpolatedX = from.x + (to.x - from.x) * clampedT
let interpolatedY = from.y + (to.y - from.y) * clampedT
let interpolatedDistance = from.distance + (to.distance - from.distance) * clampedT
let interpolatedAngle = from.angle + (to.angle - from.angle) * clampedT
return CGPoint(
x: interpolatedX,
y: interpolatedY
)
return PositionGenerator.Position(distance: interpolatedDistance, angle: interpolatedAngle, scale: from.scale)
}
let effectivePosition = interpolateRect(from: finalPosition, to: centerPosition, t: itemScaleFraction)
let toAngle: CGFloat = .pi * 0.18
let centerPosition = PositionGenerator.Position(distance: 0.0, angle: iconPosition.angle + toAngle, scale: iconPosition.scale)
let effectivePosition = interpolatePosition(from: iconPosition, to: centerPosition, t: itemScaleFraction)
let effectiveAngle = toAngle * itemScaleFraction
let absolutePosition = getAbsolutePosition(position: effectivePosition, centerPoint: component.avatarCenter)
iconTransition.setBounds(layer: iconLayer, bounds: CGRect(origin: .zero, size: iconSize))
iconTransition.setPosition(layer: iconLayer, position: effectivePosition)
iconTransition.setPosition(layer: iconLayer, position: absolutePosition)
iconLayer.updateRotation(effectiveAngle, transition: iconTransition)
iconTransition.setScale(layer: iconLayer, scale: iconPosition.scale * (1.0 - itemScaleFraction))
iconTransition.setAlpha(layer: iconLayer, alpha: 1.0 - itemScaleFraction)
@ -580,7 +588,12 @@ private class GiftIconLayer: SimpleLayer {
override func layoutSublayers() {
self.shadowLayer.frame = CGRect(origin: .zero, size: self.bounds.size).insetBy(dx: -8.0, dy: -8.0)
self.animationLayer.frame = CGRect(origin: .zero, size: self.bounds.size)
self.animationLayer.bounds = CGRect(origin: .zero, size: self.bounds.size)
self.animationLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
}
func updateRotation(_ angle: CGFloat, transition: ComponentTransition) {
self.animationLayer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
}
func startAnimations(index: Int) {
@ -638,8 +651,16 @@ private class GiftIconLayer: SimpleLayer {
private struct PositionGenerator {
struct Position {
let center: CGPoint
let distance: CGFloat
let angle: CGFloat
let scale: CGFloat
var relativeCartesian: CGPoint {
return CGPoint(
x: self.distance * cos(self.angle),
y: self.distance * sin(self.angle)
)
}
}
let containerSize: CGSize
@ -700,15 +721,13 @@ private struct PositionGenerator {
let orbitRangeSize = self.innerOrbitRange.max - self.innerOrbitRange.min
let orbitDistanceFactor = self.innerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next())
let orbitDistance = orbitDistanceFactor * centerRadius
let distance = orbitDistanceFactor * centerRadius
let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
let absoluteX = centerPoint.x + orbitDistance * cos(angle)
let absoluteY = centerPoint.y + orbitDistance * sin(angle)
let absolutePosition = CGPoint(x: absoluteX, y: absoluteY)
let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint)
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
@ -717,11 +736,6 @@ private struct PositionGenerator {
continue
}
let relativePosition = CGPoint(
x: absolutePosition.x - centerPoint.x,
y: absolutePosition.y - centerPoint.y
)
let itemRect = CGRect(
x: absolutePosition.x - itemSize.width/2,
y: absolutePosition.y - itemSize.height/2,
@ -729,10 +743,12 @@ private struct PositionGenerator {
height: itemSize.height
)
if self.isValidPosition(itemRect, existingPositions: positions.map { self.posToAbsolute($0.center, centerPoint: centerPoint) }, itemSize: itemSize) {
if self.isValidPosition(itemRect, existingPositions: positions.map {
getAbsolutePosition(distance: $0.distance, angle: $0.angle, centerPoint: centerPoint)
}, itemSize: itemSize) {
let scaleRangeSize = max(self.scaleRange.min + 0.1, 0.75) - self.scaleRange.max
let scale = self.scaleRange.max + scaleRangeSize * CGFloat(self.lokiRng.next())
positions.append(Position(center: relativePosition, scale: scale))
positions.append(Position(distance: distance, angle: angle, scale: scale))
if absolutePosition.x < centerPoint.x {
leftPositions += 1
@ -751,16 +767,13 @@ private struct PositionGenerator {
let orbitRangeSize = self.outerOrbitRange.max - self.outerOrbitRange.min
let orbitDistanceFactor = self.outerOrbitRange.min + orbitRangeSize * CGFloat(self.lokiRng.next())
let orbitDistance = orbitDistanceFactor * centerRadius
let distance = orbitDistanceFactor * centerRadius
let angleRange: CGFloat = placeOnLeftSide ? .pi : .pi
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
let absoluteX = centerPoint.x + orbitDistance * cos(angle)
let absoluteY = centerPoint.y + orbitDistance * sin(angle)
let absolutePosition = CGPoint(x: absoluteX, y: absoluteY)
let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint)
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
absolutePosition.y - itemSize.height/2 < self.edgePadding ||
@ -768,11 +781,6 @@ private struct PositionGenerator {
continue
}
let relativePosition = CGPoint(
x: absolutePosition.x - centerPoint.x,
y: absolutePosition.y - centerPoint.y
)
let itemRect = CGRect(
x: absolutePosition.x - itemSize.width/2,
y: absolutePosition.y - itemSize.height/2,
@ -780,12 +788,12 @@ private struct PositionGenerator {
height: itemSize.height
)
if self.isValidPosition(itemRect, existingPositions: positions.map { self.posToAbsolute($0.center, centerPoint: centerPoint) }, itemSize: itemSize) {
let distance = hypot(absolutePosition.x - centerPoint.x, absolutePosition.y - centerPoint.y)
if self.isValidPosition(itemRect, existingPositions: positions.map {
getAbsolutePosition(distance: $0.distance, angle: $0.angle, centerPoint: centerPoint)
}, itemSize: itemSize) {
let normalizedDistance = min(distance / maxPossibleDistance, 1.0)
let scale = self.scaleRange.max - normalizedDistance * (self.scaleRange.max - self.scaleRange.min)
positions.append(Position(center: relativePosition, scale: scale))
positions.append(Position(distance: distance, angle: angle, scale: scale))
if absolutePosition.x < centerPoint.x {
leftPositions += 1
@ -798,8 +806,11 @@ private struct PositionGenerator {
return positions
}
private func posToAbsolute(_ relativePos: CGPoint, centerPoint: CGPoint) -> CGPoint {
return CGPoint(x: relativePos.x + centerPoint.x, y: relativePos.y + centerPoint.y)
func getAbsolutePosition(distance: CGFloat, angle: CGFloat, centerPoint: CGPoint) -> CGPoint {
return CGPoint(
x: centerPoint.x + distance * cos(angle),
y: centerPoint.y + distance * sin(angle)
)
}
private func isValidPosition(_ rect: CGRect, existingPositions: [CGPoint], itemSize: CGSize) -> Bool {
@ -827,6 +838,20 @@ private struct PositionGenerator {
}
}
private func getAbsolutePosition(position: PositionGenerator.Position, centerPoint: CGPoint) -> CGPoint {
return CGPoint(
x: centerPoint.x + position.distance * cos(position.angle),
y: centerPoint.y + position.distance * sin(position.angle)
)
}
private func getAbsolutePosition(distance: CGFloat, angle: CGFloat, centerPoint: CGPoint) -> CGPoint {
return CGPoint(
x: centerPoint.x + distance * cos(angle),
y: centerPoint.y + distance * sin(angle)
)
}
private func windowFunction(t: CGFloat) -> CGFloat {
return bezierPoint(0.6, 0.0, 0.4, 1.0, t)
}

View file

@ -188,6 +188,7 @@ public final class LoadingOverlayNode: ASDisplayNode {
}, openWebApp: { _ in
}, openPhotoSetup: {
}, openAdInfo: { _ in
}, openAccountFreezeInfo: {
})
let items = (0 ..< 1).map { _ -> ChatListItem in
@ -548,6 +549,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
openPhotoSetup: {
},
openAdInfo: { _ in
},
openAccountFreezeInfo: {
}
)

View file

@ -74,6 +74,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, shouldAnimateIn: Bool, transition: ContainedViewLayoutTransition) {
let sideInset: CGFloat = 24.0
let expandedSideInset: CGFloat = 16.0
let maximumExpandOffset: CGFloat = 14.0
let expandOffset: CGFloat = -expandFraction * maximumExpandOffset
@ -188,7 +189,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
self.currentRightButtons = rightButtons
var nextRegularButtonOrigin = size.width - sideInset - 8.0
var nextExpandedButtonOrigin = size.width - sideInset - 8.0
var nextExpandedButtonOrigin = size.width - expandedSideInset
for spec in rightButtons.reversed() {
let buttonNode: PeerInfoHeaderNavigationButton
var wasAdded = false
@ -257,7 +258,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
for key in removeKeys {
if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) {
if key == .moreSearchSort || key == .searchWithTags || key == .standaloneSearch {
buttonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in
buttonNode.layer.animateAlpha(from: buttonNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in
buttonNode?.removeFromSupernode()
})
buttonNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
@ -268,7 +269,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
}
} else {
var nextRegularButtonOrigin = size.width - sideInset - 8.0
var nextExpandedButtonOrigin = size.width - sideInset - 8.0
var nextExpandedButtonOrigin = size.width - expandedSideInset
for spec in rightButtons.reversed() {
var key = spec.key

View file

@ -2289,6 +2289,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
}
if !buttonKeys.isEmpty {
backgroundDefaultHeight = 327.0
if metrics.isTablet {
backgroundDefaultHeight += 60.0
}
}
hasBackground = true
} else if let peer {
@ -2308,6 +2311,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
files: [:],
isDark: presentationData.theme.overallDarkAppearance,
avatarCenter: apparentAvatarFrame.center.offsetBy(dx: bannerInset, dy: 0.0),
avatarSize: apparentAvatarFrame.size,
avatarScale: avatarScale,
defaultHeight: backgroundDefaultHeight,
gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45),
@ -2352,6 +2356,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
giftsContext: profileGiftsContext,
hasBackground: hasBackground,
avatarCenter: apparentAvatarFrame.center,
avatarSize: apparentAvatarFrame.size,
defaultHeight: backgroundDefaultHeight,
avatarTransitionFraction: max(0.0, min(1.0, titleCollapseFraction + transitionFraction * 2.0)),
statusBarHeight: statusBarHeight,

View file

@ -379,7 +379,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let optionSpacing: CGFloat = 10.0
let itemsSideInset = params.sideInset + 16.0
let defaultItemsInRow = params.size.width > params.size.height ? 5 : 3
let defaultItemsInRow = params.size.width > params.size.height || params.size.width > 414.0 ? 5 : 3
let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow))
let defaultOptionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow)
let optionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
@ -613,7 +613,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
}
var bottomScrollInset: CGFloat = 0.0
var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0
var contentHeight = ceil(CGFloat(starsProducts.count) / CGFloat(defaultItemsInRow)) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0
let size = params.size
let sideInset = params.sideInset
@ -677,13 +677,14 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let buttonSideInset = sideInset + 16.0
let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0)
var bottomPanelHeight = max(8.0, bottomInset) + buttonSize.height + 8.0
let effectiveBottomInset = max(8.0, bottomInset)
var bottomPanelHeight = effectiveBottomInset + buttonSize.height + 8.0
if params.visibleHeight < 110.0 {
scrollOffset -= bottomPanelHeight
}
let panelTransition = ComponentTransition.immediate
panelTransition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize))
panelTransition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - effectiveBottomInset - buttonSize.height - scrollOffset), size: buttonSize))
panelTransition.setAlpha(view: panelButton.view, alpha: panelAlpha)
let _ = panelButton.updateLayout(width: buttonSize.width, transition: .immediate)
@ -754,7 +755,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
if panelCheckView.superview == nil {
self.view.addSubview(panelCheckView)
}
panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - bottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize)
panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - effectiveBottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize)
panelTransition.setAlpha(view: panelCheckView, alpha: panelAlpha)
}
panelButton.isHidden = true
@ -998,7 +999,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
})))
}
if canReorder {
if case .unique = gift.gift, canReorder {
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Context_Reorder, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
c?.dismiss(completion: { [weak self] in
guard let self else {
@ -1015,7 +1016,26 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
guard let self else {
return
}
let _ = self.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).startStandalone()
if self.context.isPremium {
let _ = self.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).startStandalone()
} else {
let text = strings.Gift_View_TooltipPremiumWearing
let tooltipController = UndoOverlayController(
presentationData: presentationData,
content: .premiumPaywall(title: nil, text: text, customUndoText: nil, timeout: nil, linkAction: nil),
position: .bottom,
animateInAsReplacement: false,
appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0),
action: { [weak self] action in
if let self, case .info = action {
let premiumController = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .messageEffects, forceDark: false, dismissed: nil)
self.parentController?.push(premiumController)
}
return false
}
)
self.parentController?.present(tooltipController, in: .current)
}
})
})))
}

View file

@ -0,0 +1,27 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "PremiumCoinComponent",
module_name = "PremiumCoinComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/GZip",
"//submodules/LegacyComponents",
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
],
visibility = [
"//visibility:public",
],
)

View file

@ -48,6 +48,9 @@ public final class PremiumCoinComponent: Component {
public final class View: UIView, SCNSceneRendererDelegate, ComponentTaggedView {
public final class Tag {
public init() {
}
}
public func matches(tag: Any) -> Bool {
@ -58,7 +61,7 @@ public final class PremiumCoinComponent: Component {
}
private var _ready = Promise<Bool>()
var ready: Signal<Bool, NoError> {
public var ready: Signal<Bool, NoError> {
return self._ready.get()
}

View file

@ -0,0 +1,36 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AccountFreezeInfoScreen",
module_name = "AccountFreezeInfoScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/Components/SheetComponent",
"//submodules/PresentationDataUtils",
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/Markdown",
],
visibility = [
"//visibility:public",
],
)

Some files were not shown because too many files have changed in this diff Show more