[WIP] Star refs

This commit is contained in:
Isaac 2024-11-28 16:13:06 +04:00
parent 17bd869ddd
commit be83150aba
59 changed files with 3764 additions and 717 deletions

View File

@ -46,8 +46,9 @@ private class AvatarNodeParameters: NSObject {
let explicitColorIndex: Int?
let hasImage: Bool
let clipStyle: AvatarNodeClipStyle
let cutoutRect: CGRect?
init(theme: PresentationTheme?, accountPeerId: EnginePeer.Id?, peerId: EnginePeer.Id?, colors: [UIColor], letters: [String], font: UIFont, icon: AvatarNodeIcon, explicitColorIndex: Int?, hasImage: Bool, clipStyle: AvatarNodeClipStyle) {
init(theme: PresentationTheme?, accountPeerId: EnginePeer.Id?, peerId: EnginePeer.Id?, colors: [UIColor], letters: [String], font: UIFont, icon: AvatarNodeIcon, explicitColorIndex: Int?, hasImage: Bool, clipStyle: AvatarNodeClipStyle, cutoutRect: CGRect?) {
self.theme = theme
self.accountPeerId = accountPeerId
self.peerId = peerId
@ -58,12 +59,13 @@ private class AvatarNodeParameters: NSObject {
self.explicitColorIndex = explicitColorIndex
self.hasImage = hasImage
self.clipStyle = clipStyle
self.cutoutRect = cutoutRect
super.init()
}
func withUpdatedHasImage(_ hasImage: Bool) -> AvatarNodeParameters {
return AvatarNodeParameters(theme: self.theme, accountPeerId: self.accountPeerId, peerId: self.peerId, colors: self.colors, letters: self.letters, font: self.font, icon: self.icon, explicitColorIndex: self.explicitColorIndex, hasImage: hasImage, clipStyle: self.clipStyle)
return AvatarNodeParameters(theme: self.theme, accountPeerId: self.accountPeerId, peerId: self.peerId, colors: self.colors, letters: self.letters, font: self.font, icon: self.icon, explicitColorIndex: self.explicitColorIndex, hasImage: hasImage, clipStyle: self.clipStyle, cutoutRect: self.cutoutRect)
}
}
@ -166,7 +168,7 @@ public enum AvatarNodeExplicitIcon {
private enum AvatarNodeState: Equatable {
case empty
case peerAvatar(EnginePeer.Id, PeerNameColor?, [String], TelegramMediaImageRepresentation?, AvatarNodeClipStyle)
case peerAvatar(EnginePeer.Id, PeerNameColor?, [String], TelegramMediaImageRepresentation?, AvatarNodeClipStyle, CGRect?)
case custom(letter: [String], explicitColorIndex: Int?, explicitIcon: AvatarNodeExplicitIcon?)
}
@ -174,8 +176,8 @@ private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool {
switch (lhs, rhs) {
case (.empty, .empty):
return true
case let (.peerAvatar(lhsPeerId, lhsPeerNameColor, lhsLetters, lhsPhotoRepresentations, lhsClipStyle), .peerAvatar(rhsPeerId, rhsPeerNameColor, rhsLetters, rhsPhotoRepresentations, rhsClipStyle)):
return lhsPeerId == rhsPeerId && lhsPeerNameColor == rhsPeerNameColor && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations && lhsClipStyle == rhsClipStyle
case let (.peerAvatar(lhsPeerId, lhsPeerNameColor, lhsLetters, lhsPhotoRepresentations, lhsClipStyle, lhsCutoutRect), .peerAvatar(rhsPeerId, rhsPeerNameColor, rhsLetters, rhsPhotoRepresentations, rhsClipStyle, rhsCutoutRect)):
return lhsPeerId == rhsPeerId && lhsPeerNameColor == rhsPeerNameColor && lhsLetters == rhsLetters && lhsPhotoRepresentations == rhsPhotoRepresentations && lhsClipStyle == rhsClipStyle && lhsCutoutRect == rhsCutoutRect
case let (.custom(lhsLetters, lhsIndex, lhsIcon), .custom(rhsLetters, rhsIndex, rhsIcon)):
return lhsLetters == rhsLetters && lhsIndex == rhsIndex && lhsIcon == rhsIcon
default:
@ -307,7 +309,7 @@ public final class AvatarNode: ASDisplayNode {
didSet {
if oldValue.pointSize != font.pointSize {
if let parameters = self.parameters {
self.parameters = AvatarNodeParameters(theme: parameters.theme, accountPeerId: parameters.accountPeerId, peerId: parameters.peerId, colors: parameters.colors, letters: parameters.letters, font: self.font, icon: parameters.icon, explicitColorIndex: parameters.explicitColorIndex, hasImage: parameters.hasImage, clipStyle: parameters.clipStyle)
self.parameters = AvatarNodeParameters(theme: parameters.theme, accountPeerId: parameters.accountPeerId, peerId: parameters.peerId, colors: parameters.colors, letters: parameters.letters, font: self.font, icon: parameters.icon, explicitColorIndex: parameters.explicitColorIndex, hasImage: parameters.hasImage, clipStyle: parameters.clipStyle, cutoutRect: parameters.cutoutRect)
}
if !self.displaySuspended {
@ -334,7 +336,7 @@ public final class AvatarNode: ASDisplayNode {
var clipStyle: AvatarNodeClipStyle {
if let params = self.params {
return params.clipStyle
} else if case let .peerAvatar(_, _, _, _, clipStyle) = self.state {
} else if case let .peerAvatar(_, _, _, _, clipStyle, _) = self.state {
return clipStyle
}
return .none
@ -498,7 +500,8 @@ public final class AvatarNode: ASDisplayNode {
clipStyle: AvatarNodeClipStyle = .round,
synchronousLoad: Bool = false,
displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0),
storeUnrounded: Bool = false
storeUnrounded: Bool = false,
cutoutRect: CGRect? = nil
) {
var synchronousLoad = synchronousLoad
var representation: TelegramMediaImageRepresentation?
@ -542,7 +545,7 @@ public final class AvatarNode: ASDisplayNode {
representation = peer?.smallProfileImage
}
let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? EnginePeer.Id(0), peer?.nameColor, peer?.displayLetters ?? [], representation, clipStyle)
let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? EnginePeer.Id(0), peer?.nameColor, peer?.displayLetters ?? [], representation, clipStyle, cutoutRect)
if updatedState != self.state || overrideImage != self.overrideImage || theme !== self.theme {
self.state = updatedState
self.overrideImage = overrideImage
@ -550,7 +553,7 @@ public final class AvatarNode: ASDisplayNode {
let parameters: AvatarNodeParameters
if let peer = peer, let signal = peerAvatarImage(postbox: postbox, network: network, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
if let peer = peer, let signal = peerAvatarImage(postbox: postbox, network: network, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded, cutoutRect: cutoutRect) {
self.contents = nil
self.displaySuspended = true
self.imageReady.set(self.imageNode.contentReady)
@ -577,7 +580,7 @@ public final class AvatarNode: ASDisplayNode {
self.editOverlayNode?.isHidden = true
}
parameters = AvatarNodeParameters(theme: theme, accountPeerId: accountPeerId, peerId: peer.id, colors: calculateAvatarColors(context: nil, explicitColorIndex: nil, peerId: peer.id, nameColor: peer.nameColor, icon: icon, theme: theme), letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: true, clipStyle: clipStyle)
parameters = AvatarNodeParameters(theme: theme, accountPeerId: accountPeerId, peerId: peer.id, colors: calculateAvatarColors(context: nil, explicitColorIndex: nil, peerId: peer.id, nameColor: peer.nameColor, icon: icon, theme: theme), letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: true, clipStyle: clipStyle, cutoutRect: cutoutRect)
} else {
self.imageReady.set(.single(true))
self.displaySuspended = false
@ -587,7 +590,7 @@ public final class AvatarNode: ASDisplayNode {
self.editOverlayNode?.isHidden = true
let colors = calculateAvatarColors(context: nil, explicitColorIndex: nil, peerId: peer?.id ?? EnginePeer.Id(0), nameColor: peer?.nameColor, icon: icon, theme: theme)
parameters = AvatarNodeParameters(theme: theme, accountPeerId: accountPeerId, peerId: peer?.id ?? EnginePeer.Id(0), colors: colors, letters: peer?.displayLetters ?? [], font: self.font, icon: icon, explicitColorIndex: nil, hasImage: false, clipStyle: clipStyle)
parameters = AvatarNodeParameters(theme: theme, accountPeerId: accountPeerId, peerId: peer?.id ?? EnginePeer.Id(0), colors: colors, letters: peer?.displayLetters ?? [], font: self.font, icon: icon, explicitColorIndex: nil, hasImage: false, clipStyle: clipStyle, cutoutRect: cutoutRect)
if let badgeView = self.badgeView {
let badgeColor: UIColor
@ -673,7 +676,8 @@ public final class AvatarNode: ASDisplayNode {
clipStyle: AvatarNodeClipStyle = .round,
synchronousLoad: Bool = false,
displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0),
storeUnrounded: Bool = false
storeUnrounded: Bool = false,
cutoutRect: CGRect? = nil
) {
var synchronousLoad = synchronousLoad
var representation: TelegramMediaImageRepresentation?
@ -717,7 +721,7 @@ public final class AvatarNode: ASDisplayNode {
representation = peer?.smallProfileImage
}
let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? EnginePeer.Id(0), peer?.nameColor, peer?.displayLetters ?? [], representation, clipStyle)
let updatedState: AvatarNodeState = .peerAvatar(peer?.id ?? EnginePeer.Id(0), peer?.nameColor, peer?.displayLetters ?? [], representation, clipStyle, cutoutRect)
if updatedState != self.state || overrideImage != self.overrideImage || theme !== self.theme {
self.state = updatedState
self.overrideImage = overrideImage
@ -727,7 +731,7 @@ public final class AvatarNode: ASDisplayNode {
let account = account ?? genericContext.account
if let peer = peer, let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded) {
if let peer = peer, let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer._asPeer()), authorOfMessage: authorOfMessage, representation: representation, displayDimensions: displayDimensions, clipStyle: clipStyle, emptyColor: emptyColor, synchronousLoad: synchronousLoad, provideUnrounded: storeUnrounded, cutoutRect: cutoutRect) {
self.contents = nil
self.displaySuspended = true
self.imageReady.set(self.imageNode.contentReady)
@ -754,7 +758,7 @@ public final class AvatarNode: ASDisplayNode {
self.editOverlayNode?.isHidden = true
}
parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer.id, colors: calculateAvatarColors(context: genericContext, explicitColorIndex: nil, peerId: peer.id, nameColor: peer.nameColor, icon: icon, theme: theme), letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: true, clipStyle: clipStyle)
parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer.id, colors: calculateAvatarColors(context: genericContext, explicitColorIndex: nil, peerId: peer.id, nameColor: peer.nameColor, icon: icon, theme: theme), letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: true, clipStyle: clipStyle, cutoutRect: cutoutRect)
} else {
self.imageReady.set(.single(true))
self.displaySuspended = false
@ -764,7 +768,7 @@ public final class AvatarNode: ASDisplayNode {
self.editOverlayNode?.isHidden = true
let colors = calculateAvatarColors(context: genericContext, explicitColorIndex: nil, peerId: peer?.id ?? EnginePeer.Id(0), nameColor: peer?.nameColor, icon: icon, theme: theme)
parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer?.id ?? EnginePeer.Id(0), colors: colors, letters: peer?.displayLetters ?? [], font: self.font, icon: icon, explicitColorIndex: nil, hasImage: false, clipStyle: clipStyle)
parameters = AvatarNodeParameters(theme: theme, accountPeerId: account.peerId, peerId: peer?.id ?? EnginePeer.Id(0), colors: colors, letters: peer?.displayLetters ?? [], font: self.font, icon: icon, explicitColorIndex: nil, hasImage: false, clipStyle: clipStyle, cutoutRect: cutoutRect)
if let badgeView = self.badgeView {
let badgeColor: UIColor
@ -786,7 +790,7 @@ public final class AvatarNode: ASDisplayNode {
}
}
public func setCustomLetters(_ letters: [String], explicitColor: AvatarNodeColorOverride? = nil, icon: AvatarNodeExplicitIcon? = nil) {
public func setCustomLetters(_ letters: [String], explicitColor: AvatarNodeColorOverride? = nil, icon: AvatarNodeExplicitIcon? = nil, cutoutRect: CGRect? = nil) {
var explicitIndex: Int?
if let explicitColor = explicitColor {
switch explicitColor {
@ -800,9 +804,9 @@ public final class AvatarNode: ASDisplayNode {
let parameters: AvatarNodeParameters
if let icon = icon, case .phone = icon {
parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, colors: calculateAvatarColors(context: nil, explicitColorIndex: explicitIndex, peerId: nil, nameColor: nil, icon: .phoneIcon, theme: nil), letters: [], font: self.font, icon: .phoneIcon, explicitColorIndex: explicitIndex, hasImage: false, clipStyle: .round)
parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, colors: calculateAvatarColors(context: nil, explicitColorIndex: explicitIndex, peerId: nil, nameColor: nil, icon: .phoneIcon, theme: nil), letters: [], font: self.font, icon: .phoneIcon, explicitColorIndex: explicitIndex, hasImage: false, clipStyle: .round, cutoutRect: cutoutRect)
} else {
parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, colors: calculateAvatarColors(context: nil, explicitColorIndex: explicitIndex, peerId: nil, nameColor: nil, icon: .none, theme: nil), letters: letters, font: self.font, icon: .none, explicitColorIndex: explicitIndex, hasImage: false, clipStyle: .round)
parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, colors: calculateAvatarColors(context: nil, explicitColorIndex: explicitIndex, peerId: nil, nameColor: nil, icon: .none, theme: nil), letters: letters, font: self.font, icon: .none, explicitColorIndex: explicitIndex, hasImage: false, clipStyle: .round, cutoutRect: cutoutRect)
}
self.displaySuspended = true
@ -998,6 +1002,12 @@ public final class AvatarNode: ASDisplayNode {
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
}
}
if let parameters = parameters as? AvatarNodeParameters, let cutoutRect = parameters.cutoutRect {
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: cutoutRect.offsetBy(dx: 0.0, dy: bounds.height - cutoutRect.maxY - cutoutRect.height))
}
}
}
@ -1190,7 +1200,8 @@ public final class AvatarNode: ASDisplayNode {
clipStyle: AvatarNodeClipStyle = .round,
synchronousLoad: Bool = false,
displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0),
storeUnrounded: Bool = false
storeUnrounded: Bool = false,
cutoutRect: CGRect? = nil
) {
self.contentNode.setPeer(
context: context,
@ -1203,7 +1214,8 @@ public final class AvatarNode: ASDisplayNode {
clipStyle: clipStyle,
synchronousLoad: synchronousLoad,
displayDimensions: displayDimensions,
storeUnrounded: storeUnrounded
storeUnrounded: storeUnrounded,
cutoutRect: cutoutRect
)
}

View File

@ -171,7 +171,7 @@ public func peerAvatarCompleteImage(postbox: Postbox, network: Network, peer: En
return iconSignal
}
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false, cutoutRect: CGRect? = nil) -> Signal<(UIImage, UIImage)?, NoError>? {
return peerAvatarImage(
postbox: account.postbox,
network: account.network,
@ -184,11 +184,12 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
inset: inset,
emptyColor: emptyColor,
synchronousLoad: synchronousLoad,
provideUnrounded: synchronousLoad
provideUnrounded: synchronousLoad,
cutoutRect: cutoutRect
)
}
public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false, cutoutRect: CGRect? = nil) -> Signal<(UIImage, UIImage)?, NoError>? {
if let imageData = peerAvatarImageData(postbox: postbox, network: network, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
return imageData
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
@ -294,6 +295,12 @@ public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: P
context.fillPath()
}
}
if let cutoutRect {
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: cutoutRect)
}
})
let unroundedImage: UIImage?
if provideUnrounded {

View File

@ -758,7 +758,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
case .reviewLogin:
break
case let .starsSubscriptionLowBalance(amount, _):
nodeInteraction?.openStarsTopup(amount)
nodeInteraction?.openStarsTopup(amount.value)
}
case .hide:
nodeInteraction?.dismissNotice(notice)
@ -1102,7 +1102,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
case .reviewLogin:
break
case let .starsSubscriptionLowBalance(amount, _):
nodeInteraction?.openStarsTopup(amount)
nodeInteraction?.openStarsTopup(amount.value)
}
case .hide:
nodeInteraction?.dismissNotice(notice)
@ -2012,7 +2012,7 @@ public final class ChatListNode: ListView {
if let starsSubscriptionsContext {
return starsSubscriptionsContext.state
|> map { state in
if state.balance > 0 && !state.subscriptions.isEmpty {
if state.balance > StarsAmount.zero && !state.subscriptions.isEmpty {
return .starsSubscriptionLowBalance(
amount: state.balance,
peers: state.subscriptions.map { $0.peer }

View File

@ -90,7 +90,7 @@ public enum ChatListNotice: Equatable {
case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday])
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
case premiumGrace
case starsSubscriptionLowBalance(amount: Int64, peers: [EnginePeer])
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
}
enum ChatListNodeEntry: Comparable, Identifiable {

View File

@ -270,7 +270,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
case let .starsSubscriptionLowBalance(amount, peers):
let title: String
let text: String
let starsValue = item.strings.ChatList_SubscriptionsLowBalance_Stars(Int32(amount))
let starsValue = item.strings.ChatList_SubscriptionsLowBalance_Stars(Int32(amount.value))
if let peer = peers.first, peers.count == 1 {
title = item.strings.ChatList_SubscriptionsLowBalance_Single_Title(starsValue, peer.compactDisplayTitle).string
text = item.strings.ChatList_SubscriptionsLowBalance_Single_Text

View File

@ -8,13 +8,15 @@ public final class BundleIconComponent: Component {
public let name: String
public let tintColor: UIColor?
public let maxSize: CGSize?
public let scaleFactor: CGFloat
public let shadowColor: UIColor?
public let shadowBlur: CGFloat
public init(name: String, tintColor: UIColor?, maxSize: CGSize? = nil, shadowColor: UIColor? = nil, shadowBlur: CGFloat = 0.0) {
public init(name: String, tintColor: UIColor?, maxSize: CGSize? = nil, scaleFactor: CGFloat = 1.0, shadowColor: UIColor? = nil, shadowBlur: CGFloat = 0.0) {
self.name = name
self.tintColor = tintColor
self.maxSize = maxSize
self.scaleFactor = scaleFactor
self.shadowColor = shadowColor
self.shadowBlur = shadowBlur
}
@ -29,6 +31,9 @@ public final class BundleIconComponent: Component {
if lhs.maxSize != rhs.maxSize {
return false
}
if lhs.scaleFactor != rhs.scaleFactor {
return false
}
if lhs.shadowColor != rhs.shadowColor {
return false
}
@ -75,6 +80,10 @@ public final class BundleIconComponent: Component {
if let maxSize = component.maxSize {
imageSize = imageSize.aspectFitted(maxSize)
}
if component.scaleFactor != 1.0 {
imageSize.width = floor(imageSize.width * component.scaleFactor)
imageSize.height = floor(imageSize.height * component.scaleFactor)
}
return CGSize(width: min(imageSize.width, availableSize.width), height: min(imageSize.height, availableSize.height))
}

View File

@ -92,7 +92,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
case subscriptionFeeToggle(PresentationTheme, String, Bool, Bool)
case subscriptionFee(PresentationTheme, String, Bool, Int64?, String, Int64?)
case subscriptionFee(PresentationTheme, String, Bool, StarsAmount?, String, StarsAmount?)
case subscriptionFeeInfo(PresentationTheme, String)
case requestApproval(PresentationTheme, String, Bool, Bool)
@ -328,7 +328,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
return ItemListSingleLineInputItem(context: arguments.context, presentationData: presentationData, title: title, text: value.flatMap { "\($0)" } ?? "", placeholder: placeholder, label: label, type: .number, spacing: 3.0, enabled: enabled, tag: InviteLinksEditEntryTag.subscriptionFee, sectionId: self.section, textUpdated: { text in
arguments.updateState { state in
var updatedState = state
if var value = Int64(text) {
if var value = Int64(text).flatMap({ StarsAmount(value: $0, nanos: 0) }) {
if let maxValue, value > maxValue {
value = maxValue
arguments.errorWithItem(.subscriptionFee)
@ -492,14 +492,14 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
entries.append(.subscriptionFeeToggle(presentationData.theme, presentationData.strings.InviteLink_Create_Fee, state.subscriptionEnabled, isEditingEnabled))
if state.subscriptionEnabled {
var label: String = ""
if let subscriptionFee = state.subscriptionFee, subscriptionFee > 0 {
if let subscriptionFee = state.subscriptionFee, subscriptionFee > StarsAmount.zero {
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
label = presentationData.strings.InviteLink_Create_FeePerMonth("\(formatTonUsdValue(subscriptionFee, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))").string
label = presentationData.strings.InviteLink_Create_FeePerMonth("\(formatTonUsdValue(subscriptionFee.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))").string
}
entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, configuration.maxFee))
entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, configuration.maxFee.flatMap({ StarsAmount(value: $0, nanos: 0) })))
}
let infoText: String
if let _ = invite, state.subscriptionEnabled {
@ -566,7 +566,7 @@ private struct InviteLinkEditControllerState: Equatable {
var time: InviteLinkTimeLimit
var requestApproval = false
var subscriptionEnabled = false
var subscriptionFee: Int64?
var subscriptionFee: StarsAmount?
var pickingExpiryDate = false
var pickingExpiryTime = false
var pickingUsageLimit = false
@ -698,7 +698,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
var doneIsEnabled = true
if state.subscriptionEnabled {
if (state.subscriptionFee ?? 0) == 0 {
if (state.subscriptionFee ?? StarsAmount.zero) == StarsAmount.zero {
doneIsEnabled = false
}
}

View File

@ -849,7 +849,7 @@ public final class InviteLinkViewController: ViewController {
var subtitle = presentationData.strings.InviteLink_SubscriptionFee_NoOneJoined
if state.count > 0 {
title += " x \(state.count)"
let usdValue = formatTonUsdValue(pricing.amount * Int64(state.count), divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat)
let usdValue = formatTonUsdValue(pricing.amount.value * Int64(state.count), divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat)
subtitle = presentationData.strings.InviteLink_SubscriptionFee_ApproximateIncome(usdValue).string
}
entries.append(.subscriptionPricing(presentationData.theme, title, subtitle))

View File

@ -4,10 +4,12 @@ import CoreMedia
import FFMpegBinding
import VideoToolbox
#if os(macOS)
private let isHardwareAv1Supported: Bool = {
let value = VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1)
return value
}()
#endif
public protocol MediaDataReader: AnyObject {
var hasVideo: Bool { get }

View File

@ -1589,7 +1589,7 @@ private func monetizationEntries(
entries.append(.adsProceedsOverview(presentationData.theme, canViewRevenue ? data : nil, canViewStarsRevenue ? starsData : nil))
let hasTonBalance = data.balances.overallRevenue > 0
let hasStarsBalance = (starsData?.balances.overallRevenue ?? 0) > 0
let hasStarsBalance = (starsData?.balances.overallRevenue ?? StarsAmount.zero) > StarsAmount.zero
let proceedsInfo: String
if (canViewStarsRevenue && hasStarsBalance) && (canViewRevenue && hasTonBalance) {
@ -1624,9 +1624,9 @@ private func monetizationEntries(
}
if canViewStarsRevenue {
if let starsData, starsData.balances.overallRevenue > 0 {
if let starsData, starsData.balances.overallRevenue > StarsAmount.zero {
entries.append(.adsStarsBalanceTitle(presentationData.theme, presentationData.strings.Monetization_StarsBalanceTitle))
entries.append(.adsStarsBalance(presentationData.theme, starsData, isCreator && starsData.balances.availableBalance > 0, starsData.balances.withdrawEnabled, starsData.balances.nextWithdrawalTimestamp))
entries.append(.adsStarsBalance(presentationData.theme, starsData, isCreator && starsData.balances.availableBalance > StarsAmount.zero, starsData.balances.withdrawEnabled, starsData.balances.nextWithdrawalTimestamp))
entries.append(.adsStarsBalanceInfo(presentationData.theme, presentationData.strings.Monetization_Balance_StarsInfo))
}
}

View File

@ -180,8 +180,8 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode {
amountString = tonAmountAttributedString(cryptoValue, integralFont: integralFont, fractionalFont: fractionalFont, color: item.presentationData.theme.list.itemPrimaryTextColor)
value = stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))"
} else if let stats = item.stats as? StarsRevenueStats {
amountString = NSAttributedString(string: presentationStringsFormattedNumber(Int32(stats.balances.availableBalance), item.presentationData.dateTimeFormat.groupingSeparator), font: integralFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
value = stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))"
amountString = NSAttributedString(string: presentationStringsFormattedNumber(stats.balances.availableBalance, item.presentationData.dateTimeFormat.groupingSeparator), font: integralFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
value = stats.balances.availableBalance == StarsAmount.zero ? "" : "\(formatTonUsdValue(stats.balances.availableBalance.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))"
isStars = true
} else {
fatalError()

View File

@ -277,8 +277,9 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
let itemLabel: NSAttributedString
let labelString: String
let formattedLabel = presentationStringsFormattedNumber(abs(Int32(item.transaction.count)), item.presentationData.dateTimeFormat.groupingSeparator)
if item.transaction.count < 0 {
let absCount = StarsAmount(value: abs(item.transaction.count.value), nanos: abs(item.transaction.count.nanos))
let formattedLabel = presentationStringsFormattedNumber(absCount, item.presentationData.dateTimeFormat.groupingSeparator)
if item.transaction.count < StarsAmount.zero {
labelString = "- \(formattedLabel)"
} else {
labelString = "+ \(formattedLabel)"

View File

@ -764,7 +764,7 @@ class StatsOverviewItemNode: ListViewItemNode {
height += topLeftItemLayoutAndApply!.0.height * 4.0 + verticalSpacing * 3.0
}
} else if let stats = item.stats as? RevenueStats {
if let additionalStats = item.additionalStats as? StarsRevenueStats, additionalStats.balances.overallRevenue > 0 {
if let additionalStats = item.additionalStats as? StarsRevenueStats, additionalStats.balances.overallRevenue > StarsAmount.zero {
twoColumnLayout = true
useMinLeftColumnWidth = true
@ -802,9 +802,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
presentationStringsFormattedNumber(Int32(additionalStats.balances.availableBalance), item.presentationData.dateTimeFormat.groupingSeparator),
presentationStringsFormattedNumber(additionalStats.balances.availableBalance, item.presentationData.dateTimeFormat.groupingSeparator),
" ",
(additionalStats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.availableBalance, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
(additionalStats.balances.availableBalance == StarsAmount.zero ? "" : "\(formatTonUsdValue(additionalStats.balances.availableBalance.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -812,9 +812,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
presentationStringsFormattedNumber(Int32(additionalStats.balances.currentBalance), item.presentationData.dateTimeFormat.groupingSeparator),
presentationStringsFormattedNumber(additionalStats.balances.currentBalance, item.presentationData.dateTimeFormat.groupingSeparator),
" ",
(additionalStats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.currentBalance, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
(additionalStats.balances.currentBalance == StarsAmount.zero ? "" : "\(formatTonUsdValue(additionalStats.balances.currentBalance.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -822,9 +822,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
presentationStringsFormattedNumber(Int32(additionalStats.balances.overallRevenue), item.presentationData.dateTimeFormat.groupingSeparator),
presentationStringsFormattedNumber(additionalStats.balances.overallRevenue, item.presentationData.dateTimeFormat.groupingSeparator),
" ",
(additionalStats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(additionalStats.balances.overallRevenue, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
(additionalStats.balances.overallRevenue == StarsAmount.zero ? "" : "\(formatTonUsdValue(additionalStats.balances.overallRevenue.value, divide: false, rate: additionalStats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -871,9 +871,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
presentationStringsFormattedNumber(Int32(stats.balances.availableBalance), item.presentationData.dateTimeFormat.groupingSeparator),
presentationStringsFormattedNumber(stats.balances.availableBalance, item.presentationData.dateTimeFormat.groupingSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Available,
(stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
(stats.balances.availableBalance == StarsAmount.zero ? "" : "\(formatTonUsdValue(stats.balances.availableBalance.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -881,9 +881,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
presentationStringsFormattedNumber(Int32(stats.balances.currentBalance), item.presentationData.dateTimeFormat.groupingSeparator),
presentationStringsFormattedNumber(stats.balances.currentBalance, item.presentationData.dateTimeFormat.groupingSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Current,
(stats.balances.currentBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.currentBalance, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
(stats.balances.currentBalance == StarsAmount.zero ? "" : "\(formatTonUsdValue(stats.balances.currentBalance.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)
@ -891,9 +891,9 @@ class StatsOverviewItemNode: ListViewItemNode {
item.context,
params.width,
item.presentationData,
presentationStringsFormattedNumber(Int32(stats.balances.overallRevenue), item.presentationData.dateTimeFormat.groupingSeparator),
presentationStringsFormattedNumber(stats.balances.overallRevenue, item.presentationData.dateTimeFormat.groupingSeparator),
item.presentationData.strings.Monetization_StarsProceeds_Total,
(stats.balances.overallRevenue == 0 ? "" : "\(formatTonUsdValue(stats.balances.overallRevenue, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
(stats.balances.overallRevenue == StarsAmount.zero ? "" : "\(formatTonUsdValue(stats.balances.overallRevenue.value, divide: false, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))", .generic),
.stars
)

View File

@ -904,6 +904,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
dict[1237678029] = { return Api.StarGift.parse_starGift($0) }
dict[708628759] = { return Api.StarRefProgram.parse_starRefProgram($0) }
dict[-1145654109] = { return Api.StarsAmount.parse_starsAmount($0) }
dict[1577421297] = { return Api.StarsGiftOption.parse_starsGiftOption($0) }
dict[-1798404822] = { return Api.StarsGiveawayOption.parse_starsGiveawayOption($0) }
dict[1411605001] = { return Api.StarsGiveawayWinnersOption.parse_starsGiveawayWinnersOption($0) }
@ -911,7 +912,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[779004698] = { return Api.StarsSubscription.parse_starsSubscription($0) }
dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) }
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) }
dict[-1352584166] = { return Api.StarsTransaction.parse_starsTransaction($0) }
dict[1692387622] = { return Api.StarsTransaction.parse_starsTransaction($0) }
dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
dict[-110658899] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAPI($0) }
dict[1617438738] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAds($0) }
@ -1089,7 +1090,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[2103604867] = { return Api.Update.parse_updateSentStoryReaction($0) }
dict[-337352679] = { return Api.Update.parse_updateServiceNotification($0) }
dict[-245208620] = { return Api.Update.parse_updateSmsJob($0) }
dict[1042067513] = { return Api.Update.parse_updateStarsBalance($0) }
dict[1317053305] = { return Api.Update.parse_updateStarsBalance($0) }
dict[-1518030823] = { return Api.Update.parse_updateStarsRevenueStatus($0) }
dict[834816008] = { return Api.Update.parse_updateStickerSets($0) }
dict[196268545] = { return Api.Update.parse_updateStickerSetsOrder($0) }
@ -1302,6 +1303,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[913709011] = { return Api.messages.ForumTopics.parse_forumTopics($0) }
dict[-1963942446] = { return Api.messages.FoundStickerSets.parse_foundStickerSets($0) }
dict[223655517] = { return Api.messages.FoundStickerSets.parse_foundStickerSetsNotModified($0) }
dict[-2100698480] = { return Api.messages.FoundStickers.parse_foundStickers($0) }
dict[1611711796] = { return Api.messages.FoundStickers.parse_foundStickersNotModified($0) }
dict[-1707344487] = { return Api.messages.HighScores.parse_highScores($0) }
dict[375566091] = { return Api.messages.HistoryImport.parse_historyImport($0) }
dict[1578088377] = { return Api.messages.HistoryImportParsed.parse_historyImportParsed($0) }
@ -1367,7 +1370,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[961445665] = { return Api.payments.StarsRevenueAdsAccountUrl.parse_starsRevenueAdsAccountUrl($0) }
dict[-919881925] = { return Api.payments.StarsRevenueStats.parse_starsRevenueStats($0) }
dict[497778871] = { return Api.payments.StarsRevenueWithdrawalUrl.parse_starsRevenueWithdrawalUrl($0) }
dict[1447376356] = { return Api.payments.StarsStatus.parse_starsStatus($0) }
dict[1822222573] = { return Api.payments.StarsStatus.parse_starsStatus($0) }
dict[-937776981] = { return Api.payments.SuggestedStarRefBots.parse_suggestedStarRefBots($0) }
dict[1801827607] = { return Api.payments.UserStarGifts.parse_userStarGifts($0) }
dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) }
@ -2052,6 +2055,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.StarRefProgram:
_1.serialize(buffer, boxed)
case let _1 as Api.StarsAmount:
_1.serialize(buffer, boxed)
case let _1 as Api.StarsGiftOption:
_1.serialize(buffer, boxed)
case let _1 as Api.StarsGiveawayOption:
@ -2354,6 +2359,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.messages.FoundStickerSets:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.FoundStickers:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.HighScores:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.HistoryImport:

View File

@ -690,6 +690,46 @@ public extension Api {
}
}
public extension Api {
enum StarsAmount: TypeConstructorDescription {
case starsAmount(amount: Int64, nanos: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsAmount(let amount, let nanos):
if boxed {
buffer.appendInt32(-1145654109)
}
serializeInt64(amount, buffer: buffer, boxed: false)
serializeInt32(nanos, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsAmount(let amount, let nanos):
return ("starsAmount", [("amount", amount as Any), ("nanos", nanos as Any)])
}
}
public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StarsAmount.starsAmount(amount: _1!, nanos: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
enum StarsGiftOption: TypeConstructorDescription {
case starsGiftOption(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64)
@ -1018,55 +1058,3 @@ public extension Api {
}
}
public extension Api {
enum StarsTopupOption: TypeConstructorDescription {
case starsTopupOption(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsTopupOption(let flags, let stars, let storeProduct, let currency, let amount):
if boxed {
buffer.appendInt32(198776256)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(stars, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)}
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsTopupOption(let flags, let stars, let storeProduct, let currency, let amount):
return ("starsTopupOption", [("flags", flags as Any), ("stars", stars as Any), ("storeProduct", storeProduct as Any), ("currency", currency as Any), ("amount", amount as Any)])
}
}
public static func parse_starsTopupOption(_ reader: BufferReader) -> StarsTopupOption? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: String?
if Int(_1!) & Int(1 << 0) != 0 {_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 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.StarsTopupOption.starsTopupOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!)
}
else {
return nil
}
}
}
}

View File

@ -1,17 +1,68 @@
public extension Api {
enum StarsTransaction: TypeConstructorDescription {
case starsTransaction(flags: Int32, id: String, stars: Int64, starNanos: Int32?, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipNumber: Int32?)
enum StarsTopupOption: TypeConstructorDescription {
case starsTopupOption(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsTransaction(let flags, let id, let stars, let starNanos, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber):
case .starsTopupOption(let flags, let stars, let storeProduct, let currency, let amount):
if boxed {
buffer.appendInt32(-1352584166)
buffer.appendInt32(198776256)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(stars, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)}
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(amount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsTopupOption(let flags, let stars, let storeProduct, let currency, let amount):
return ("starsTopupOption", [("flags", flags as Any), ("stars", stars as Any), ("storeProduct", storeProduct as Any), ("currency", currency as Any), ("amount", amount as Any)])
}
}
public static func parse_starsTopupOption(_ reader: BufferReader) -> StarsTopupOption? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: String?
if Int(_1!) & Int(1 << 0) != 0 {_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 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.StarsTopupOption.starsTopupOption(flags: _1!, stars: _2!, storeProduct: _3, currency: _4!, amount: _5!)
}
else {
return nil
}
}
}
}
public extension Api {
enum StarsTransaction: TypeConstructorDescription {
case starsTransaction(flags: Int32, id: String, stars: Api.StarsAmount, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipNumber: Int32?, starrefCommissionPermille: Int32?, starrefPeer: Api.Peer?, starrefAmount: Api.StarsAmount?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber, let starrefCommissionPermille, let starrefPeer, let starrefAmount):
if boxed {
buffer.appendInt32(1692387622)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(id, buffer: buffer, boxed: false)
serializeInt64(stars, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 16) != 0 {serializeInt32(starNanos!, buffer: buffer, boxed: false)}
stars.serialize(buffer, true)
serializeInt32(date, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
@ -30,14 +81,17 @@ public extension Api {
if Int(flags) & Int(1 << 13) != 0 {serializeInt32(giveawayPostId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 14) != 0 {stargift!.serialize(buffer, true)}
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipNumber!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 16) != 0 {serializeInt32(starrefCommissionPermille!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 17) != 0 {starrefPeer!.serialize(buffer, true)}
if Int(flags) & Int(1 << 17) != 0 {starrefAmount!.serialize(buffer, true)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsTransaction(let flags, let id, let stars, let starNanos, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber):
return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("starNanos", starNanos as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipNumber", floodskipNumber as Any)])
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber, let starrefCommissionPermille, let starrefPeer, let starrefAmount):
return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipNumber", floodskipNumber as Any), ("starrefCommissionPermille", starrefCommissionPermille as Any), ("starrefPeer", starrefPeer as Any), ("starrefAmount", starrefAmount as Any)])
}
}
@ -46,66 +100,78 @@ public extension Api {
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Int64?
_3 = reader.readInt64()
var _4: Int32?
if Int(_1!) & Int(1 << 16) != 0 {_4 = reader.readInt32() }
var _5: Int32?
_5 = reader.readInt32()
var _6: Api.StarsTransactionPeer?
var _3: Api.StarsAmount?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.StarsTransactionPeer
_3 = Api.parse(reader, signature: signature) as? Api.StarsAmount
}
var _4: Int32?
_4 = reader.readInt32()
var _5: Api.StarsTransactionPeer?
if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.StarsTransactionPeer
}
var _6: String?
if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) }
var _7: String?
if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) }
var _8: String?
if Int(_1!) & Int(1 << 1) != 0 {_8 = parseString(reader) }
var _9: Api.WebDocument?
if Int(_1!) & Int(1 << 1) != 0 {_7 = parseString(reader) }
var _8: Api.WebDocument?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.WebDocument
_8 = Api.parse(reader, signature: signature) as? Api.WebDocument
} }
var _10: Int32?
if Int(_1!) & Int(1 << 5) != 0 {_10 = reader.readInt32() }
var _11: String?
if Int(_1!) & Int(1 << 5) != 0 {_11 = parseString(reader) }
var _12: Buffer?
if Int(_1!) & Int(1 << 7) != 0 {_12 = parseBytes(reader) }
var _13: Int32?
if Int(_1!) & Int(1 << 8) != 0 {_13 = reader.readInt32() }
var _14: [Api.MessageMedia]?
var _9: Int32?
if Int(_1!) & Int(1 << 5) != 0 {_9 = reader.readInt32() }
var _10: String?
if Int(_1!) & Int(1 << 5) != 0 {_10 = parseString(reader) }
var _11: Buffer?
if Int(_1!) & Int(1 << 7) != 0 {_11 = parseBytes(reader) }
var _12: Int32?
if Int(_1!) & Int(1 << 8) != 0 {_12 = reader.readInt32() }
var _13: [Api.MessageMedia]?
if Int(_1!) & Int(1 << 9) != 0 {if let _ = reader.readInt32() {
_14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self)
_13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageMedia.self)
} }
var _14: Int32?
if Int(_1!) & Int(1 << 12) != 0 {_14 = reader.readInt32() }
var _15: Int32?
if Int(_1!) & Int(1 << 12) != 0 {_15 = reader.readInt32() }
var _16: Int32?
if Int(_1!) & Int(1 << 13) != 0 {_16 = reader.readInt32() }
var _17: Api.StarGift?
if Int(_1!) & Int(1 << 13) != 0 {_15 = reader.readInt32() }
var _16: Api.StarGift?
if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() {
_17 = Api.parse(reader, signature: signature) as? Api.StarGift
_16 = Api.parse(reader, signature: signature) as? Api.StarGift
} }
var _17: Int32?
if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() }
var _18: Int32?
if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() }
if Int(_1!) & Int(1 << 16) != 0 {_18 = reader.readInt32() }
var _19: Api.Peer?
if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() {
_19 = Api.parse(reader, signature: signature) as? Api.Peer
} }
var _20: Api.StarsAmount?
if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() {
_20 = Api.parse(reader, signature: signature) as? Api.StarsAmount
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 16) == 0) || _4 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 2) == 0) || _9 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil
let _c9 = (Int(_1!) & Int(1 << 5) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 5) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 5) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 7) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 8) == 0) || _13 != nil
let _c14 = (Int(_1!) & Int(1 << 9) == 0) || _14 != nil
let _c15 = (Int(_1!) & Int(1 << 12) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 13) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 14) == 0) || _17 != nil
let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 {
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, starNanos: _4, date: _5!, peer: _6!, title: _7, description: _8, photo: _9, transactionDate: _10, transactionUrl: _11, botPayload: _12, msgId: _13, extendedMedia: _14, subscriptionPeriod: _15, giveawayPostId: _16, stargift: _17, floodskipNumber: _18)
let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil
let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 9) == 0) || _13 != nil
let _c14 = (Int(_1!) & Int(1 << 12) == 0) || _14 != nil
let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil
let _c18 = (Int(_1!) & Int(1 << 16) == 0) || _18 != nil
let _c19 = (Int(_1!) & Int(1 << 17) == 0) || _19 != nil
let _c20 = (Int(_1!) & Int(1 << 17) == 0) || _20 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 {
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17, starrefCommissionPermille: _18, starrefPeer: _19, starrefAmount: _20)
}
else {
return nil
@ -1196,101 +1262,3 @@ public extension Api {
}
}
public extension Api {
indirect enum StoryReaction: TypeConstructorDescription {
case storyReaction(peerId: Api.Peer, date: Int32, reaction: Api.Reaction)
case storyReactionPublicForward(message: Api.Message)
case storyReactionPublicRepost(peerId: Api.Peer, story: Api.StoryItem)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .storyReaction(let peerId, let date, let reaction):
if boxed {
buffer.appendInt32(1620104917)
}
peerId.serialize(buffer, true)
serializeInt32(date, buffer: buffer, boxed: false)
reaction.serialize(buffer, true)
break
case .storyReactionPublicForward(let message):
if boxed {
buffer.appendInt32(-1146411453)
}
message.serialize(buffer, true)
break
case .storyReactionPublicRepost(let peerId, let story):
if boxed {
buffer.appendInt32(-808644845)
}
peerId.serialize(buffer, true)
story.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .storyReaction(let peerId, let date, let reaction):
return ("storyReaction", [("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)])
case .storyReactionPublicForward(let message):
return ("storyReactionPublicForward", [("message", message as Any)])
case .storyReactionPublicRepost(let peerId, let story):
return ("storyReactionPublicRepost", [("peerId", peerId as Any), ("story", story as Any)])
}
}
public static func parse_storyReaction(_ reader: BufferReader) -> StoryReaction? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.Reaction?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Reaction
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.StoryReaction.storyReaction(peerId: _1!, date: _2!, reaction: _3!)
}
else {
return nil
}
}
public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? {
var _1: Api.Message?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Message
}
let _c1 = _1 != nil
if _c1 {
return Api.StoryReaction.storyReactionPublicForward(message: _1!)
}
else {
return nil
}
}
public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Api.StoryItem?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.StoryItem
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StoryReaction.storyReactionPublicRepost(peerId: _1!, story: _2!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,101 @@
public extension Api {
indirect enum StoryReaction: TypeConstructorDescription {
case storyReaction(peerId: Api.Peer, date: Int32, reaction: Api.Reaction)
case storyReactionPublicForward(message: Api.Message)
case storyReactionPublicRepost(peerId: Api.Peer, story: Api.StoryItem)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .storyReaction(let peerId, let date, let reaction):
if boxed {
buffer.appendInt32(1620104917)
}
peerId.serialize(buffer, true)
serializeInt32(date, buffer: buffer, boxed: false)
reaction.serialize(buffer, true)
break
case .storyReactionPublicForward(let message):
if boxed {
buffer.appendInt32(-1146411453)
}
message.serialize(buffer, true)
break
case .storyReactionPublicRepost(let peerId, let story):
if boxed {
buffer.appendInt32(-808644845)
}
peerId.serialize(buffer, true)
story.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .storyReaction(let peerId, let date, let reaction):
return ("storyReaction", [("peerId", peerId as Any), ("date", date as Any), ("reaction", reaction as Any)])
case .storyReactionPublicForward(let message):
return ("storyReactionPublicForward", [("message", message as Any)])
case .storyReactionPublicRepost(let peerId, let story):
return ("storyReactionPublicRepost", [("peerId", peerId as Any), ("story", story as Any)])
}
}
public static func parse_storyReaction(_ reader: BufferReader) -> StoryReaction? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32?
_2 = reader.readInt32()
var _3: Api.Reaction?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Reaction
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.StoryReaction.storyReaction(peerId: _1!, date: _2!, reaction: _3!)
}
else {
return nil
}
}
public static func parse_storyReactionPublicForward(_ reader: BufferReader) -> StoryReaction? {
var _1: Api.Message?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Message
}
let _c1 = _1 != nil
if _c1 {
return Api.StoryReaction.storyReactionPublicForward(message: _1!)
}
else {
return nil
}
}
public static func parse_storyReactionPublicRepost(_ reader: BufferReader) -> StoryReaction? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Api.StoryItem?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.StoryItem
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StoryReaction.storyReactionPublicRepost(peerId: _1!, story: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum StoryView: TypeConstructorDescription {
case storyView(flags: Int32, userId: Int64, date: Int32, reaction: Api.Reaction?)
@ -798,7 +896,7 @@ public extension Api {
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)
case updateStarsBalance(flags: Int32, balance: Int64, balanceNanos: Int32?)
case updateStarsBalance(balance: Api.StarsAmount)
case updateStarsRevenueStatus(peer: Api.Peer, status: Api.StarsRevenueStatus)
case updateStickerSets(flags: Int32)
case updateStickerSetsOrder(flags: Int32, order: [Int64])
@ -1919,13 +2017,11 @@ public extension Api {
}
serializeString(jobId, buffer: buffer, boxed: false)
break
case .updateStarsBalance(let flags, let balance, let balanceNanos):
case .updateStarsBalance(let balance):
if boxed {
buffer.appendInt32(1042067513)
buffer.appendInt32(1317053305)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(balance, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(balanceNanos!, buffer: buffer, boxed: false)}
balance.serialize(buffer, true)
break
case .updateStarsRevenueStatus(let peer, let status):
if boxed {
@ -2301,8 +2397,8 @@ public extension Api {
return ("updateServiceNotification", [("flags", flags as Any), ("inboxDate", inboxDate as Any), ("type", type as Any), ("message", message as Any), ("media", media as Any), ("entities", entities as Any)])
case .updateSmsJob(let jobId):
return ("updateSmsJob", [("jobId", jobId as Any)])
case .updateStarsBalance(let flags, let balance, let balanceNanos):
return ("updateStarsBalance", [("flags", flags as Any), ("balance", balance as Any), ("balanceNanos", balanceNanos as Any)])
case .updateStarsBalance(let balance):
return ("updateStarsBalance", [("balance", balance as Any)])
case .updateStarsRevenueStatus(let peer, let status):
return ("updateStarsRevenueStatus", [("peer", peer as Any), ("status", status as Any)])
case .updateStickerSets(let flags):
@ -4556,17 +4652,13 @@ public extension Api {
}
}
public static func parse_updateStarsBalance(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
var _1: Api.StarsAmount?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StarsAmount
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.Update.updateStarsBalance(flags: _1!, balance: _2!, balanceNanos: _3)
if _c1 {
return Api.Update.updateStarsBalance(balance: _1!)
}
else {
return nil

View File

@ -56,6 +56,84 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum FoundStickers: TypeConstructorDescription {
case foundStickers(flags: Int32, nextOffset: Int32?, hash: Int64, stickers: [Api.Document])
case foundStickersNotModified(flags: Int32, nextOffset: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .foundStickers(let flags, let nextOffset, let hash, let stickers):
if boxed {
buffer.appendInt32(-2100698480)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextOffset!, buffer: buffer, boxed: false)}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stickers.count))
for item in stickers {
item.serialize(buffer, true)
}
break
case .foundStickersNotModified(let flags, let nextOffset):
if boxed {
buffer.appendInt32(1611711796)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextOffset!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .foundStickers(let flags, let nextOffset, let hash, let stickers):
return ("foundStickers", [("flags", flags as Any), ("nextOffset", nextOffset as Any), ("hash", hash as Any), ("stickers", stickers as Any)])
case .foundStickersNotModified(let flags, let nextOffset):
return ("foundStickersNotModified", [("flags", flags as Any), ("nextOffset", nextOffset as Any)])
}
}
public static func parse_foundStickers(_ reader: BufferReader) -> FoundStickers? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() }
var _3: Int64?
_3 = reader.readInt64()
var _4: [Api.Document]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.messages.FoundStickers.foundStickers(flags: _1!, nextOffset: _2, hash: _3!, stickers: _4!)
}
else {
return nil
}
}
public static func parse_foundStickersNotModified(_ reader: BufferReader) -> FoundStickers? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
if _c1 && _c2 {
return Api.messages.FoundStickers.foundStickersNotModified(flags: _1!, nextOffset: _2)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum HighScores: TypeConstructorDescription {
case highScores(scores: [Api.HighScore], users: [Api.User])
@ -1418,49 +1496,3 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum SearchCounter: TypeConstructorDescription {
case searchCounter(flags: Int32, filter: Api.MessagesFilter, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .searchCounter(let flags, let filter, let count):
if boxed {
buffer.appendInt32(-398136321)
}
serializeInt32(flags, buffer: buffer, boxed: false)
filter.serialize(buffer, true)
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .searchCounter(let flags, let filter, let count):
return ("searchCounter", [("flags", flags as Any), ("filter", filter as Any), ("count", count as Any)])
}
}
public static func parse_searchCounter(_ reader: BufferReader) -> SearchCounter? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.MessagesFilter?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.MessagesFilter
}
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.messages.SearchCounter.searchCounter(flags: _1!, filter: _2!, count: _3!)
}
else {
return nil
}
}
}
}

View File

@ -1,3 +1,49 @@
public extension Api.messages {
enum SearchCounter: TypeConstructorDescription {
case searchCounter(flags: Int32, filter: Api.MessagesFilter, count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .searchCounter(let flags, let filter, let count):
if boxed {
buffer.appendInt32(-398136321)
}
serializeInt32(flags, buffer: buffer, boxed: false)
filter.serialize(buffer, true)
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .searchCounter(let flags, let filter, let count):
return ("searchCounter", [("flags", flags as Any), ("filter", filter as Any), ("count", count as Any)])
}
}
public static func parse_searchCounter(_ reader: BufferReader) -> SearchCounter? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.MessagesFilter?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.MessagesFilter
}
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.messages.SearchCounter.searchCounter(flags: _1!, filter: _2!, count: _3!)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum SearchResultsCalendar: TypeConstructorDescription {
case searchResultsCalendar(flags: Int32, count: Int32, minDate: Int32, minMsgId: Int32, offsetIdOffset: Int32?, periods: [Api.SearchResultsCalendarPeriod], messages: [Api.Message], chats: [Api.Chat], users: [Api.User])

View File

@ -120,17 +120,16 @@ public extension Api.payments {
}
public extension Api.payments {
enum StarsStatus: TypeConstructorDescription {
case starsStatus(flags: Int32, balance: Int64, balanceNanos: Int32?, subscriptions: [Api.StarsSubscription]?, subscriptionsNextOffset: String?, subscriptionsMissingBalance: Int64?, history: [Api.StarsTransaction]?, nextOffset: String?, chats: [Api.Chat], users: [Api.User])
case starsStatus(flags: Int32, balance: Api.StarsAmount, subscriptions: [Api.StarsSubscription]?, subscriptionsNextOffset: String?, subscriptionsMissingBalance: Int64?, history: [Api.StarsTransaction]?, nextOffset: String?, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsStatus(let flags, let balance, let balanceNanos, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
case .starsStatus(let flags, let balance, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
if boxed {
buffer.appendInt32(1447376356)
buffer.appendInt32(1822222573)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(balance, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 5) != 0 {serializeInt32(balanceNanos!, buffer: buffer, boxed: false)}
balance.serialize(buffer, true)
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(subscriptions!.count))
for item in subscriptions! {
@ -160,52 +159,51 @@ public extension Api.payments {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsStatus(let flags, let balance, let balanceNanos, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
return ("starsStatus", [("flags", flags as Any), ("balance", balance as Any), ("balanceNanos", balanceNanos as Any), ("subscriptions", subscriptions as Any), ("subscriptionsNextOffset", subscriptionsNextOffset as Any), ("subscriptionsMissingBalance", subscriptionsMissingBalance as Any), ("history", history as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
case .starsStatus(let flags, let balance, let subscriptions, let subscriptionsNextOffset, let subscriptionsMissingBalance, let history, let nextOffset, let chats, let users):
return ("starsStatus", [("flags", flags as Any), ("balance", balance as Any), ("subscriptions", subscriptions as Any), ("subscriptionsNextOffset", subscriptionsNextOffset as Any), ("subscriptionsMissingBalance", subscriptionsMissingBalance as Any), ("history", history as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_starsStatus(_ reader: BufferReader) -> StarsStatus? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
if Int(_1!) & Int(1 << 5) != 0 {_3 = reader.readInt32() }
var _4: [Api.StarsSubscription]?
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsSubscription.self)
} }
var _5: String?
if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) }
var _6: Int64?
if Int(_1!) & Int(1 << 4) != 0 {_6 = reader.readInt64() }
var _7: [Api.StarsTransaction]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self)
} }
var _8: String?
if Int(_1!) & Int(1 << 0) != 0 {_8 = parseString(reader) }
var _9: [Api.Chat]?
if let _ = reader.readInt32() {
_9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
var _2: Api.StarsAmount?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.StarsAmount
}
var _10: [Api.User]?
var _3: [Api.StarsSubscription]?
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsSubscription.self)
} }
var _4: String?
if Int(_1!) & Int(1 << 2) != 0 {_4 = parseString(reader) }
var _5: Int64?
if Int(_1!) & Int(1 << 4) != 0 {_5 = reader.readInt64() }
var _6: [Api.StarsTransaction]?
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarsTransaction.self)
} }
var _7: String?
if Int(_1!) & Int(1 << 0) != 0 {_7 = parseString(reader) }
var _8: [Api.Chat]?
if let _ = reader.readInt32() {
_10 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _9: [Api.User]?
if let _ = reader.readInt32() {
_9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 0) == 0) || _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
return Api.payments.StarsStatus.starsStatus(flags: _1!, balance: _2!, balanceNanos: _3, subscriptions: _4, subscriptionsNextOffset: _5, subscriptionsMissingBalance: _6, history: _7, nextOffset: _8, chats: _9!, users: _10!)
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.payments.StarsStatus.starsStatus(flags: _1!, balance: _2!, subscriptions: _3, subscriptionsNextOffset: _4, subscriptionsMissingBalance: _5, history: _6, nextOffset: _7, chats: _8!, users: _9!)
}
else {
return nil

View File

@ -7872,6 +7872,26 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func searchStickers(flags: Int32, q: String, langCode: String, offset: Int32, limit: Int32, hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.FoundStickers>) {
let buffer = Buffer()
buffer.appendInt32(1277568311)
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(q, buffer: buffer, boxed: false)
serializeString(langCode, buffer: buffer, boxed: false)
serializeInt32(offset, buffer: buffer, boxed: false)
serializeInt32(limit, buffer: buffer, boxed: false)
serializeInt64(hash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "messages.searchStickers", parameters: [("flags", String(describing: flags)), ("q", String(describing: q)), ("langCode", String(describing: langCode)), ("offset", String(describing: offset)), ("limit", String(describing: limit)), ("hash", String(describing: hash))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.FoundStickers? in
let reader = BufferReader(buffer)
var result: Api.messages.FoundStickers?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.FoundStickers
}
return result
})
}
}
public extension Api.functions.messages {
static func sendBotRequestedPeer(peer: Api.InputPeer, msgId: Int32, buttonId: Int32, requestedPeers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()

View File

@ -1,4 +1,5 @@
import Foundation
import UIKit
import SwiftSignalKit
import AVFoundation

View File

@ -129,7 +129,7 @@ enum AccountStateMutationOperation {
case UpdateNewAuthorization(isUnconfirmed: Bool, hash: Int64, date: Int32, device: String, location: String)
case UpdateWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?)
case UpdateRevenueBalances(peerId: PeerId, balances: RevenueStats.Balances)
case UpdateStarsBalance(peerId: PeerId, balance: Int64)
case UpdateStarsBalance(peerId: PeerId, balance: Api.StarsAmount)
case UpdateStarsRevenueStatus(peerId: PeerId, status: StarsRevenueStats.Balances)
case UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: Bool)
}
@ -690,7 +690,7 @@ struct AccountMutableState {
self.addOperation(.UpdateRevenueBalances(peerId: peerId, balances: balances))
}
mutating func updateStarsBalance(peerId: PeerId, balance: Int64) {
mutating func updateStarsBalance(peerId: PeerId, balance: Api.StarsAmount) {
self.addOperation(.UpdateStarsBalance(peerId: peerId, balance: balance))
}
@ -849,7 +849,7 @@ struct AccountReplayedFinalState {
let updateConfig: Bool
let isPremiumUpdated: Bool
let updatedRevenueBalances: [PeerId: RevenueStats.Balances]
let updatedStarsBalance: [PeerId: Int64]
let updatedStarsBalance: [PeerId: StarsAmount]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let sentScheduledMessageIds: Set<MessageId>
}
@ -880,14 +880,14 @@ struct AccountFinalStateEvents {
let updateConfig: Bool
let isPremiumUpdated: Bool
let updatedRevenueBalances: [PeerId: RevenueStats.Balances]
let updatedStarsBalance: [PeerId: Int64]
let updatedStarsBalance: [PeerId: StarsAmount]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty
}
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: Int64] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set()) {
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set()) {
self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds

View File

@ -1785,7 +1785,7 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
updatedState.updateWallpaper(peerId: peer.peerId, wallpaper: wallpaper.flatMap { TelegramWallpaper(apiWallpaper: $0) })
case let .updateBroadcastRevenueTransactions(peer, balances):
updatedState.updateRevenueBalances(peerId: peer.peerId, balances: RevenueStats.Balances(apiRevenueBalances: balances))
case let .updateStarsBalance(_, balance, _):
case let .updateStarsBalance(balance):
updatedState.updateStarsBalance(peerId: accountPeerId, balance: balance)
case let .updateStarsRevenueStatus(peer, status):
updatedState.updateStarsRevenueStatus(peerId: peer.peerId, status: StarsRevenueStats.Balances(apiStarsRevenueStatus: status))
@ -3418,7 +3418,7 @@ func replayFinalState(
var syncAttachMenuBots = false
var updateConfig = false
var updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:]
var updatedStarsBalance: [PeerId: Int64] = [:]
var updatedStarsBalance: [PeerId: StarsAmount] = [:]
var updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:]
var updatedStarsReactionsAreAnonymousByDefault: Bool?
@ -4846,7 +4846,7 @@ func replayFinalState(
case let .UpdateRevenueBalances(peerId, balances):
updatedRevenueBalances[peerId] = balances
case let .UpdateStarsBalance(peerId, balance):
updatedStarsBalance[peerId] = balance
updatedStarsBalance[peerId] = StarsAmount(apiAmount: balance)
case let .UpdateStarsRevenueStatus(peerId, status):
updatedStarsRevenueStatus[peerId] = status
case let .UpdateStarsReactionsAreAnonymousByDefault(value):

View File

@ -49,7 +49,7 @@ private final class UpdatedRevenueBalancesSubscriberContext {
}
private final class UpdatedStarsBalanceSubscriberContext {
let subscribers = Bag<([PeerId: Int64]) -> Void>()
let subscribers = Bag<([PeerId: StarsAmount]) -> Void>()
}
private final class UpdatedStarsRevenueStatusSubscriberContext {
@ -1705,7 +1705,7 @@ public final class AccountStateManager {
}
}
public func updatedStarsBalance() -> Signal<[PeerId: Int64], NoError> {
public func updatedStarsBalance() -> Signal<[PeerId: StarsAmount], NoError> {
let queue = self.queue
return Signal { [weak self] subscriber in
let disposable = MetaDisposable()
@ -1726,7 +1726,7 @@ public final class AccountStateManager {
}
}
private func notifyUpdatedStarsBalance(_ updatedStarsBalance: [PeerId: Int64]) {
private func notifyUpdatedStarsBalance(_ updatedStarsBalance: [PeerId: StarsAmount]) {
for subscriber in self.updatedStarsBalanceContext.subscribers.copyItems() {
subscriber(updatedStarsBalance)
}
@ -2084,7 +2084,7 @@ public final class AccountStateManager {
}
}
public func updatedStarsBalance() -> Signal<[PeerId: Int64], NoError> {
public func updatedStarsBalance() -> Signal<[PeerId: StarsAmount], NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.updatedStarsBalance().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
}

View File

@ -412,7 +412,7 @@ private func requestSendStarsReaction(postbox: Postbox, network: Network, stateM
return .generic
}
|> mapToSignal { result -> Signal<Never, RequestUpdateMessageReactionError> in
stateManager.starsContext?.add(balance: Int64(-count), addTransaction: false)
stateManager.starsContext?.add(balance: StarsAmount(value: Int64(-count), nanos: 0), addTransaction: false)
return postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .sendStarsReaction, id: messageId, action: UpdateMessageReactionsAction())

View File

@ -24,18 +24,21 @@ public struct StarsRevenueStats: Equatable, Codable {
case overallRevenue
case withdrawEnabled
case nextWithdrawalTimestamp
case currentBalanceStars
case availableBalanceStars
case overallRevenueStars
}
public let currentBalance: Int64
public let availableBalance: Int64
public let overallRevenue: Int64
public let currentBalance: StarsAmount
public let availableBalance: StarsAmount
public let overallRevenue: StarsAmount
public let withdrawEnabled: Bool
public let nextWithdrawalTimestamp: Int32?
public init(
currentBalance: Int64,
availableBalance: Int64,
overallRevenue: Int64,
currentBalance: StarsAmount,
availableBalance: StarsAmount,
overallRevenue: StarsAmount,
withdrawEnabled: Bool,
nextWithdrawalTimestamp: Int32?
) {
@ -48,18 +51,35 @@ public struct StarsRevenueStats: Equatable, Codable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.currentBalance = try container.decode(Int64.self, forKey: .currentBalance)
self.availableBalance = try container.decode(Int64.self, forKey: .availableBalance)
self.overallRevenue = try container.decode(Int64.self, forKey: .overallRevenue)
if let legacyCurrentBalance = try container.decodeIfPresent(Int64.self, forKey: .currentBalance) {
self.currentBalance = StarsAmount(value: legacyCurrentBalance, nanos: 0)
} else {
self.currentBalance = try container.decode(StarsAmount.self, forKey: .currentBalanceStars)
}
if let legacyAvailableBalance = try container.decodeIfPresent(Int64.self, forKey: .availableBalance) {
self.availableBalance = StarsAmount(value: legacyAvailableBalance, nanos: 0)
} else {
self.availableBalance = try container.decode(StarsAmount.self, forKey: .availableBalanceStars)
}
if let legacyOverallRevenue = try container.decodeIfPresent(Int64.self, forKey: .overallRevenue) {
self.overallRevenue = StarsAmount(value: legacyOverallRevenue, nanos: 0)
} else {
self.overallRevenue = try container.decode(StarsAmount.self, forKey: .overallRevenueStars)
}
self.withdrawEnabled = try container.decode(Bool.self, forKey: .withdrawEnabled)
self.nextWithdrawalTimestamp = try container.decodeIfPresent(Int32.self, forKey: .nextWithdrawalTimestamp)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.currentBalance, forKey: .currentBalance)
try container.encode(self.availableBalance, forKey: .availableBalance)
try container.encode(self.overallRevenue, forKey: .overallRevenue)
try container.encode(self.currentBalance, forKey: .currentBalanceStars)
try container.encode(self.availableBalance, forKey: .availableBalanceStars)
try container.encode(self.overallRevenue, forKey: .overallRevenueStars)
try container.encode(self.withdrawEnabled, forKey: .withdrawEnabled)
try container.encodeIfPresent(self.nextWithdrawalTimestamp, forKey: .nextWithdrawalTimestamp)
}
@ -126,7 +146,7 @@ extension StarsRevenueStats.Balances {
init(apiStarsRevenueStatus: Api.StarsRevenueStatus) {
switch apiStarsRevenueStatus {
case let .starsRevenueStatus(flags, currentBalance, availableBalance, overallRevenue, nextWithdrawalAt):
self.init(currentBalance: currentBalance, availableBalance: availableBalance, overallRevenue: overallRevenue, withdrawEnabled: ((flags & (1 << 0)) != 0), nextWithdrawalTimestamp: nextWithdrawalAt)
self.init(currentBalance: StarsAmount(value: currentBalance, nanos: 0), availableBalance: StarsAmount(value: availableBalance, nanos: 0), overallRevenue: StarsAmount(value: overallRevenue, nanos: 0), withdrawEnabled: ((flags & (1 << 0)) != 0), nextWithdrawalTimestamp: nextWithdrawalAt)
}
}
}

View File

@ -749,7 +749,11 @@ func _internal_requestConnectedStarRefBots(account: Account, id: EnginePeer.Id,
}
}
public final class TelegramSuggestedStarRefBotList : Equatable {
public final class TelegramSuggestedStarRefBotList: Equatable {
public enum SortMode {
case date
case commission
}
public final class Item: Equatable {
public let peer: EnginePeer

View File

@ -269,9 +269,70 @@ func _internal_starsGiveawayOptions(account: Account) -> Signal<[StarsGiveawayOp
}
}
public struct StarsAmount: Equatable, Comparable, Hashable, Codable, CustomStringConvertible {
public static let zero: StarsAmount = StarsAmount(value: 0, nanos: 0)
public var value: Int64
public var nanos: Int32
public init(value: Int64, nanos: Int32) {
self.value = value
self.nanos = nanos
}
public var stringValue: String {
if self.nanos == 0 {
return "\(self.value)"
} else {
let totalValue = (Double(self.value) * 1e9 + Double(self.nanos)) / 1e9
return "\(totalValue)"
}
}
public var description: String {
return self.stringValue
}
public static func <(lhs: StarsAmount, rhs: StarsAmount) -> Bool {
if lhs.value == rhs.value {
return lhs.nanos < rhs.nanos
} else {
return lhs.value < rhs.value
}
}
public static func +(lhs: StarsAmount, rhs: StarsAmount) -> StarsAmount {
let totalNanos = Int64(lhs.nanos) + Int64(rhs.nanos)
let overflow = totalNanos / 1_000_000_000
let remainingNanos = totalNanos % 1_000_000_000
return StarsAmount(value: lhs.value + rhs.value + overflow, nanos: Int32(remainingNanos))
}
public static func -(lhs: StarsAmount, rhs: StarsAmount) -> StarsAmount {
var totalNanos = Int64(lhs.nanos) - Int64(rhs.nanos)
var totalValue = lhs.value - rhs.value
if totalNanos < 0 {
totalValue -= 1
totalNanos += 1_000_000_000
}
return StarsAmount(value: totalValue, nanos: Int32(totalNanos))
}
}
extension StarsAmount {
init(apiAmount: Api.StarsAmount) {
switch apiAmount {
case let .starsAmount(amount, nanos):
self.init(value: amount, nanos: nanos)
}
}
}
struct InternalStarsStatus {
let balance: Int64
let subscriptionsMissingBalance: Int64?
let balance: StarsAmount
let subscriptionsMissingBalance: StarsAmount?
let subscriptions: [StarsContext.State.Subscription]
let nextSubscriptionsOffset: String?
let transactions: [StarsContext.State.Transaction]
@ -317,8 +378,7 @@ private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id
|> mapToSignal { result -> Signal<InternalStarsStatus, RequestStarsStateError> in
return account.postbox.transaction { transaction -> InternalStarsStatus in
switch result {
case let .starsStatus(flags: _, balance, balanceNanos, _, _, subscriptionsMissingBalance, transactions, nextTransactionsOffset, chats, users):
let _ = balanceNanos
case let .starsStatus(flags: _, balance, _, _, subscriptionsMissingBalance, transactions, nextTransactionsOffset, chats, users):
let peers = AccumulatedPeers(chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: peers)
@ -331,8 +391,8 @@ private func _internal_requestStarsState(account: Account, peerId: EnginePeer.Id
}
}
return InternalStarsStatus(
balance: balance,
subscriptionsMissingBalance: subscriptionsMissingBalance,
balance: StarsAmount(apiAmount: balance),
subscriptionsMissingBalance: subscriptionsMissingBalance.flatMap { StarsAmount(value: $0, nanos: 0) },
subscriptions: [],
nextSubscriptionsOffset: nil,
transactions: parsedTransactions,
@ -368,8 +428,7 @@ private func _internal_requestStarsSubscriptions(account: Account, peerId: Engin
|> mapToSignal { result -> Signal<InternalStarsStatus, RequestStarsSubscriptionsError> in
return account.postbox.transaction { transaction -> InternalStarsStatus in
switch result {
case let .starsStatus(_, balance, balanceNanos, subscriptions, subscriptionsNextOffset, subscriptionsMissingBalance, _, _, chats, users):
let _ = balanceNanos
case let .starsStatus(_, balance, subscriptions, subscriptionsNextOffset, subscriptionsMissingBalance, _, _, chats, users):
let peers = AccumulatedPeers(chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: peers)
@ -384,8 +443,8 @@ private func _internal_requestStarsSubscriptions(account: Account, peerId: Engin
}
}
return InternalStarsStatus(
balance: balance,
subscriptionsMissingBalance: subscriptionsMissingBalance,
balance: StarsAmount(apiAmount: balance),
subscriptionsMissingBalance: subscriptionsMissingBalance.flatMap { StarsAmount(value: $0, nanos: 0) },
subscriptions: parsedSubscriptions,
nextSubscriptionsOffset: subscriptionsNextOffset,
transactions: [],
@ -464,7 +523,7 @@ private final class StarsContextImpl {
}))
}
func add(balance: Int64, addTransaction: Bool) {
func add(balance: StarsAmount, addTransaction: Bool) {
guard let state = self._state else {
return
}
@ -473,10 +532,10 @@ private final class StarsContextImpl {
transactions.insert(.init(flags: [.isLocal], id: "\(arc4random())", count: balance, date: Int32(Date().timeIntervalSince1970), peer: .appStore, title: nil, description: nil, photo: nil, transactionDate: nil, transactionUrl: nil, paidMessageId: nil, giveawayMessageId: nil, media: [], subscriptionPeriod: nil, starGift: nil, floodskipNumber: nil), at: 0)
}
self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: max(0, state.balance + balance), subscriptions: state.subscriptions, canLoadMoreSubscriptions: state.canLoadMoreSubscriptions, transactions: transactions, canLoadMoreTransactions: state.canLoadMoreTransactions, isLoading: state.isLoading))
self.updateState(StarsContext.State(flags: [.isPendingBalance], balance: max(StarsAmount(value: 0, nanos: 0), state.balance + balance), subscriptions: state.subscriptions, canLoadMoreSubscriptions: state.canLoadMoreSubscriptions, transactions: transactions, canLoadMoreTransactions: state.canLoadMoreTransactions, isLoading: state.isLoading))
}
fileprivate func updateBalance(_ balance: Int64, transactions: [StarsContext.State.Transaction]?) {
fileprivate func updateBalance(_ balance: StarsAmount, transactions: [StarsContext.State.Transaction]?) {
guard let state = self._state else {
return
}
@ -492,8 +551,11 @@ private final class StarsContextImpl {
private extension StarsContext.State.Transaction {
init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) {
switch apiTransaction {
case let .starsTransaction(apiFlags, id, stars, starNanos, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift, floodskipNumber):
let _ = starNanos
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift, floodskipNumber, starrefCommissionPermille, starrefPeer, starrefAmount):
let _ = starrefCommissionPermille
let _ = starrefPeer
let _ = starrefAmount
let parsedPeer: StarsContext.State.Transaction.Peer
var paidMessageId: MessageId?
var giveawayMessageId: MessageId?
@ -549,7 +611,7 @@ private extension StarsContext.State.Transaction {
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
let _ = subscriptionPeriod
self.init(flags: flags, id: id, count: stars, date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, giveawayMessageId: giveawayMessageId, media: media, subscriptionPeriod: subscriptionPeriod, starGift: starGift.flatMap { StarGift(apiStarGift: $0) }, floodskipNumber: floodskipNumber)
self.init(flags: flags, id: id, count: StarsAmount(apiAmount: stars), date: date, peer: parsedPeer, title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), transactionDate: transactionDate, transactionUrl: transactionUrl, paidMessageId: paidMessageId, giveawayMessageId: giveawayMessageId, media: media, subscriptionPeriod: subscriptionPeriod, starGift: starGift.flatMap { StarGift(apiStarGift: $0) }, floodskipNumber: floodskipNumber)
}
}
}
@ -610,7 +672,7 @@ public final class StarsContext {
public let flags: Flags
public let id: String
public let count: Int64
public let count: StarsAmount
public let date: Int32
public let peer: Peer
public let title: String?
@ -628,7 +690,7 @@ public final class StarsContext {
public init(
flags: Flags,
id: String,
count: Int64,
count: StarsAmount,
date: Int32,
peer: Peer,
title: String?,
@ -803,14 +865,14 @@ public final class StarsContext {
}
public var flags: Flags
public var balance: Int64
public var balance: StarsAmount
public var subscriptions: [Subscription]
public var canLoadMoreSubscriptions: Bool
public var transactions: [Transaction]
public var canLoadMoreTransactions: Bool
public var isLoading: Bool
init(flags: Flags, balance: Int64, subscriptions: [Subscription], canLoadMoreSubscriptions: Bool, transactions: [Transaction], canLoadMoreTransactions: Bool, isLoading: Bool) {
init(flags: Flags, balance: StarsAmount, subscriptions: [Subscription], canLoadMoreSubscriptions: Bool, transactions: [Transaction], canLoadMoreTransactions: Bool, isLoading: Bool) {
self.flags = flags
self.balance = balance
self.subscriptions = subscriptions
@ -873,13 +935,13 @@ public final class StarsContext {
return state
}
public func add(balance: Int64, addTransaction: Bool = true) {
public func add(balance: StarsAmount, addTransaction: Bool = true) {
self.impl.with {
$0.add(balance: balance, addTransaction: addTransaction)
}
}
fileprivate func updateBalance(_ balance: Int64, transactions: [StarsContext.State.Transaction]?) {
fileprivate func updateBalance(_ balance: StarsAmount, transactions: [StarsContext.State.Transaction]?) {
self.impl.with {
$0.updateBalance(balance, transactions: transactions)
}
@ -942,9 +1004,9 @@ private final class StarsTransactionsContextImpl {
case .all:
initialTransactions = currentTransactions
case .incoming:
initialTransactions = currentTransactions.filter { $0.count > 0 }
initialTransactions = currentTransactions.filter { $0.count > StarsAmount.zero }
case .outgoing:
initialTransactions = currentTransactions.filter { $0.count < 0 }
initialTransactions = currentTransactions.filter { $0.count < StarsAmount.zero }
}
self._state = StarsTransactionsContext.State(transactions: initialTransactions, canLoadMore: true, isLoading: false)
@ -962,9 +1024,9 @@ private final class StarsTransactionsContextImpl {
case .all:
filteredTransactions = currentTransactions
case .incoming:
filteredTransactions = currentTransactions.filter { $0.count > 0 }
filteredTransactions = currentTransactions.filter { $0.count > StarsAmount.zero }
case .outgoing:
filteredTransactions = currentTransactions.filter { $0.count < 0 }
filteredTransactions = currentTransactions.filter { $0.count < StarsAmount.zero }
}
if !filteredTransactions.isEmpty && self._state.transactions.isEmpty && filteredTransactions != initialTransactions {
@ -989,9 +1051,9 @@ private final class StarsTransactionsContextImpl {
case .all:
filteredTransactions = currentTransactions
case .incoming:
filteredTransactions = currentTransactions.filter { $0.count > 0 }
filteredTransactions = currentTransactions.filter { $0.count > StarsAmount.zero }
case .outgoing:
filteredTransactions = currentTransactions.filter { $0.count < 0 }
filteredTransactions = currentTransactions.filter { $0.count < StarsAmount.zero }
}
if filteredTransactions != initialTransactions {
@ -1161,7 +1223,7 @@ private final class StarsSubscriptionsContextImpl {
let currentSubscriptions = starsContext?.currentState?.subscriptions ?? []
let canLoadMore = starsContext?.currentState?.canLoadMoreSubscriptions ?? true
self._state = StarsSubscriptionsContext.State(balance: 0, subscriptions: currentSubscriptions, canLoadMore: canLoadMore, isLoading: false)
self._state = StarsSubscriptionsContext.State(balance: StarsAmount.zero, subscriptions: currentSubscriptions, canLoadMore: canLoadMore, isLoading: false)
self._statePromise.set(.single(self._state))
self.loadMore()
@ -1194,7 +1256,7 @@ private final class StarsSubscriptionsContextImpl {
self.nextOffset = status.nextSubscriptionsOffset
var updatedState = self._state
updatedState.balance = status.subscriptionsMissingBalance ?? 0
updatedState.balance = status.subscriptionsMissingBalance ?? StarsAmount.zero
updatedState.subscriptions = nextOffset.isEmpty ? status.subscriptions : updatedState.subscriptions + status.subscriptions
updatedState.isLoading = false
updatedState.canLoadMore = self.nextOffset != nil
@ -1257,12 +1319,12 @@ private final class StarsSubscriptionsContextImpl {
public final class StarsSubscriptionsContext {
public struct State: Equatable {
public var balance: Int64
public var balance: StarsAmount
public var subscriptions: [StarsContext.State.Subscription]
public var canLoadMore: Bool
public var isLoading: Bool
init(balance: Int64, subscriptions: [StarsContext.State.Subscription], canLoadMore: Bool, isLoading: Bool) {
init(balance: StarsAmount, subscriptions: [StarsContext.State.Subscription], canLoadMore: Bool, isLoading: Bool) {
self.balance = balance
self.subscriptions = subscriptions
self.canLoadMore = canLoadMore
@ -1449,7 +1511,7 @@ func _internal_getStarsTransaction(accountPeerId: PeerId, postbox: Postbox, netw
}
|> mapToSignal { result -> Signal<StarsContext.State.Transaction?, NoError> in
return postbox.transaction { transaction -> StarsContext.State.Transaction? in
guard let result, case let .starsStatus(_, _, _, _, _, _, transactions, _, chats, users) = result, let matchingTransaction = transactions?.first else {
guard let result, case let .starsStatus(_, _, _, _, _, transactions, _, chats, users) = result, let matchingTransaction = transactions?.first else {
return nil
}
let peers = AccumulatedPeers(chats: chats, users: users)
@ -1465,12 +1527,13 @@ public struct StarsSubscriptionPricing: Codable, Equatable {
private enum CodingKeys: String, CodingKey {
case period
case amount
case starsAmount
}
public let period: Int32
public let amount: Int64
public let amount: StarsAmount
public init(period: Int32, amount: Int64) {
public init(period: Int32, amount: StarsAmount) {
self.period = period
self.amount = amount
}
@ -1479,14 +1542,19 @@ public struct StarsSubscriptionPricing: Codable, Equatable {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.period = try container.decode(Int32.self, forKey: .period)
self.amount = try container.decode(Int64.self, forKey: .amount)
if let legacyAmount = try container.decodeIfPresent(Int64.self, forKey: .amount) {
self.amount = StarsAmount(value: legacyAmount, nanos: 0)
} else {
self.amount = try container.decode(StarsAmount.self, forKey: .starsAmount)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.period, forKey: .period)
try container.encode(self.amount, forKey: .amount)
try container.encode(self.amount, forKey: .starsAmount)
}
public static let monthPeriod: Int32 = 2592000
@ -1497,12 +1565,12 @@ extension StarsSubscriptionPricing {
init(apiStarsSubscriptionPricing: Api.StarsSubscriptionPricing) {
switch apiStarsSubscriptionPricing {
case let .starsSubscriptionPricing(period, amount):
self = .init(period: period, amount: amount)
self = .init(period: period, amount: StarsAmount(value: amount, nanos: 0))
}
}
var apiStarsSubscriptionPricing: Api.StarsSubscriptionPricing {
return .starsSubscriptionPricing(period: self.period, amount: self.amount)
return .starsSubscriptionPricing(period: self.period, amount: self.amount.value)
}
}

View File

@ -1,5 +1,6 @@
import Foundation
import PresentationStrings
import TelegramCore
public func compactNumericCountString(_ count: Int, decimalSeparator: String = ".") -> String {
if count >= 1000 * 1000 {
@ -38,6 +39,28 @@ public func presentationStringsFormattedNumber(_ count: Int32, _ groupingSeparat
}
}
public func presentationStringsFormattedNumber(_ starsAmount: StarsAmount, _ groupingSeparator: String = "") -> String {
if starsAmount.nanos == 0 {
let count = Int32(starsAmount.value)
let string = "\(count)"
if groupingSeparator.isEmpty || abs(count) < 1000 {
return string
} else {
var groupedString: String = ""
for i in 0 ..< Int(ceil(Double(string.count) / 3.0)) {
let index = string.count - Int(i + 1) * 3
if !groupedString.isEmpty {
groupedString = groupingSeparator + groupedString
}
groupedString = String(string[string.index(string.startIndex, offsetBy: max(0, index)) ..< string.index(string.startIndex, offsetBy: index + 3)]) + groupedString
}
return groupedString
}
} else {
return starsAmount.stringValue
}
}
public func dayIntervalString(strings: PresentationStrings, days: Int32) -> String {
return strings.MessageTimer_Days(max(0, days))
}

View File

@ -0,0 +1,22 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AlertComponent",
module_name = "AlertComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,395 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import ComponentDisplayAdapters
import AsyncDisplayKit
private let alertWidth: CGFloat = 270.0
public enum ComponentAlertActionType {
case genericAction
case defaultAction
case destructiveAction
case defaultDestructiveAction
}
public struct ComponentAlertAction {
public let type: ComponentAlertActionType
public let title: String
public let action: () -> Void
public init(type: ComponentAlertActionType, title: String, action: @escaping () -> Void) {
self.type = type
self.title = title
self.action = action
}
}
public final class ComponentAlertContentActionNode: HighlightableButtonNode {
private var theme: AlertControllerTheme
public var action: ComponentAlertAction {
didSet {
self.updateTitle()
}
}
private let backgroundNode: ASDisplayNode
public var highlightedUpdated: (Bool) -> Void = { _ in }
public init(theme: AlertControllerTheme, action: ComponentAlertAction) {
self.theme = theme
self.action = action
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.alpha = 0.0
super.init()
self.titleNode.maximumNumberOfLines = 2
self.highligthedChanged = { [weak self] value in
if let strongSelf = self {
strongSelf.setHighlighted(value, animated: true)
}
}
self.updateTheme(theme)
}
public override func didLoad() {
super.didLoad()
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
self.pointerInteraction = PointerInteraction(node: self, style: .hover, willEnter: { [weak self] in
if let strongSelf = self {
strongSelf.setHighlighted(true, animated: false)
}
}, willExit: { [weak self] in
if let strongSelf = self {
strongSelf.setHighlighted(false, animated: false)
}
})
}
public func performAction() {
if self.actionEnabled {
self.action.action()
}
}
public func setHighlighted(_ highlighted: Bool, animated: Bool) {
self.highlightedUpdated(highlighted)
if highlighted {
if self.backgroundNode.supernode == nil {
self.insertSubnode(self.backgroundNode, at: 0)
}
self.backgroundNode.alpha = 1.0
} else {
if animated {
UIView.animate(withDuration: 0.3, animations: {
self.backgroundNode.alpha = 0.0
})
} else {
self.backgroundNode.alpha = 0.0
}
}
}
public var actionEnabled: Bool = true {
didSet {
self.isUserInteractionEnabled = self.actionEnabled
self.updateTitle()
}
}
public func updateTheme(_ theme: AlertControllerTheme) {
self.theme = theme
self.backgroundNode.backgroundColor = theme.highlightedItemColor
self.updateTitle()
}
private func updateTitle() {
var font = Font.regular(theme.baseFontSize)
var color: UIColor
switch self.action.type {
case .defaultAction, .genericAction:
color = self.actionEnabled ? self.theme.accentColor : self.theme.disabledColor
case .destructiveAction, .defaultDestructiveAction:
color = self.actionEnabled ? self.theme.destructiveColor : self.theme.disabledColor
}
switch self.action.type {
case .defaultAction, .defaultDestructiveAction:
font = Font.semibold(theme.baseFontSize)
case .destructiveAction, .genericAction:
break
}
self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: [])
self.accessibilityLabel = self.action.title
self.accessibilityTraits = [.button]
}
@objc func pressed() {
self.action.action()
}
override public func layout() {
super.layout()
self.backgroundNode.frame = self.bounds
}
}
public enum ComponentAlertContentActionLayout {
case horizontal
case vertical
}
public final class ComponentAlertContentNode: AlertContentNode {
private var theme: AlertControllerTheme
private let actionLayout: ComponentAlertContentActionLayout
private let content: AnyComponent<Empty>
private let contentView = ComponentView<Empty>()
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [ComponentAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var validLayout: CGSize?
private let _dismissOnOutsideTap: Bool
override public var dismissOnOutsideTap: Bool {
return self._dismissOnOutsideTap
}
private var highlightedItemIndex: Int? = nil
public init(theme: AlertControllerTheme, content: AnyComponent<Empty>, actions: [ComponentAlertAction], actionLayout: ComponentAlertContentActionLayout, dismissOnOutsideTap: Bool) {
self.theme = theme
self.actionLayout = actionLayout
self._dismissOnOutsideTap = dismissOnOutsideTap
self.content = content
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isUserInteractionEnabled = false
self.actionNodesSeparator.backgroundColor = theme.separatorColor
self.actionNodes = actions.map { action -> ComponentAlertContentActionNode in
return ComponentAlertContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
separatorNode.backgroundColor = theme.separatorColor
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.actionNodesSeparator)
var i = 0
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
let index = i
actionNode.highlightedUpdated = { [weak self] highlighted in
if highlighted {
self?.highlightedItemIndex = index
}
}
i += 1
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
}
func setHighlightedItemIndex(_ index: Int?, update: Bool = false) {
self.highlightedItemIndex = index
if update {
var i = 0
for actionNode in self.actionNodes {
if i == index {
actionNode.setHighlighted(true, animated: false)
} else {
actionNode.setHighlighted(false, animated: false)
}
i += 1
}
}
}
override public func decreaseHighlightedIndex() {
let currentHighlightedIndex = self.highlightedItemIndex ?? 0
self.setHighlightedItemIndex(max(0, currentHighlightedIndex - 1), update: true)
}
override public func increaseHighlightedIndex() {
let currentHighlightedIndex = self.highlightedItemIndex ?? -1
self.setHighlightedItemIndex(min(self.actionNodes.count - 1, currentHighlightedIndex + 1), update: true)
}
override public func performHighlightedAction() {
guard let highlightedItemIndex = self.highlightedItemIndex else {
return
}
var i = 0
for itemNode in self.actionNodes {
if i == highlightedItemIndex {
itemNode.performAction()
return
}
i += 1
}
}
override public func updateTheme(_ theme: AlertControllerTheme) {
self.theme = theme
self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes {
actionNode.updateTheme(theme)
}
for separatorNode in self.actionVerticalSeparators {
separatorNode.backgroundColor = theme.separatorColor
}
if let size = self.validLayout {
_ = self.updateLayout(size: size, transition: .immediate)
}
}
override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
self.validLayout = size
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
var size = size
size.width = min(size.width, alertWidth)
let contentSize = self.contentView.update(
transition: ComponentTransition(transition),
component: self.content,
environment: {},
containerSize: CGSize(width: size.width - insets.left - insets.right, height: 10000.0)
)
let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
let actionTitleInsets: CGFloat = 8.0
var effectiveActionLayout = self.actionLayout
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
effectiveActionLayout = .vertical
}
switch effectiveActionLayout {
case .horizontal:
minActionsWidth += actionTitleSize.width + actionTitleInsets
case .vertical:
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
}
let resultSize: CGSize
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let contentWidth = alertWidth - insets.left - insets.right
let contentFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - contentSize.width) / 2.0), y: insets.top), size: contentSize)
if let contentComponentView = self.contentView.view {
if contentComponentView.superview == nil {
self.view.insertSubview(contentComponentView, belowSubview: self.actionNodesSeparator.view)
transition.updateFrame(view: contentComponentView, frame: contentFrame)
}
}
resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: contentSize.height + actionsHeight + insets.top + insets.bottom)
self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))
var actionOffset: CGFloat = 0.0
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
switch effectiveActionLayout {
case .horizontal:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
case .vertical:
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
}
}
separatorIndex += 1
let currentActionWidth: CGFloat
switch effectiveActionLayout {
case .horizontal:
if nodeIndex == self.actionNodes.count - 1 {
currentActionWidth = resultSize.width - actionOffset
} else {
currentActionWidth = actionWidth
}
case .vertical:
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
switch effectiveActionLayout {
case .horizontal:
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += currentActionWidth
case .vertical:
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
actionOffset += actionButtonHeight
}
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
nodeIndex += 1
}
return resultSize
}
}
public func componentAlertController(theme: AlertControllerTheme, content: AnyComponent<Empty>, actions: [ComponentAlertAction], actionLayout: ComponentAlertContentActionLayout = .horizontal, dismissOnOutsideTap: Bool = true) -> AlertController {
var dismissImpl: (() -> Void)?
let controller = AlertController(theme: theme, contentNode: ComponentAlertContentNode(theme: theme, content: content, actions: actions.map { action in
return ComponentAlertAction(type: action.type, title: action.title, action: {
dismissImpl?()
action.action()
})
}, actionLayout: actionLayout, dismissOnOutsideTap: dismissOnOutsideTap))
dismissImpl = { [weak controller] in
controller?.dismissAnimated()
}
return controller
}

View File

@ -114,6 +114,7 @@ public final class ButtonTextContentComponent: Component {
public let text: String
public let badge: Int
public let textColor: UIColor
public let fontSize: CGFloat
public let badgeBackground: UIColor
public let badgeForeground: UIColor
public let badgeStyle: BadgeStyle
@ -124,6 +125,7 @@ public final class ButtonTextContentComponent: Component {
text: String,
badge: Int,
textColor: UIColor,
fontSize: CGFloat = 17.0,
badgeBackground: UIColor,
badgeForeground: UIColor,
badgeStyle: BadgeStyle = .round,
@ -133,6 +135,7 @@ public final class ButtonTextContentComponent: Component {
self.text = text
self.badge = badge
self.textColor = textColor
self.fontSize = fontSize
self.badgeBackground = badgeBackground
self.badgeForeground = badgeForeground
self.badgeStyle = badgeStyle
@ -150,6 +153,9 @@ public final class ButtonTextContentComponent: Component {
if lhs.textColor != rhs.textColor {
return false
}
if lhs.fontSize != rhs.fontSize {
return false
}
if lhs.badgeBackground != rhs.badgeBackground {
return false
}
@ -202,7 +208,7 @@ public final class ButtonTextContentComponent: Component {
transition: .immediate,
component: AnyComponent(Text(
text: component.text,
font: Font.semibold(17.0),
font: Font.semibold(component.fontSize),
color: component.textColor
)),
environment: {},

View File

@ -1283,14 +1283,14 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
photo.append(photoRepresentation)
}
let channel = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil)
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: [], version: 0)
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount.value, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: [], version: 0)
inputData.set(.single(BotCheckoutController.InputData(
form: BotPaymentForm(
id: subscriptionFormId,
canSaveCredentials: false,
passwordMissing: false,
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil, subscriptionPeriod: subscriptionPricing.period),
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount.value)], tip: nil, termsInfo: nil, subscriptionPeriod: subscriptionPricing.period),
paymentBotId: channel.id,
providerId: nil,
url: nil,

View File

@ -25,13 +25,13 @@ private final class BalanceComponent: CombinedComponent {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let balance: Int64?
let balance: StarsAmount?
init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
balance: Int64?
balance: StarsAmount?
) {
self.context = context
self.theme = theme
@ -76,7 +76,7 @@ private final class BalanceComponent: CombinedComponent {
let balanceText: String
if let value = context.component.balance {
balanceText = "\(value)"
balanceText = "\(value.stringValue)"
} else {
balanceText = "..."
}
@ -822,7 +822,7 @@ private final class ChatSendStarsScreenComponent: Component {
let myPeer: EnginePeer
let messageId: EngineMessage.Id
let maxAmount: Int
let balance: Int64?
let balance: StarsAmount?
let currentSentAmount: Int?
let topPeers: [ChatSendStarsScreen.TopPeer]
let myTopPeer: ChatSendStarsScreen.TopPeer?
@ -834,7 +834,7 @@ private final class ChatSendStarsScreenComponent: Component {
myPeer: EnginePeer,
messageId: EngineMessage.Id,
maxAmount: Int,
balance: Int64?,
balance: StarsAmount?,
currentSentAmount: Int?,
topPeers: [ChatSendStarsScreen.TopPeer],
myTopPeer: ChatSendStarsScreen.TopPeer?,
@ -1021,7 +1021,7 @@ private final class ChatSendStarsScreenComponent: Component {
private var topOffsetDistance: CGFloat?
private var balance: Int64?
private var balance: StarsAmount?
private var amount: Amount = Amount(realValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true)
private var didChangeAmount: Bool = false
@ -1953,7 +1953,7 @@ private final class ChatSendStarsScreenComponent: Component {
return
}
if balance < self.amount.realValue {
if balance < StarsAmount(value: Int64(self.amount.realValue), nanos: 0) {
let _ = (component.context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak self] options in
@ -2102,7 +2102,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
fileprivate let peer: EnginePeer
fileprivate let myPeer: EnginePeer
fileprivate let messageId: EngineMessage.Id
fileprivate let balance: Int64?
fileprivate let balance: StarsAmount?
fileprivate let currentSentAmount: Int?
fileprivate let topPeers: [ChatSendStarsScreen.TopPeer]
fileprivate let myTopPeer: ChatSendStarsScreen.TopPeer?
@ -2111,7 +2111,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
peer: EnginePeer,
myPeer: EnginePeer,
messageId: EngineMessage.Id,
balance: Int64?,
balance: StarsAmount?,
currentSentAmount: Int?,
topPeers: [ChatSendStarsScreen.TopPeer],
myTopPeer: ChatSendStarsScreen.TopPeer?
@ -2240,7 +2240,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer {
}
public static func initialData(context: AccountContext, peerId: EnginePeer.Id, messageId: EngineMessage.Id, topPeers: [ReactionsMessageAttribute.TopPeer]) -> Signal<InitialData?, NoError> {
let balance: Signal<Int64?, NoError>
let balance: Signal<StarsAmount?, NoError>
if let starsContext = context.starsContext {
balance = starsContext.state
|> map { state in

View File

@ -580,7 +580,7 @@ final class GiftOptionsScreenComponent: Component {
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: presentationStringsFormattedNumber(Int32(self.starsState?.balance ?? 0), environment.dateTimeFormat.groupingSeparator),
string: presentationStringsFormattedNumber(self.starsState?.balance ?? StarsAmount.zero, environment.dateTimeFormat.groupingSeparator),
font: Font.semibold(14.0),
textColor: environment.theme.actionSheet.primaryTextColor
)),

View File

@ -391,7 +391,7 @@ final class GiftSetupScreenComponent: Component {
})
}
if starsState.balance < starGift.price {
if starsState.balance < StarsAmount(value: starGift.price, nanos: 0) {
let _ = (self.optionsPromise.get()
|> filter { $0 != nil }
|> take(1)
@ -405,7 +405,7 @@ final class GiftSetupScreenComponent: Component {
options: options ?? [],
purpose: .starGift(peerId: component.peerId, requiredStars: starGift.price),
completion: { [weak starsContext] stars in
starsContext?.add(balance: stars)
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
Queue.mainQueue().after(0.1) {
proceed()
}

View File

@ -363,6 +363,10 @@ public final class ListSectionComponent: Component {
public final class View: UIView {
private let contentView: ListSectionContentView
public var contentViewImpl: UIView {
return self.contentView
}
private var header: ComponentView<Empty>?
private var footer: ComponentView<Empty>?

View File

@ -34,6 +34,10 @@ swift_library(
"//submodules/PresentationDataUtils",
"//submodules/TelegramUI/Components/Stories/PeerListItemComponent",
"//submodules/ContextUI",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/ToastComponent",
"//submodules/AvatarNode",
],
visibility = [
"//visibility:public",

View File

@ -25,6 +25,7 @@ import PeerListItemComponent
import TelegramStringFormatting
import ContextUI
import BalancedTextComponent
import AlertComponent
private func textForTimeout(value: Int32) -> String {
if value < 3600 {
@ -114,6 +115,8 @@ final class AffiliateProgramSetupScreenComponent: Component {
private var suggestedStarBotList: TelegramSuggestedStarRefBotList?
private var suggestedStarBotListDisposable: Disposable?
private var suggestedSortMode: TelegramSuggestedStarRefBotList.SortMode = .date
private var isSuggestedSortModeUpdating: Bool = false
override init(frame: CGRect) {
self.scrollView = UIScrollView()
@ -160,24 +163,43 @@ final class AffiliateProgramSetupScreenComponent: Component {
}
private func requestApplyProgram() {
guard let component = self.component else {
guard let component = self.component, let environment = self.environment else {
return
}
let programPermille: Int32 = Int32(self.commissionPermille)
let programDuration: Int32? = self.durationValue == Int(Int32.max) ? nil : Int32(self.durationValue)
let commissionTitle: String = "\(programPermille / 10)%"
let durationTitle: String
if let durationMonths = programDuration {
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (24 * 60 * 60))
} else {
durationTitle = "Lifetime"
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
self.environment?.controller()?.present(standardTextAlertController(
theme: AlertControllerTheme(presentationData: presentationData),
self.environment?.controller()?.present(tableAlert(
theme: presentationData.theme,
title: "Warning",
text: "This change is irreversible. You won't be able to reduce commission or duration. You can only increase these parameters or end the program, which will disable all previously shared referral links.",
table: TableComponent(theme: environment.theme, items: [
TableComponent.Item(id: 0, title: "Commission", component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: commissionTitle, font: Font.regular(17.0), textColor: environment.theme.actionSheet.primaryTextColor))
))),
TableComponent.Item(id: 1, title: "Duration", component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: durationTitle, font: Font.regular(17.0), textColor: environment.theme.actionSheet.primaryTextColor))
)))
]),
actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: "Start", action: { [weak self] in
ComponentAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
ComponentAlertAction(type: .defaultAction, title: "Start", action: { [weak self] in
guard let self else {
return
}
self.applyProgram()
})
],
actionLayout: .horizontal
]
), in: .window(.root))
}
@ -263,11 +285,19 @@ If you end your affiliate program:
program: nil
)
|> deliverOnMainQueue).startStrict(completed: { [weak self] in
guard let self else {
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
}
self.isApplying = false
self.environment?.controller()?.dismiss()
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
if let navigationController = controller.navigationController, let index = navigationController.viewControllers.firstIndex(where: { $0 === controller }), index != 0 {
if let previousController = navigationController.viewControllers[index - 1] as? ViewController {
previousController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_link_broken", scale: 0.065, colors: [:], title: "Affiliate Program Ended", text: "Participating affiliates have been notified. All referral links will be disabled in 24 hours.", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
}
controller.dismiss()
})
self.state?.updated(transition: .immediate)
@ -311,6 +341,121 @@ If you end your affiliate program:
}
}
private func openConnectedBot(bot: TelegramConnectedStarRefBotList.Item) {
guard let component = self.component else {
return
}
let _ = (component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: component.initialContent.peerId)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] targetPeer in
guard let self, let component = self.component else {
return
}
guard let targetPeer else {
return
}
self.environment?.controller()?.push(JoinAffiliateProgramScreen(
context: component.context,
sourcePeer: bot.peer,
commissionPermille: bot.commissionPermille,
programDuration: bot.durationMonths,
mode: .active(JoinAffiliateProgramScreen.Active(
targetPeer: targetPeer,
link: bot.url,
userCount: Int(bot.participants),
copyLink: { [weak self] in
guard let self, let component = self.component else {
return
}
UIPasteboard.general.string = bot.url
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: "Link copied to clipboard", text: "Share this link and earn **\(bot.commissionPermille / 10)%** of what people who use it spend in **\(bot.peer.compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
}
))
))
})
}
private func leaveProgram(bot: TelegramConnectedStarRefBotList.Item) {
guard let component = self.component else {
return
}
let _ = (component.context.engine.peers.removeConnectedStarRefBot(id: component.initialContent.peerId, link: bot.url)
|> deliverOnMainQueue).startStandalone(completed: { [weak self] in
guard let self else {
return
}
if let connectedStarBotList = self.connectedStarBotList {
var updatedItems = connectedStarBotList.items
if let index = updatedItems.firstIndex(where: { $0.peer.id == bot.peer.id }) {
updatedItems.remove(at: index)
}
self.connectedStarBotList = TelegramConnectedStarRefBotList(
items: updatedItems,
totalCount: connectedStarBotList.totalCount + 1
)
self.state?.updated(transition: .immediate)
}
})
}
private func openSortModeMenu(sourceView: UIView) {
guard let component = self.component, let environment = self.environment, let controller = environment.controller() else {
return
}
var items: [ContextMenuItem] = []
let availableModes: [(TelegramSuggestedStarRefBotList.SortMode, String)] = [
(.date, "Date"),
(.commission, "Commission")
]
for (mode, title) in availableModes {
let isSelected = mode == self.suggestedSortMode
items.append(.action(ContextMenuActionItem(text: title, icon: { theme in
if isSelected {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
} else {
return nil
}
}, action: { [weak self] _, f in
f(.default)
guard let self else {
return
}
if self.suggestedSortMode != mode {
self.suggestedSortMode = mode
self.isSuggestedSortModeUpdating = true
self.state?.updated(transition: .immediate)
self.suggestedStarBotListDisposable?.dispose()
self.suggestedStarBotListDisposable = (component.context.engine.peers.requestSuggestedStarRefBots(
id: component.initialContent.peerId,
orderByCommission: self.suggestedSortMode == .commission,
offset: nil,
limit: 100)
|> deliverOnMainQueue).startStrict(next: { [weak self] list in
guard let self else {
return
}
self.suggestedStarBotList = list
self.isSuggestedSortModeUpdating = false
self.state?.updated(transition: .immediate)
})
}
})))
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
controller.presentInGlobalOverlay(contextController)
}
func update(component: AffiliateProgramSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
@ -324,7 +469,7 @@ If you end your affiliate program:
(12, "1y", "1 YEAR"),
(2 * 12, "2y", "2 YEARS"),
(3 * 12, "3y", "3 YEARS"),
(Int32.max, "", "INDEFINITELY")
(Int32.max, "", "LIFETIME")
]
let environment = environment[EnvironmentType.self].value
@ -342,36 +487,60 @@ If you end your affiliate program:
switch component.initialContent.mode {
case let .editProgram(editProgram):
if let currentRefProgram = editProgram.currentRefProgram {
self.commissionPermille = Int(currentRefProgram.commissionPermille)
let commissionPercentValue = CGFloat(self.commissionPermille) / 1000.0
self.commissionSliderValue = (commissionPercentValue - 0.01) / (0.9 - 0.01)
self.durationValue = Int(currentRefProgram.durationMonths ?? Int32.max)
self.commissionMinPermille = Int(currentRefProgram.commissionPermille)
self.durationMinValue = Int(currentRefProgram.durationMonths ?? Int32.max)
self.currentProgram = currentRefProgram
var ignoreCurrentProgram = false
if let endDate = currentRefProgram.endDate {
self.programEndTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
guard let self else {
return
}
let timestamp = Int32(Date().timeIntervalSince1970)
let remainingTime: Int32 = max(0, endDate - timestamp)
if remainingTime <= 0 {
self.currentProgram = nil
self.programEndTimer?.invalidate()
self.programEndTimer = nil
}
self.state?.updated(transition: .immediate)
})
let timestamp = Int32(Date().timeIntervalSince1970)
let remainingTime: Int32 = max(0, endDate - timestamp)
if remainingTime <= 0 {
ignoreCurrentProgram = true
}
}
if !ignoreCurrentProgram {
self.commissionPermille = Int(currentRefProgram.commissionPermille)
let commissionPercentValue = CGFloat(self.commissionPermille) / 1000.0
self.commissionSliderValue = (commissionPercentValue - 0.01) / (0.9 - 0.01)
self.durationValue = Int(currentRefProgram.durationMonths ?? Int32.max)
self.commissionMinPermille = Int(currentRefProgram.commissionPermille)
self.durationMinValue = Int(currentRefProgram.durationMonths ?? Int32.max)
self.currentProgram = currentRefProgram
if let endDate = currentRefProgram.endDate {
self.programEndTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { [weak self] _ in
guard let self else {
return
}
let timestamp = Int32(Date().timeIntervalSince1970)
let remainingTime: Int32 = max(0, endDate - timestamp)
if remainingTime <= 0 {
self.currentProgram = nil
self.programEndTimer?.invalidate()
self.programEndTimer = nil
self.commissionSliderValue = 0.0
self.commissionPermille = 10
self.commissionMinPermille = 10
self.durationValue = 0
self.durationMinValue = 0
}
self.state?.updated(transition: .immediate)
})
}
} else {
self.commissionPermille = 10
self.commissionSliderValue = 0.0
self.commissionMinPermille = 10
self.durationValue = 10
}
} else {
self.commissionPermille = 10
self.commissionSliderValue = 0.0
self.commissionMinPermille = 10
self.durationValue = 10
}
@ -919,6 +1088,8 @@ If you end your affiliate program:
let timestamp = Int32(Date().timeIntervalSince1970)
let remainingTime: Int32 = max(0, endDate - timestamp)
buttonText = textForTimeout(value: remainingTime)
} else if self.currentProgram != nil {
buttonText = "Update Affiliate Program"
} else {
buttonText = "Start Affiliate Program"
}
@ -996,7 +1167,7 @@ If you end your affiliate program:
} else {
durationTitle = "Lifetime"
}
let subtitle = "\(item.commissionPermille / 10)%, \(durationTitle)"
let commissionTitle = "\(item.commissionPermille / 10)%"
let itemContextAction: (EnginePeer, ContextExtractedContentContainingView, ContextGesture?) -> Void = { [weak self] peer, sourceView, gesture in
guard let self, let component = self.component, let environment = self.environment else {
@ -1065,27 +1236,10 @@ If you end your affiliate program:
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] c, _ in
c?.dismiss(completion: {
guard let self, let component = self.component else {
guard let self else {
return
}
let _ = (component.context.engine.peers.removeConnectedStarRefBot(id: component.initialContent.peerId, link: item.url)
|> deliverOnMainQueue).startStandalone(completed: { [weak self] in
guard let self else {
return
}
if let connectedStarBotList = self.connectedStarBotList {
var updatedItems = connectedStarBotList.items
if let index = updatedItems.firstIndex(where: { $0.peer.id == peer.id }) {
updatedItems.remove(at: index)
}
self.connectedStarBotList = TelegramConnectedStarRefBotList(
items: updatedItems,
totalCount: connectedStarBotList.totalCount + 1
)
self.state?.updated(transition: .immediate)
}
})
self.leaveProgram(bot: item)
})
})))
@ -1108,21 +1262,43 @@ If you end your affiliate program:
style: .generic,
sideInset: 0.0,
title: item.peer.compactDisplayTitle,
avatarComponent: AnyComponent(PeerBadgeAvatarComponent(
context: component.context,
peer: item.peer,
theme: environment.theme,
hasBadge: true
)),
peer: item.peer,
subtitle: PeerListItemComponent.Subtitle(text: subtitle, color: .neutral),
subtitle: nil,
subtitleComponent: AnyComponent(AffiliatePeerSubtitleComponent(
theme: environment.theme,
percentText: commissionTitle,
text: durationTitle
)),
subtitleAccessory: .none,
presence: nil,
rightAccessory: .none,
rightAccessory: .disclosure,
selectionState: .none,
hasNext: false,
extractedTheme: PeerListItemComponent.ExtractedTheme(
inset: 2.0,
background: environment.theme.list.itemBlocksBackgroundColor
),
action: { peer, _, itemView in
itemContextAction(peer, itemView.extractedContainerView, nil)
insets: UIEdgeInsets(top: -1.0, left: 0.0, bottom: -1.0, right: 0.0),
action: { [weak self] peer, _, itemView in
guard let self else {
return
}
self.openConnectedBot(bot: item)
},
inlineActions: nil,
inlineActions: PeerListItemComponent.InlineActionsState(actions: [
PeerListItemComponent.InlineAction(id: 0, title: "Leave", color: .destructive, action: { [weak self] in
guard let self else {
return
}
self.leaveProgram(bot: item)
})
]),
contextAction: { peer, sourceView, gesture in
itemContextAction(peer, sourceView, gesture)
}
@ -1135,7 +1311,7 @@ If you end your affiliate program:
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "PROGRAMS",
string: "MY PROGRAMS",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
@ -1168,60 +1344,13 @@ If you end your affiliate program:
do {
var suggestedSectionItems: [AnyComponentWithIdentity<Empty>] = []
for item in suggestedStarBotListItems {
let commissionTitle = "\(item.commissionPermille / 10)%"
let durationTitle: String
if let durationMonths = item.durationMonths {
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (24 * 60 * 60))
} else {
durationTitle = "Lifetime"
}
let subtitle = "\(item.commissionPermille / 10)%, \(durationTitle)"
let itemContextAction: (EnginePeer, ContextExtractedContentContainingView, ContextGesture?) -> Void = { [weak self] peer, sourceView, gesture in
guard let self, let component = self.component, let environment = self.environment else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
var itemList: [ContextMenuItem] = []
itemList.append(.action(ContextMenuActionItem(text: "Join", textColor: .primary, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c?.dismiss(completion: {
guard let self, let component = self.component else {
return
}
let _ = (component.context.engine.peers.connectStarRefBot(id: component.initialContent.peerId, botId: peer.id)
|> deliverOnMainQueue).startStandalone(next: { [weak self] result in
guard let self else {
return
}
if let connectedStarBotList = self.connectedStarBotList {
var updatedItems = connectedStarBotList.items
if !updatedItems.contains(where: { $0.peer.id == peer.id }) {
updatedItems.insert(result, at: 0)
}
self.connectedStarBotList = TelegramConnectedStarRefBotList(
items: updatedItems,
totalCount: connectedStarBotList.totalCount + 1
)
self.state?.updated(transition: .immediate)
}
})
})
})))
let items = ContextController.Items(content: .list(itemList))
let controller = ContextController(
presentationData: presentationData,
source: .extracted(ListContextExtractedContentSource(contentView: sourceView)),
items: .single(items),
recognizer: nil,
gesture: gesture
)
environment.controller()?.presentInGlobalOverlay(controller, with: nil)
}
suggestedSectionItems.append(AnyComponentWithIdentity(id: item.peer.id, component: AnyComponent(PeerListItemComponent(
context: component.context,
@ -1230,24 +1359,83 @@ If you end your affiliate program:
style: .generic,
sideInset: 0.0,
title: item.peer.compactDisplayTitle,
avatarComponent: AnyComponent(PeerBadgeAvatarComponent(
context: component.context,
peer: item.peer,
theme: environment.theme,
hasBadge: false
)),
peer: item.peer,
subtitle: PeerListItemComponent.Subtitle(text: subtitle, color: .neutral),
subtitle: nil,
subtitleComponent: AnyComponent(AffiliatePeerSubtitleComponent(
theme: environment.theme,
percentText: commissionTitle,
text: durationTitle
)),
subtitleAccessory: .none,
presence: nil,
rightAccessory: .none,
rightAccessory: .disclosure,
selectionState: .none,
hasNext: false,
extractedTheme: PeerListItemComponent.ExtractedTheme(
inset: 2.0,
background: environment.theme.list.itemBlocksBackgroundColor
),
action: { peer, _, itemView in
itemContextAction(peer, itemView.extractedContainerView, nil)
insets: UIEdgeInsets(top: -1.0, left: 0.0, bottom: -1.0, right: 0.0),
action: { [weak self] peer, _, itemView in
guard let self, let component = self.component else {
return
}
let _ = (component.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: item.peer.id),
TelegramEngine.EngineData.Item.Peer.Peer(id: component.initialContent.peerId)
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] botPeer, targetPeer in
guard let self, let component = self.component else {
return
}
guard let botPeer, let targetPeer else {
return
}
self.environment?.controller()?.push(JoinAffiliateProgramScreen(
context: component.context,
sourcePeer: botPeer,
commissionPermille: item.commissionPermille,
programDuration: item.durationMonths,
mode: .join(JoinAffiliateProgramScreen.Join(
initialTargetPeer: targetPeer,
canSelectTargetPeer: false,
completion: { [weak self] _ in
guard let self, let component = self.component else {
return
}
let _ = (component.context.engine.peers.connectStarRefBot(id: component.initialContent.peerId, botId: peer.id)
|> deliverOnMainQueue).startStandalone(next: { [weak self] result in
guard let self else {
return
}
if let connectedStarBotList = self.connectedStarBotList {
var updatedItems = connectedStarBotList.items
if !updatedItems.contains(where: { $0.peer.id == peer.id }) {
updatedItems.insert(result, at: 0)
}
self.connectedStarBotList = TelegramConnectedStarRefBotList(
items: updatedItems,
totalCount: connectedStarBotList.totalCount + 1
)
self.state?.updated(transition: .immediate)
self.openConnectedBot(bot: result)
}
})
}
))
))
})
},
inlineActions: nil,
contextAction: { peer, sourceView, gesture in
itemContextAction(peer, sourceView, gesture)
}
contextAction: nil
))))
}
@ -1255,14 +1443,27 @@ If you end your affiliate program:
transition: transition,
component: AnyComponent(ListSectionComponent(
theme: environment.theme,
header: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "SUGGESTED PROGRAMS",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
)),
header: AnyComponent(HStack([
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "PROGRAMS",
font: Font.regular(13.0),
textColor: environment.theme.list.freeTextColor
)),
maximumNumberOfLines: 0
))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(BotSectionSortButtonComponent(
theme: environment.theme,
strings: environment.strings,
sortMode: self.suggestedSortMode,
action: { [weak self] sourceView in
guard let self else {
return
}
self.openSortModeMenu(sourceView: sourceView)
}
)))
], spacing: 4.0, alignment: .alternatingLeftRight)),
footer: nil,
items: suggestedSectionItems,
displaySeparators: true
@ -1271,8 +1472,9 @@ If you end your affiliate program:
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
)
let suggestedProgramsSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: suggestedProgramsSectionSize)
if let suggestedProgramsSectionView = self.suggestedProgramsSection.view {
if let suggestedProgramsSectionView = self.suggestedProgramsSection.view as? ListSectionComponent.View {
if suggestedProgramsSectionView.superview == nil {
suggestedProgramsSectionView.contentViewImpl.layer.allowsGroupOpacity = true
self.scrollView.addSubview(suggestedProgramsSectionView)
}
transition.setFrame(view: suggestedProgramsSectionView, frame: suggestedProgramsSectionFrame)
@ -1281,6 +1483,9 @@ If you end your affiliate program:
} else {
suggestedProgramsSectionView.isHidden = true
}
suggestedProgramsSectionView.contentViewImpl.alpha = self.isSuggestedSortModeUpdating ? 0.6 : 1.0
suggestedProgramsSectionView.contentViewImpl.isUserInteractionEnabled = !self.isSuggestedSortModeUpdating
}
if !suggestedStarBotListItems.isEmpty {
contentHeight += suggestedProgramsSectionSize.height
@ -1440,3 +1645,17 @@ private final class ListContextExtractedContentSource: ContextExtractedContentSo
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
}
}
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceView: UIView
init(controller: ViewController, sourceView: UIView) {
self.controller = controller
self.sourceView = sourceView
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -0,0 +1,246 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramPresentationData
import MultilineTextComponent
import AlertComponent
final class TableComponent: CombinedComponent {
class Item: Equatable {
public let id: AnyHashable
public let title: String
public let component: AnyComponent<Empty>
public let insets: UIEdgeInsets?
public init<IdType: Hashable>(id: IdType, title: String, component: AnyComponent<Empty>, insets: UIEdgeInsets? = nil) {
self.id = AnyHashable(id)
self.title = title
self.component = component
self.insets = insets
}
public static func == (lhs: Item, rhs: Item) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.component != rhs.component {
return false
}
if lhs.insets != rhs.insets {
return false
}
return true
}
}
private let theme: PresentationTheme
private let items: [Item]
public init(theme: PresentationTheme, items: [Item]) {
self.theme = theme
self.items = items
}
public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.items != rhs.items {
return false
}
return true
}
final class State: ComponentState {
var cachedBorderImage: (UIImage, PresentationTheme)?
}
func makeState() -> State {
return State()
}
public static var body: Body {
let leftColumnBackground = Child(Rectangle.self)
let verticalBorder = Child(Rectangle.self)
let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let outerBorder = Child(Image.self)
return { context in
let verticalPadding: CGFloat = 11.0
let horizontalPadding: CGFloat = 12.0
let borderWidth: CGFloat = 1.0
let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor
let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6)
var leftColumnWidth: CGFloat = 0.0
var updatedTitleChildren: [_UpdatedChildComponent] = []
var updatedValueChildren: [(_UpdatedChildComponent, UIEdgeInsets)] = []
var updatedBorderChildren: [_UpdatedChildComponent] = []
for item in context.component.items {
let titleChild = titleChildren[item.id].update(
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: item.title, font: Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor))
)),
availableSize: context.availableSize,
transition: context.transition
)
updatedTitleChildren.append(titleChild)
if titleChild.size.width > leftColumnWidth {
leftColumnWidth = titleChild.size.width
}
}
leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0)
let rightColumnWidth = context.availableSize.width - leftColumnWidth
var i = 0
var rowHeights: [Int: CGFloat] = [:]
var totalHeight: CGFloat = 0.0
for item in context.component.items {
let titleChild = updatedTitleChildren[i]
let insets: UIEdgeInsets
if let customInsets = item.insets {
insets = customInsets
} else {
insets = UIEdgeInsets(top: 0.0, left: horizontalPadding, bottom: 0.0, right: horizontalPadding)
}
let valueChild = valueChildren[item.id].update(
component: item.component,
availableSize: CGSize(width: rightColumnWidth - insets.left - insets.right, height: context.availableSize.height),
transition: context.transition
)
updatedValueChildren.append((valueChild, insets))
let rowHeight = max(40.0, max(titleChild.size.height, valueChild.size.height) + verticalPadding * 2.0)
rowHeights[i] = rowHeight
totalHeight += rowHeight
if i < context.component.items.count - 1 {
let borderChild = borderChildren[item.id].update(
component: AnyComponent(Rectangle(color: borderColor)),
availableSize: CGSize(width: context.availableSize.width, height: borderWidth),
transition: context.transition
)
updatedBorderChildren.append(borderChild)
}
i += 1
}
let leftColumnBackground = leftColumnBackground.update(
component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor),
availableSize: CGSize(width: leftColumnWidth, height: totalHeight),
transition: context.transition
)
context.add(
leftColumnBackground
.position(CGPoint(x: leftColumnWidth / 2.0, y: totalHeight / 2.0))
)
let borderImage: UIImage
if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme {
borderImage = currentImage
} else {
let borderRadius: CGFloat = 5.0
borderImage = generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
let bounds = CGRect(origin: .zero, size: size)
context.setFillColor(backgroundColor.cgColor)
context.fill(bounds)
let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil)
context.setBlendMode(.clear)
context.addPath(path)
context.fillPath()
context.setBlendMode(.normal)
context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.addPath(path)
context.strokePath()
})!.stretchableImage(withLeftCapWidth: 5, topCapHeight: 5)
context.state.cachedBorderImage = (borderImage, context.component.theme)
}
let outerBorder = outerBorder.update(
component: Image(image: borderImage),
availableSize: CGSize(width: context.availableSize.width, height: totalHeight),
transition: context.transition
)
context.add(outerBorder
.position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0))
)
let verticalBorder = verticalBorder.update(
component: Rectangle(color: borderColor),
availableSize: CGSize(width: borderWidth, height: totalHeight),
transition: context.transition
)
context.add(
verticalBorder
.position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: totalHeight / 2.0))
)
i = 0
var originY: CGFloat = 0.0
for (titleChild, (valueChild, valueInsets)) in zip(updatedTitleChildren, updatedValueChildren) {
let rowHeight = rowHeights[i] ?? 0.0
let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size)
let valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + valueInsets.left, y: originY + verticalPadding), size: valueChild.size)
context.add(titleChild
.position(titleFrame.center)
)
context.add(valueChild
.position(valueFrame.center)
)
if i < updatedBorderChildren.count {
let borderChild = updatedBorderChildren[i]
context.add(borderChild
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0))
)
}
originY += rowHeight
i += 1
}
return CGSize(width: context.availableSize.width, height: totalHeight)
}
}
}
func tableAlert(theme: PresentationTheme, title: String, text: String, table: TableComponent, actions: [ComponentAlertAction]) -> ViewController {
let content: AnyComponent<Empty> = AnyComponent(VStack([
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: title, font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor)),
horizontalAlignment: .center
))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: theme.actionSheet.primaryTextColor)),
horizontalAlignment: .center,
maximumNumberOfLines: 0
))),
AnyComponentWithIdentity(id: 2, component: AnyComponent(table)),
], spacing: 10.0))
return componentAlertController(
theme: AlertControllerTheme(presentationTheme: theme, fontSize: .regular),
content: content,
actions: actions,
actionLayout: .horizontal
)
}

View File

@ -993,8 +993,8 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
}))
if let starsState = data.starsState {
let balanceText: String
if starsState.balance > 0 {
balanceText = presentationStringsFormattedNumber(Int32(starsState.balance), presentationData.dateTimeFormat.groupingSeparator)
if starsState.balance > StarsAmount.zero {
balanceText = presentationStringsFormattedNumber(starsState.balance, presentationData.dateTimeFormat.groupingSeparator)
} else {
balanceText = ""
}
@ -1514,10 +1514,10 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
let revenueBalance = data.revenueStatsState?.balances.currentBalance ?? 0
let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue ?? 0
let starsBalance = data.starsRevenueStatsState?.balances.currentBalance ?? 0
let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? 0
let starsBalance = data.starsRevenueStatsState?.balances.currentBalance ?? StarsAmount.zero
let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? StarsAmount.zero
if overallRevenueBalance > 0 || overallStarsBalance > 0 {
if overallRevenueBalance > 0 || overallStarsBalance > StarsAmount.zero {
items[.balances]!.append(PeerInfoScreenHeaderItem(id: 20, text: presentationData.strings.PeerInfo_BotBalance_Title))
if overallRevenueBalance > 0 {
let string = "*\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))"
@ -1531,8 +1531,8 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}))
}
if overallStarsBalance > 0 {
let string = "*\(presentationStringsFormattedNumber(Int32(starsBalance), presentationData.dateTimeFormat.groupingSeparator))"
if overallStarsBalance > StarsAmount.zero {
let string = "*\(presentationStringsFormattedNumber(starsBalance, presentationData.dateTimeFormat.groupingSeparator))"
let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor)
if let range = attributedString.string.range(of: "*") {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
@ -1765,21 +1765,21 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
if cachedData.flags.contains(.canViewRevenue) || cachedData.flags.contains(.canViewStarsRevenue) {
let revenueBalance = data.revenueStatsState?.balances.currentBalance ?? 0
let starsBalance = data.starsRevenueStatsState?.balances.currentBalance ?? 0
let starsBalance = data.starsRevenueStatsState?.balances.currentBalance ?? StarsAmount.zero
let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue ?? 0
let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? 0
let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? StarsAmount.zero
if overallRevenueBalance > 0 || overallStarsBalance > 0 {
if overallRevenueBalance > 0 || overallStarsBalance > StarsAmount.zero {
var string = ""
if overallRevenueBalance > 0 {
string.append("#\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))")
}
if overallStarsBalance > 0 {
if overallStarsBalance > StarsAmount.zero {
if !string.isEmpty {
string.append(" ")
}
string.append("*\(presentationStringsFormattedNumber(Int32(starsBalance), presentationData.dateTimeFormat.groupingSeparator))")
string.append("*\(presentationStringsFormattedNumber(starsBalance, presentationData.dateTimeFormat.groupingSeparator))")
}
let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor)
if let range = attributedString.string.range(of: "#") {

View File

@ -200,16 +200,16 @@ public final class PlainButtonComponent: Component {
transition: component.animateContents ? transition : transition.withAnimation(.none),
component: component.content,
environment: {},
containerSize: availableSize
containerSize: CGSize(width: availableSize.width - component.contentInsets.left - component.contentInsets.right, height: availableSize.height - component.contentInsets.top - component.contentInsets.bottom)
)
var size = contentSize
size.width += component.contentInsets.left + component.contentInsets.right
size.height += component.contentInsets.top + component.contentInsets.bottom
if let minSize = component.minSize {
size.width = max(size.width, minSize.width)
size.height = max(size.height, minSize.height)
}
size.width += component.contentInsets.left + component.contentInsets.right
size.height += component.contentInsets.top + component.contentInsets.bottom
if let contentView = self.content.view {
var contentTransition = transition

View File

@ -77,7 +77,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
let context: AccountContext
let externalState: ExternalState
let containerSize: CGSize
let balance: Int64?
let balance: StarsAmount?
let options: [Any]
let purpose: StarsPurchasePurpose
let selectedProductId: String?
@ -93,7 +93,7 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
context: AccountContext,
externalState: ExternalState,
containerSize: CGSize,
balance: Int64?,
balance: StarsAmount?,
options: [Any],
purpose: StarsPurchasePurpose,
selectedProductId: String?,
@ -297,17 +297,17 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
var items: [AnyComponentWithIdentity<Empty>] = []
if let products = state.products, let balance = context.component.balance {
var minimumCount: Int64?
var minimumCount: StarsAmount?
if let requiredStars = context.component.purpose.requiredStars {
if case .generic = context.component.purpose {
minimumCount = requiredStars
minimumCount = StarsAmount(value: requiredStars, nanos: 0)
} else {
minimumCount = requiredStars - balance
minimumCount = StarsAmount(value: requiredStars, nanos: 0) - balance
}
}
for product in products {
if let minimumCount, minimumCount > product.count && !(items.isEmpty && product.id == products.last?.id) {
if let minimumCount, minimumCount > StarsAmount(value: product.count, nanos: 0) && !(items.isEmpty && product.id == products.last?.id) {
continue
}
@ -847,10 +847,11 @@ private final class StarsPurchaseScreenComponent: CombinedComponent {
availableSize: context.availableSize,
transition: .immediate
)
let starsBalance: StarsAmount = state.starsState?.balance ?? StarsAmount.zero
let balanceValue = balanceValue.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: presentationStringsFormattedNumber(Int32(state.starsState?.balance ?? 0), environment.dateTimeFormat.groupingSeparator),
string: presentationStringsFormattedNumber(starsBalance, environment.dateTimeFormat.groupingSeparator),
font: Font.semibold(14.0),
textColor: environment.theme.actionSheet.primaryTextColor
)),

View File

@ -207,7 +207,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
var statusText: String?
var statusIsDestructive = false
let count: Int64
let count: StarsAmount
var countIsGeneric = false
var countOnTop = false
var transactionId: String?
@ -243,14 +243,14 @@ private final class StarsTransactionSheetContent: CombinedComponent {
titleText = strings.Stars_Transaction_Giveaway_Boost_Stars(Int32(stars))
descriptionText = ""
boostsText = strings.Stars_Transaction_Giveaway_Boost_Boosts(boosts)
count = stars
count = StarsAmount(value: stars, nanos: 0)
date = boost.date
toPeer = state.peerMap[peerId]
// toString = strings.Stars_Transaction_Giveaway_Boost_Subscribers(boost.quantity)
giveawayMessageId = boost.giveawayMessageId
isBoost = true
case let .importer(peer, pricing, importer, usdRate):
let usdValue = formatTonUsdValue(pricing.amount, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat)
let usdValue = formatTonUsdValue(pricing.amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat)
titleText = strings.Stars_Transaction_Subscription_Title
descriptionText = strings.Stars_Transaction_Subscription_PerMonthUsd(usdValue).string
count = pricing.amount
@ -499,7 +499,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
case let .receipt(receipt):
titleText = receipt.invoiceMedia.title
descriptionText = receipt.invoiceMedia.description
count = (receipt.invoice.prices.first?.amount ?? receipt.invoiceMedia.totalAmount) * -1
count = StarsAmount(value: (receipt.invoice.prices.first?.amount ?? receipt.invoiceMedia.totalAmount) * -1, nanos: 0)
transactionId = receipt.transactionId
date = receipt.date
if let peer = state.peerMap[receipt.botPaymentId] {
@ -516,7 +516,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
if case let .giftStars(_, _, countValue, _, _, _) = action.action {
titleText = incoming ? strings.Stars_Gift_Received_Title : strings.Stars_Gift_Sent_Title
count = countValue
count = StarsAmount(value: countValue, nanos: 0)
if !incoming {
countIsGeneric = true
}
@ -530,7 +530,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if case let .prizeStars(countValue, _, boostPeerId, _, giveawayMessageIdValue) = action.action {
titleText = strings.Stars_Transaction_Giveaway_Title
count = countValue
count = StarsAmount(value: countValue, nanos: 0)
countOnTop = true
transactionId = nil
giveawayMessageId = giveawayMessageIdValue
@ -561,7 +561,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
descriptionText = modifiedString
}
let formattedAmount = presentationStringsFormattedNumber(abs(Int32(count)), dateTimeFormat.groupingSeparator)
let absCount = StarsAmount(value: abs(count.value), nanos: abs(count.nanos))
let formattedAmount = presentationStringsFormattedNumber(absCount, dateTimeFormat.groupingSeparator)
let countColor: UIColor
var countFont: UIFont = isSubscription || isSubscriber ? Font.regular(17.0) : Font.semibold(17.0)
var countBackgroundColor: UIColor?
@ -576,7 +577,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if countIsGeneric {
amountText = "\(formattedAmount)"
countColor = theme.list.itemPrimaryTextColor
} else if count < 0 {
} else if count < StarsAmount.zero {
amountText = "- \(formattedAmount)"
countColor = theme.list.itemDestructiveColor
} else {
@ -602,7 +603,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let imageSubject: StarsImageComponent.Subject
var imageIcon: StarsImageComponent.Icon?
if isGift {
imageSubject = .gift(count)
imageSubject = .gift(count.value)
} else if !media.isEmpty {
imageSubject = .media(media)
} else if let photo {
@ -734,7 +735,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else if isSubscriber {
title = strings.Stars_Transaction_Subscription_Subscriber
} else {
title = count < 0 || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From
title = count < StarsAmount.zero || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From
}
tableItems.append(.init(
id: "to",
@ -788,7 +789,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
id: "prize",
title: strings.Stars_Transaction_Giveaway_Prize,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_Giveaway_Stars(Int32(count)), font: tableFont, textColor: tableTextColor)))
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_Giveaway_Stars(Int32(count.value)), font: tableFont, textColor: tableTextColor)))
)
))

View File

@ -10,12 +10,13 @@ import PresentationDataUtils
import ButtonComponent
import BundleIconComponent
import TelegramStringFormatting
import TelegramCore
final class StarsBalanceComponent: Component {
let theme: PresentationTheme
let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat
let count: Int64
let count: StarsAmount
let rate: Double?
let actionTitle: String
let actionAvailable: Bool
@ -29,7 +30,7 @@ final class StarsBalanceComponent: Component {
theme: PresentationTheme,
strings: PresentationStrings,
dateTimeFormat: PresentationDateTimeFormat,
count: Int64,
count: StarsAmount,
rate: Double?,
actionTitle: String,
actionAvailable: Bool,
@ -139,7 +140,7 @@ final class StarsBalanceComponent: Component {
let sideInset: CGFloat = 16.0
var contentHeight: CGFloat = sideInset
let balanceString = presentationStringsFormattedNumber(Int32(component.count), component.dateTimeFormat.groupingSeparator)
let balanceString = presentationStringsFormattedNumber(component.count, component.dateTimeFormat.groupingSeparator)
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(
@ -168,7 +169,7 @@ final class StarsBalanceComponent: Component {
let subtitleText: String
if let rate = component.rate {
subtitleText = "\(formatTonUsdValue(component.count, divide: false, rate: rate, dateTimeFormat: component.dateTimeFormat))"
subtitleText = "\(formatTonUsdValue(component.count.value, divide: false, rate: rate, dateTimeFormat: component.dateTimeFormat))"
} else {
subtitleText = component.strings.Stars_Intro_YourBalance
}

View File

@ -8,19 +8,20 @@ import MultilineTextComponent
import TelegramPresentationData
import PresentationDataUtils
import TelegramStringFormatting
import TelegramCore
final class StarsOverviewItemComponent: Component {
let theme: PresentationTheme
let dateTimeFormat: PresentationDateTimeFormat
let title: String
let value: Int64
let value: StarsAmount
let rate: Double
init(
theme: PresentationTheme,
dateTimeFormat: PresentationDateTimeFormat,
title: String,
value: Int64,
value: StarsAmount,
rate: Double
) {
self.theme = theme
@ -80,8 +81,8 @@ final class StarsOverviewItemComponent: Component {
valueOffset += icon.size.width
}
let valueString = presentationStringsFormattedNumber(Int32(component.value), component.dateTimeFormat.groupingSeparator)
let usdValueString = formatTonUsdValue(component.value, divide: false, rate: component.rate, dateTimeFormat: component.dateTimeFormat)
let valueString = presentationStringsFormattedNumber(component.value, component.dateTimeFormat.groupingSeparator)
let usdValueString = formatTonUsdValue(component.value.value, divide: false, rate: component.rate, dateTimeFormat: component.dateTimeFormat)
let valueSize = self.value.update(
transition: .immediate,

View File

@ -519,21 +519,21 @@ final class StarsStatisticsScreenComponent: Component {
theme: environment.theme,
dateTimeFormat: environment.dateTimeFormat,
title: strings.Stars_BotRevenue_Proceeds_Available,
value: starsState?.balances.availableBalance ?? 0,
value: starsState?.balances.availableBalance ?? StarsAmount.zero,
rate: starsState?.usdRate ?? 0.0
))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(StarsOverviewItemComponent(
theme: environment.theme,
dateTimeFormat: environment.dateTimeFormat,
title: strings.Stars_BotRevenue_Proceeds_Current,
value: starsState?.balances.currentBalance ?? 0,
value: starsState?.balances.currentBalance ?? StarsAmount.zero,
rate: starsState?.usdRate ?? 0.0
))),
AnyComponentWithIdentity(id: 2, component: AnyComponent(StarsOverviewItemComponent(
theme: environment.theme,
dateTimeFormat: environment.dateTimeFormat,
title: strings.Stars_BotRevenue_Proceeds_Total,
value: starsState?.balances.overallRevenue ?? 0,
value: starsState?.balances.overallRevenue ?? StarsAmount.zero,
rate: starsState?.usdRate ?? 0.0
)))
],
@ -602,7 +602,7 @@ final class StarsStatisticsScreenComponent: Component {
theme: environment.theme,
strings: strings,
dateTimeFormat: environment.dateTimeFormat,
count: self.starsState?.balances.availableBalance ?? 0,
count: self.starsState?.balances.availableBalance ?? StarsAmount.zero,
rate: self.starsState?.usdRate ?? 0,
actionTitle: strings.Stars_BotRevenue_Withdraw_Withdraw,
actionAvailable: true,

View File

@ -20,7 +20,7 @@ import LottieComponent
private extension StarsContext.State.Transaction {
var extendedId: String {
if self.count > 0 {
if self.count > StarsAmount.zero {
return "\(id)_in"
} else {
return "\(id)_out"
@ -304,7 +304,7 @@ final class StarsTransactionsListPanelComponent: Component {
case let .peer(peer):
if let starGift = item.starGift {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
itemSubtitle = item.count > 0 ? environment.strings.Stars_Intro_Transaction_ConvertedGift : environment.strings.Stars_Intro_Transaction_Gift
itemSubtitle = item.count > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_ConvertedGift : environment.strings.Stars_Intro_Transaction_Gift
itemFile = starGift.file
} else if let _ = item.giveawayMessageId {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
@ -344,7 +344,7 @@ final class StarsTransactionsListPanelComponent: Component {
itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Subtitle
}
} else {
if item.count > 0 && !item.flags.contains(.isRefund) {
if item.count > StarsAmount.zero && !item.flags.contains(.isRefund) {
itemTitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Title
itemSubtitle = environment.strings.Stars_Intro_Transaction_FragmentTopUp_Subtitle
} else {
@ -373,8 +373,9 @@ final class StarsTransactionsListPanelComponent: Component {
let itemLabel: NSAttributedString
let labelString: String
let formattedLabel = presentationStringsFormattedNumber(abs(Int32(item.count)), environment.dateTimeFormat.groupingSeparator)
if item.count < 0 {
let absCount = StarsAmount(value: abs(item.count.value), nanos: abs(item.count.nanos))
let formattedLabel = presentationStringsFormattedNumber(absCount, environment.dateTimeFormat.groupingSeparator)
if item.count < StarsAmount.zero {
labelString = "- \(formattedLabel)"
} else {
labelString = "+ \(formattedLabel)"

View File

@ -127,7 +127,7 @@ final class StarsTransactionsScreenComponent: Component {
private var stateDisposable: Disposable?
private var starsState: StarsContext.State?
private var previousBalance: Int64?
private var previousBalance: StarsAmount?
private var subscriptionsStateDisposable: Disposable?
private var subscriptionsState: StarsSubscriptionsContext.State?
@ -527,7 +527,7 @@ final class StarsTransactionsScreenComponent: Component {
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: presentationStringsFormattedNumber(Int32(self.starsState?.balance ?? 0), environment.dateTimeFormat.groupingSeparator),
string: presentationStringsFormattedNumber(self.starsState?.balance ?? StarsAmount.zero, environment.dateTimeFormat.groupingSeparator),
font: Font.semibold(14.0),
textColor: environment.theme.actionSheet.primaryTextColor
)),
@ -611,7 +611,7 @@ final class StarsTransactionsScreenComponent: Component {
theme: environment.theme,
strings: environment.strings,
dateTimeFormat: environment.dateTimeFormat,
count: self.starsState?.balance ?? 0,
count: self.starsState?.balance ?? StarsAmount.zero,
rate: nil,
actionTitle: environment.strings.Stars_Intro_Buy,
actionAvailable: !premiumConfiguration.areStarsDisabled,
@ -1091,7 +1091,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
guard let self else {
return
}
self.starsContext.add(balance: stars)
self.starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let resultController = UndoOverlayController(

View File

@ -80,7 +80,7 @@ private final class SheetContent: CombinedComponent {
private(set) var chatPeer: EnginePeer?
private(set) var authorPeer: EnginePeer?
private var peerDisposable: Disposable?
private(set) var balance: Int64?
private(set) var balance: StarsAmount?
private(set) var form: BotPaymentForm?
private(set) var navigateToPeer: (EnginePeer) -> Void
@ -129,14 +129,14 @@ private final class SheetContent: CombinedComponent {
guard let self else {
return
}
self.balance = inputData?.0.balance ?? 0
self.balance = inputData?.0.balance ?? StarsAmount.zero
self.form = inputData?.1
self.botPeer = inputData?.2
self.chatPeer = chatPeer
self.authorPeer = inputData?.3
self.updated(transition: .immediate)
if self.optionsDisposable == nil, let balance = self.balance, balance < self.invoice.totalAmount {
if self.optionsDisposable == nil, let balance = self.balance, balance < StarsAmount(value: self.invoice.totalAmount, nanos: 0) {
self.optionsDisposable = (context.engine.payments.starsTopUpOptions()
|> deliverOnMainQueue).start(next: { [weak self] options in
guard let self else {
@ -206,7 +206,7 @@ private final class SheetContent: CombinedComponent {
})
}
if balance < self.invoice.totalAmount {
if balance < StarsAmount(value: self.invoice.totalAmount, nanos: 0) {
if self.options.isEmpty {
self.inProgress = true
self.updated()
@ -236,7 +236,7 @@ private final class SheetContent: CombinedComponent {
|> take(1)
|> deliverOnMainQueue).start(next: { _ in
Queue.mainQueue().after(0.1, { [weak self] in
if let self, let balance = self.balance, balance < self.invoice.totalAmount {
if let self, let balance = self.balance, balance < StarsAmount(value: self.invoice.totalAmount, nanos: 0) {
self.inProgress = false
self.updated()
@ -272,7 +272,7 @@ private final class SheetContent: CombinedComponent {
let balanceValue = Child(MultilineTextComponent.self)
let balanceIcon = Child(BundleIconComponent.self)
let info = Child(BalancedTextComponent.self)
return { context in
let environment = context.environment[EnvironmentType.self]
let component = context.component
@ -501,7 +501,7 @@ private final class SheetContent: CombinedComponent {
let balanceValue = balanceValue.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: presentationStringsFormattedNumber(Int32(state.balance ?? 0), environment.dateTimeFormat.groupingSeparator),
string: presentationStringsFormattedNumber(state.balance ?? StarsAmount.zero, environment.dateTimeFormat.groupingSeparator),
font: Font.semibold(16.0),
textColor: textColor
)),
@ -586,7 +586,7 @@ private final class SheetContent: CombinedComponent {
options: state?.options ?? [],
purpose: purpose,
completion: { [weak starsContext] stars in
starsContext?.add(balance: stars)
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
Queue.mainQueue().after(0.1) {
completion()
}
@ -650,7 +650,6 @@ private final class SheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + button.size.height / 2.0))
)
contentSize.height += button.size.height
if isSubscription {
contentSize.height += 14.0

View File

@ -102,8 +102,8 @@ private final class SheetContent: CombinedComponent {
let amountPlaceholder: String
let amountLabel: String?
let minAmount: Int64?
let maxAmount: Int64?
let minAmount: StarsAmount?
let maxAmount: StarsAmount?
let configuration = StarsWithdrawConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
@ -113,7 +113,7 @@ private final class SheetContent: CombinedComponent {
amountTitle = environment.strings.Stars_Withdraw_AmountTitle
amountPlaceholder = environment.strings.Stars_Withdraw_AmountPlaceholder
minAmount = configuration.minWithdrawAmount
minAmount = configuration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
maxAmount = status.balances.availableBalance
amountLabel = nil
case .paidMedia:
@ -121,13 +121,13 @@ private final class SheetContent: CombinedComponent {
amountTitle = environment.strings.Stars_PaidContent_AmountTitle
amountPlaceholder = environment.strings.Stars_PaidContent_AmountPlaceholder
minAmount = 1
maxAmount = configuration.maxPaidMediaAmount
minAmount = StarsAmount(value: 1, nanos: 0)
maxAmount = configuration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate, let amount = state.amount, amount > 0 {
if let usdWithdrawRate = configuration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
amountLabel = "\(formatTonUsdValue(amount, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
amountLabel = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
} else {
amountLabel = nil
}
@ -136,8 +136,8 @@ private final class SheetContent: CombinedComponent {
amountTitle = environment.strings.Stars_SendStars_AmountTitle
amountPlaceholder = environment.strings.Stars_SendStars_AmountPlaceholder
minAmount = 1
maxAmount = configuration.maxPaidMediaAmount
minAmount = StarsAmount(value: 1, nanos: 0)
maxAmount = configuration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
amountLabel = nil
}
@ -152,7 +152,7 @@ private final class SheetContent: CombinedComponent {
contentSize.height += title.size.height
contentSize.height += 40.0
let balance: Int64?
let balance: StarsAmount?
if case .reaction = component.mode {
balance = state.balance
} else if case let .withdraw(starsState) = component.mode {
@ -177,7 +177,7 @@ private final class SheetContent: CombinedComponent {
let balanceValue = balanceValue.update(
component: MultilineTextComponent(
text: .plain(NSAttributedString(
string: presentationStringsFormattedNumber(Int32(balance), environment.dateTimeFormat.groupingSeparator),
string: presentationStringsFormattedNumber(balance, environment.dateTimeFormat.groupingSeparator),
font: Font.semibold(16.0),
textColor: theme.list.itemPrimaryTextColor
)),
@ -246,7 +246,6 @@ private final class SheetContent: CombinedComponent {
default:
amountFooter = nil
}
let amountSection = amountSection.update(
component: ListSectionComponent(
theme: theme,
@ -267,13 +266,13 @@ private final class SheetContent: CombinedComponent {
textColor: theme.list.itemPrimaryTextColor,
secondaryColor: theme.list.itemSecondaryTextColor,
placeholderColor: theme.list.itemPlaceholderTextColor,
value: state.amount,
minValue: minAmount,
maxValue: maxAmount,
value: state.amount?.value,
minValue: minAmount?.value,
maxValue: maxAmount?.value,
placeholderText: amountPlaceholder,
labelText: amountLabel,
amountUpdated: { [weak state] amount in
state?.amount = amount
state?.amount = amount.flatMap { StarsAmount(value: $0, nanos: 0) }
state?.updated()
},
tag: amountTag
@ -298,7 +297,7 @@ private final class SheetContent: CombinedComponent {
if case .paidMedia = component.mode {
buttonString = environment.strings.Stars_PaidContent_Create
} else if let amount = state.amount {
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(Int32(amount), environment.dateTimeFormat.groupingSeparator))"
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
} else {
buttonString = environment.strings.Stars_Withdraw_Withdraw
}
@ -326,14 +325,14 @@ private final class SheetContent: CombinedComponent {
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
),
isEnabled: (state.amount ?? 0) > 0,
isEnabled: (state.amount ?? StarsAmount.zero) > StarsAmount.zero,
displaysProgress: false,
action: { [weak state] in
if let controller = controller() as? StarsWithdrawScreen, let amount = state?.amount {
if let minAmount, amount < minAmount {
controller.presentMinAmountTooltip(minAmount)
controller.presentMinAmountTooltip(minAmount.value)
} else {
controller.completion(amount)
controller.completion(amount.value)
controller.dismissAnimated()
}
}
@ -360,9 +359,9 @@ private final class SheetContent: CombinedComponent {
private let context: AccountContext
private let mode: StarsWithdrawScreen.Mode
fileprivate var amount: Int64?
fileprivate var amount: StarsAmount?
fileprivate var balance: Int64?
fileprivate var balance: StarsAmount?
private var stateDisposable: Disposable?
var cachedCloseImage: (UIImage, PresentationTheme)?
@ -376,12 +375,12 @@ private final class SheetContent: CombinedComponent {
self.context = context
self.mode = mode
var amount: Int64?
var amount: StarsAmount?
switch mode {
case let .withdraw(stats):
amount = stats.balances.availableBalance
case let .paidMedia(initialValue):
amount = initialValue
amount = initialValue.flatMap { StarsAmount(value: $0, nanos: 0) }
case .reaction:
amount = nil
}

View File

@ -28,7 +28,24 @@ private let readIconImage: UIImage? = generateTintedImage(image: UIImage(bundleI
private let repostIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Stories/HeaderRepost"), color: .white)?.withRenderingMode(.alwaysTemplate)
private let forwardIconImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Stories/HeaderForward"), color: .white)?.withRenderingMode(.alwaysTemplate)
private let checkImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)?.withRenderingMode(.alwaysTemplate)
private let disclosureImage: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Item List/DisclosureArrow"), color: .white)?.withRenderingMode(.alwaysTemplate)
private func generateDisclosureImage() -> UIImage? {
return generateImage(CGSize(width: 7.0, height: 12.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(UIColor.white.cgColor)
let lineWidth: CGFloat = 2.0
context.setLineWidth(lineWidth)
context.setLineJoin(.round)
context.setLineCap(.round)
context.move(to: CGPoint(x: lineWidth * 0.5, y: lineWidth * 0.5))
context.addLine(to: CGPoint(x: size.width - lineWidth * 0.5, y: size.height * 0.5))
context.addLine(to: CGPoint(x: lineWidth * 0.5, y: size.height - lineWidth * 0.5))
context.strokePath()
})?.withRenderingMode(.alwaysTemplate)
}
private let disclosureImage: UIImage? = generateDisclosureImage()
public final class PeerListItemComponent: Component {
public final class TransitionHint {
@ -214,6 +231,7 @@ public final class PeerListItemComponent: Component {
let peer: EnginePeer?
let storyStats: PeerStoryStats?
let subtitle: Subtitle?
let subtitleComponent: AnyComponent<Empty>?
let subtitleAccessory: SubtitleAccessory
let presence: EnginePeer.Presence?
let rightAccessory: RightAccessory
@ -226,6 +244,7 @@ public final class PeerListItemComponent: Component {
let isEnabled: Bool
let hasNext: Bool
let extractedTheme: ExtractedTheme?
let insets: UIEdgeInsets?
let action: (EnginePeer, EngineMessage.Id?, PeerListItemComponent.View) -> Void
let inlineActions: InlineActionsState?
let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)?
@ -243,6 +262,7 @@ public final class PeerListItemComponent: Component {
peer: EnginePeer?,
storyStats: PeerStoryStats? = nil,
subtitle: Subtitle?,
subtitleComponent: AnyComponent<Empty>? = nil,
subtitleAccessory: SubtitleAccessory,
presence: EnginePeer.Presence?,
rightAccessory: RightAccessory = .none,
@ -255,6 +275,7 @@ public final class PeerListItemComponent: Component {
isEnabled: Bool = true,
hasNext: Bool,
extractedTheme: ExtractedTheme? = nil,
insets: UIEdgeInsets? = nil,
action: @escaping (EnginePeer, EngineMessage.Id?, PeerListItemComponent.View) -> Void,
inlineActions: InlineActionsState? = nil,
contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
@ -271,6 +292,7 @@ public final class PeerListItemComponent: Component {
self.peer = peer
self.storyStats = storyStats
self.subtitle = subtitle
self.subtitleComponent = subtitleComponent
self.subtitleAccessory = subtitleAccessory
self.presence = presence
self.rightAccessory = rightAccessory
@ -283,6 +305,7 @@ public final class PeerListItemComponent: Component {
self.isEnabled = isEnabled
self.hasNext = hasNext
self.extractedTheme = extractedTheme
self.insets = insets
self.action = action
self.inlineActions = inlineActions
self.contextAction = contextAction
@ -323,6 +346,9 @@ public final class PeerListItemComponent: Component {
if lhs.subtitle != rhs.subtitle {
return false
}
if lhs.subtitleComponent != rhs.subtitleComponent {
return false
}
if lhs.subtitleAccessory != rhs.subtitleAccessory {
return false
}
@ -356,6 +382,12 @@ public final class PeerListItemComponent: Component {
if lhs.hasNext != rhs.hasNext {
return false
}
if lhs.insets != rhs.insets {
return false
}
if lhs.extractedTheme != rhs.extractedTheme {
return false
}
if lhs.inlineActions != rhs.inlineActions {
return false
}
@ -370,6 +402,7 @@ public final class PeerListItemComponent: Component {
private let title = ComponentView<Empty>()
private var label = ComponentView<Empty>()
private var subtitleView: ComponentView<Empty>?
private let separatorLayer: SimpleLayer
private var avatarNode: AvatarNode?
private var avatarImageView: UIImageView?
@ -378,6 +411,7 @@ public final class PeerListItemComponent: Component {
private var avatarComponentView: ComponentView<Empty>?
private var rightIconView: UIImageView?
private var iconView: UIImageView?
private var checkLayer: CheckLayer?
private var rightAccessoryComponentView: ComponentView<Empty>?
@ -665,25 +699,8 @@ public final class PeerListItemComponent: Component {
contextInset = 0.0
}
let height: CGFloat
let titleFont: UIFont
let subtitleFont: UIFont
switch component.style {
case .generic:
titleFont = Font.semibold(17.0)
subtitleFont = Font.regular(15.0)
if labelData.0.isEmpty {
height = 50.0
} else {
height = 60.0
}
case .compact:
titleFont = Font.semibold(14.0)
subtitleFont = Font.regular(14.0)
height = 42.0
}
let verticalInset: CGFloat = component.insets?.top ?? 1.0
let verticalInset: CGFloat = 1.0
var leftInset: CGFloat = 53.0 + component.sideInset
if case .generic = component.style {
leftInset += 9.0
@ -696,6 +713,50 @@ public final class PeerListItemComponent: Component {
rightInset += 40.0
}
var subtitleComponentSize: CGSize?
if let subtitleComponent = component.subtitleComponent {
let subtitleView: ComponentView<Empty>
if let current = self.subtitleView {
subtitleView = current
} else {
subtitleView = ComponentView()
self.subtitleView = subtitleView
}
subtitleComponentSize = subtitleView.update(
transition: transition,
component: subtitleComponent,
environment: {},
containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0)
)
} else if let subtitleView = self.subtitleView {
self.subtitleView = nil
subtitleView.view?.removeFromSuperview()
}
var height: CGFloat
let titleFont: UIFont
let subtitleFont: UIFont
switch component.style {
case .generic:
titleFont = Font.semibold(17.0)
subtitleFont = Font.regular(15.0)
if let subtitleComponentSize {
height = 40.0 + subtitleComponentSize.height + verticalInset * 2.0
} else if labelData.0.isEmpty {
height = 48.0 + verticalInset * 2.0
} else {
height = 58.0 + verticalInset * 2.0
}
case .compact:
titleFont = Font.semibold(14.0)
subtitleFont = Font.regular(14.0)
if let subtitleComponentSize {
height = 20.0 + subtitleComponentSize.height + verticalInset * 2.0
} else {
height = 40.0 + verticalInset * 2.0
}
}
var rightAccessoryComponentSize: CGSize?
if let rightAccessoryComponent = component.rightAccessoryComponent {
var rightAccessoryComponentTransition = transition
@ -911,9 +972,13 @@ public final class PeerListItemComponent: Component {
let availableTextWidth = availableSize.width - leftInset - rightInset
var titleAvailableWidth = component.style == .compact ? availableTextWidth * 0.7 : availableSize.width - leftInset - rightInset
if case .none = component.rightAccessory {
} else {
switch component.rightAccessory {
case .disclosure:
titleAvailableWidth -= 32.0
case .check:
titleAvailableWidth -= 20.0
case .none:
break
}
if statusIcon != nil {
@ -971,7 +1036,9 @@ public final class PeerListItemComponent: Component {
let titleSpacing: CGFloat = 2.0
let titleVerticalOffset: CGFloat = 0.0
let centralContentHeight: CGFloat
if labelSize.height > 0.0, case .generic = component.style {
if let subtitleComponentSize {
centralContentHeight = titleSize.height + subtitleComponentSize.height + titleSpacing
} else if labelSize.height > 0.0, case .generic = component.style {
centralContentHeight = titleSize.height + labelSize.height + titleSpacing
} else {
centralContentHeight = titleSize.height
@ -1086,7 +1153,6 @@ public final class PeerListItemComponent: Component {
labelFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: labelSize)
}
if labelView.superview == nil {
labelView.isUserInteractionEnabled = false
labelView.layer.anchorPoint = CGPoint()
@ -1107,20 +1173,31 @@ public final class PeerListItemComponent: Component {
}
}
if let subtitleComponentView = self.subtitleView?.view, let subtitleComponentSize {
let subtitleFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSpacing), size: subtitleComponentSize)
if subtitleComponentView.superview == nil {
subtitleComponentView.isUserInteractionEnabled = false
subtitleComponentView.layer.anchorPoint = CGPoint()
self.containerButton.addSubview(subtitleComponentView)
}
transition.setFrame(view: subtitleComponentView, frame: subtitleFrame)
}
let imageSize = CGSize(width: 22.0, height: 22.0)
self.iconFrame = CGRect(origin: CGPoint(x: availableSize.width - (contextInset * 2.0 + 14.0 + component.sideInset) - imageSize.width, y: floor((height - verticalInset * 2.0 - imageSize.height) * 0.5)), size: imageSize)
if case .none = component.rightAccessory {
if case .none = component.subtitleAccessory {
if let iconView = self.iconView {
self.iconView = nil
iconView.removeFromSuperview()
if let rightIconView = self.rightIconView {
self.rightIconView = nil
rightIconView.removeFromSuperview()
}
}
} else {
let iconView: UIImageView
if let current = self.iconView {
iconView = current
let rightIconView: UIImageView
if let current = self.rightIconView {
rightIconView = current
} else {
var image: UIImage?
var color: UIColor = component.theme.list.itemSecondaryTextColor
@ -1130,17 +1207,26 @@ public final class PeerListItemComponent: Component {
color = component.theme.list.itemAccentColor
case .disclosure:
image = disclosureImage
color = component.theme.list.disclosureArrowColor
case .none:
break
}
iconView = UIImageView(image: image)
iconView.tintColor = color
self.iconView = iconView
self.containerButton.addSubview(iconView)
rightIconView = UIImageView(image: image)
rightIconView.tintColor = color
self.rightIconView = rightIconView
self.containerButton.addSubview(rightIconView)
}
if let image = iconView.image {
transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: availableSize.width - image.size.width, y: floor((height - verticalInset * 2.0 - image.size.width) / 2.0)), size: image.size))
if let image = rightIconView.image {
let iconFrame: CGRect
switch component.rightAccessory {
case .disclosure:
iconFrame = CGRect(origin: CGPoint(x: availableSize.width - image.size.width - 16.0 - contextInset, y: floor((height - verticalInset * 2.0 - image.size.height) / 2.0)), size: image.size)
default:
iconFrame = CGRect(origin: CGPoint(x: availableSize.width - image.size.width, y: floor((height - verticalInset * 2.0 - image.size.height) / 2.0)), size: image.size)
}
transition.setFrame(view: rightIconView, frame: iconFrame)
}
}

View File

@ -7,13 +7,19 @@ import ComponentDisplayAdapters
public final class ToastContentComponent: Component {
public let icon: AnyComponent<Empty>
public let content: AnyComponent<Empty>
public let insets: UIEdgeInsets
public let iconSpacing: CGFloat
public init(
icon: AnyComponent<Empty>,
content: AnyComponent<Empty>
content: AnyComponent<Empty>,
insets: UIEdgeInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0),
iconSpacing: CGFloat = 10.0
) {
self.icon = icon
self.content = content
self.insets = insets
self.iconSpacing = iconSpacing
}
public static func ==(lhs: ToastContentComponent, rhs: ToastContentComponent) -> Bool {
@ -23,6 +29,12 @@ public final class ToastContentComponent: Component {
if lhs.content != rhs.content {
return false
}
if lhs.insets != rhs.insets {
return false
}
if lhs.iconSpacing != rhs.iconSpacing {
return false
}
return true
}
@ -54,10 +66,11 @@ public final class ToastContentComponent: Component {
func update(component: ToastContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
var contentHeight: CGFloat = 0.0
let leftInset: CGFloat = 9.0
let rightInset: CGFloat = 6.0
let verticalInset: CGFloat = 10.0
let spacing: CGFloat = 9.0
let leftInset: CGFloat = component.insets.left
let rightInset: CGFloat = component.insets.right
let topInset: CGFloat = component.insets.top
let bottomInset: CGFloat = component.insets.bottom
let spacing: CGFloat = component.iconSpacing
let iconSize = self.icon.update(
transition: transition,
@ -72,7 +85,7 @@ public final class ToastContentComponent: Component {
containerSize: CGSize(width: availableSize.width - leftInset - rightInset - spacing - iconSize.width, height: availableSize.height)
)
contentHeight += verticalInset * 2.0 + max(iconSize.height, contentSize.height)
contentHeight += topInset + bottomInset + max(iconSize.height, contentSize.height)
if let iconView = self.icon.view {
if iconView.superview == nil {
@ -89,7 +102,7 @@ public final class ToastContentComponent: Component {
let size = CGSize(width: availableSize.width, height: contentHeight)
self.backgroundView.updateColor(color: UIColor(white: 0.0, alpha: 0.7), transition: .immediate)
self.backgroundView.update(size: size, cornerRadius: 10.0, transition: transition.containedViewLayoutTransition)
self.backgroundView.update(size: size, cornerRadius: 14.0, transition: transition.containedViewLayoutTransition)
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
return size

View File

@ -418,7 +418,7 @@ extension ChatControllerImpl {
return
}
if balance < 1 {
if balance < StarsAmount(value: 1, nanos: 0) {
controller?.dismiss(completion: {
guard let strongSelf = self else {
return

View File

@ -1755,7 +1755,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
if balance < 1 {
if balance < StarsAmount(value: 1, nanos: 0) {
let _ = (strongSelf.context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] options in

View File

@ -301,14 +301,14 @@ func openResolvedUrlImpl(
photo.append(photoRepresentation)
}
let channel = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil)
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: [], version: 0)
let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount.value, startParam: "", extendedMedia: nil, subscriptionPeriod: nil, flags: [], version: 0)
inputData.set(.single(BotCheckoutController.InputData(
form: BotPaymentForm(
id: subscriptionFormId,
canSaveCredentials: false,
passwordMissing: false,
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil, subscriptionPeriod: subscriptionPricing.period),
invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount.value)], tip: nil, termsInfo: nil, subscriptionPeriod: subscriptionPricing.period),
paymentBotId: channel.id,
providerId: nil,
url: nil,
@ -725,7 +725,7 @@ func openResolvedUrlImpl(
navigationController.pushViewController(controller, animated: true)
}
}
if let currentState = starsContext.currentState, currentState.balance >= amount {
if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = UndoOverlayController(
presentationData: presentationData,