mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '399933bc5e'
This commit is contained in:
commit
6faefd876a
143 changed files with 3629 additions and 755 deletions
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
|
|||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
}, openAdInfo: { _ in
|
||||
}, openAccountFreezeInfo: {
|
||||
})
|
||||
interaction.isInlineMode = isInlineMode
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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: [])
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Markdown
|
|||
import TelegramPresentationData
|
||||
import BundleIconComponent
|
||||
import ScrollComponent
|
||||
import PremiumCoinComponent
|
||||
|
||||
private final class HeaderComponent: Component {
|
||||
let context: AccountContext
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -16,6 +16,7 @@ import BundleIconComponent
|
|||
import Markdown
|
||||
import SolidRoundedButtonNode
|
||||
import BlurredBackgroundComponent
|
||||
import PremiumCoinComponent
|
||||
|
||||
public class PremiumLimitsListScreen: ViewController {
|
||||
final class Node: ViewControllerTracingNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -382,6 +382,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
}, openAdInfo: { _ in
|
||||
}, openAccountFreezeInfo: {
|
||||
})
|
||||
|
||||
func makeChatListItem(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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! {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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([])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 >]()"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -687,6 +687,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||
openPhotoSetup: {
|
||||
},
|
||||
openAdInfo: { _ in
|
||||
},
|
||||
openAccountFreezeInfo: {
|
||||
}
|
||||
)
|
||||
self.chatListNodeInteraction = chatListNodeInteraction
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue