Stars ref

This commit is contained in:
Isaac 2024-11-29 17:05:02 +04:00
parent ef652c0e30
commit 005887b8a8
7 changed files with 295 additions and 54 deletions

View File

@ -896,14 +896,12 @@ public enum JoinAffiliateProgramScreenMode {
public final class Active { public final class Active {
public let targetPeer: EnginePeer public let targetPeer: EnginePeer
public let link: String public let bot: TelegramConnectedStarRefBotList.Item
public let userCount: Int public let copyLink: (TelegramConnectedStarRefBotList.Item) -> Void
public let copyLink: () -> Void
public init(targetPeer: EnginePeer, link: String, userCount: Int, copyLink: @escaping () -> Void) { public init(targetPeer: EnginePeer, bot: TelegramConnectedStarRefBotList.Item, copyLink: @escaping (TelegramConnectedStarRefBotList.Item) -> Void) {
self.targetPeer = targetPeer self.targetPeer = targetPeer
self.link = link self.bot = bot
self.userCount = userCount
self.copyLink = copyLink self.copyLink = copyLink
} }
} }

View File

@ -632,7 +632,6 @@ func _internal_updateStarRefProgram(account: Account, id: EnginePeer.Id, program
} }
public final class TelegramConnectedStarRefBotList : Equatable { public final class TelegramConnectedStarRefBotList : Equatable {
public final class Item: Equatable { public final class Item: Equatable {
public let peer: EnginePeer public let peer: EnginePeer
public let url: String public let url: String

View File

@ -302,6 +302,10 @@ public struct StarsAmount: Equatable, Comparable, Hashable, Codable, CustomStrin
} }
public static func +(lhs: StarsAmount, rhs: StarsAmount) -> StarsAmount { public static func +(lhs: StarsAmount, rhs: StarsAmount) -> StarsAmount {
if rhs.value < 0 || rhs.nanos < 0 {
return lhs - StarsAmount(value: abs(rhs.value), nanos: abs(rhs.nanos))
}
let totalNanos = Int64(lhs.nanos) + Int64(rhs.nanos) let totalNanos = Int64(lhs.nanos) + Int64(rhs.nanos)
let overflow = totalNanos / 1_000_000_000 let overflow = totalNanos / 1_000_000_000
let remainingNanos = totalNanos % 1_000_000_000 let remainingNanos = totalNanos % 1_000_000_000
@ -529,7 +533,7 @@ private final class StarsContextImpl {
} }
var transactions = state.transactions var transactions = state.transactions
if addTransaction { if addTransaction {
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) 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, starrefCommissionPermille: nil, starrefPeerId: nil, starrefAmount: nil), at: 0)
} }
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)) 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))
@ -552,10 +556,6 @@ private extension StarsContext.State.Transaction {
init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) { init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) {
switch apiTransaction { switch apiTransaction {
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift, floodskipNumber, starrefCommissionPermille, starrefPeer, starrefAmount): 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 let parsedPeer: StarsContext.State.Transaction.Peer
var paidMessageId: MessageId? var paidMessageId: MessageId?
var giveawayMessageId: MessageId? var giveawayMessageId: MessageId?
@ -611,7 +611,7 @@ private extension StarsContext.State.Transaction {
let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? [] let media = extendedMedia.flatMap({ $0.compactMap { textMediaAndExpirationTimerFromApiMedia($0, PeerId(0)).media } }) ?? []
let _ = subscriptionPeriod let _ = subscriptionPeriod
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) 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, starrefCommissionPermille: starrefCommissionPermille, starrefPeerId: starrefPeer.flatMap(\.peerId), starrefAmount: starrefAmount.flatMap(StarsAmount.init(apiAmount:)))
} }
} }
} }
@ -686,6 +686,9 @@ public final class StarsContext {
public let subscriptionPeriod: Int32? public let subscriptionPeriod: Int32?
public let starGift: StarGift? public let starGift: StarGift?
public let floodskipNumber: Int32? public let floodskipNumber: Int32?
public let starrefCommissionPermille: Int32?
public let starrefPeerId: PeerId?
public let starrefAmount: StarsAmount?
public init( public init(
flags: Flags, flags: Flags,
@ -703,7 +706,10 @@ public final class StarsContext {
media: [Media], media: [Media],
subscriptionPeriod: Int32?, subscriptionPeriod: Int32?,
starGift: StarGift?, starGift: StarGift?,
floodskipNumber: Int32? floodskipNumber: Int32?,
starrefCommissionPermille: Int32?,
starrefPeerId: PeerId?,
starrefAmount: StarsAmount?
) { ) {
self.flags = flags self.flags = flags
self.id = id self.id = id
@ -721,6 +727,9 @@ public final class StarsContext {
self.subscriptionPeriod = subscriptionPeriod self.subscriptionPeriod = subscriptionPeriod
self.starGift = starGift self.starGift = starGift
self.floodskipNumber = floodskipNumber self.floodskipNumber = floodskipNumber
self.starrefCommissionPermille = starrefCommissionPermille
self.starrefPeerId = starrefPeerId
self.starrefAmount = starrefAmount
} }
public static func == (lhs: Transaction, rhs: Transaction) -> Bool { public static func == (lhs: Transaction, rhs: Transaction) -> Bool {
@ -772,6 +781,15 @@ public final class StarsContext {
if lhs.floodskipNumber != rhs.floodskipNumber { if lhs.floodskipNumber != rhs.floodskipNumber {
return false return false
} }
if lhs.starrefCommissionPermille != rhs.starrefCommissionPermille {
return false
}
if lhs.starrefPeerId != rhs.starrefPeerId {
return false
}
if lhs.starrefAmount != rhs.starrefAmount {
return false
}
return true return true
} }
} }

View File

@ -364,9 +364,8 @@ If you end your affiliate program:
programDuration: bot.durationMonths, programDuration: bot.durationMonths,
mode: .active(JoinAffiliateProgramScreenMode.Active( mode: .active(JoinAffiliateProgramScreenMode.Active(
targetPeer: targetPeer, targetPeer: targetPeer,
link: bot.url, bot: bot,
userCount: Int(bot.participants), copyLink: { [weak self] bot in
copyLink: { [weak self] in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
} }

View File

@ -119,10 +119,14 @@ private final class JoinAffiliateProgramScreenComponent: Component {
private var topOffsetDistance: CGFloat? private var topOffsetDistance: CGFloat?
private var currentTargetPeer: EnginePeer? private var currentTargetPeer: EnginePeer?
private var currentMode: JoinAffiliateProgramScreen.Mode?
private var possibleTargetPeers: [EnginePeer] = [] private var possibleTargetPeers: [EnginePeer] = []
private var possibleTargetPeersDisposable: Disposable? private var possibleTargetPeersDisposable: Disposable?
private var changeTargetPeerDisposable: Disposable?
private var isChangingTargetPeer: Bool = false
private var cachedCloseImage: UIImage? private var cachedCloseImage: UIImage?
override init(frame: CGRect) { override init(frame: CGRect) {
@ -194,6 +198,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
deinit { deinit {
self.possibleTargetPeersDisposable?.dispose() self.possibleTargetPeersDisposable?.dispose()
self.changeTargetPeerDisposable?.dispose()
} }
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
@ -337,7 +342,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
guard let component = self.component, let environment = self.environment, let controller = environment.controller() else { guard let component = self.component, let environment = self.environment, let controller = environment.controller() else {
return return
} }
guard case let .join(join) = component.mode else { guard let currentTargetPeer = self.currentTargetPeer else {
return return
} }
@ -346,7 +351,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
let peers: [EnginePeer] = self.possibleTargetPeers.isEmpty ? [ let peers: [EnginePeer] = self.possibleTargetPeers.isEmpty ? [
join.initialTargetPeer currentTargetPeer
] : self.possibleTargetPeers ] : self.possibleTargetPeers
let avatarSize = CGSize(width: 30.0, height: 30.0) let avatarSize = CGSize(width: 30.0, height: 30.0)
@ -360,14 +365,80 @@ private final class JoinAffiliateProgramScreenComponent: Component {
} else { } else {
peerLabel = "bot" peerLabel = "bot"
} }
items.append(.action(ContextMenuActionItem(text: peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: .secondLineWithValue(peerLabel), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: component.context.account, peer: peer, size: avatarSize)), action: { [weak self] c, _ in let isSelected = peer.id == self.currentTargetPeer?.id
let accentColor = environment.theme.list.itemAccentColor
let avatarSignal = peerAvatarCompleteImage(account: component.context.account, peer: peer, size: avatarSize)
|> map { image in
let context = DrawingContext(size: avatarSize, scale: 0.0, clear: true)
context?.withContext { c in
UIGraphicsPushContext(c)
defer {
UIGraphicsPopContext()
}
if isSelected {
}
c.saveGState()
let scaleFactor = (avatarSize.width - 3.0 * 2.0) / avatarSize.width
if isSelected {
c.translateBy(x: avatarSize.width * 0.5, y: avatarSize.height * 0.5)
c.scaleBy(x: scaleFactor, y: scaleFactor)
c.translateBy(x: -avatarSize.width * 0.5, y: -avatarSize.height * 0.5)
}
if let image {
image.draw(in: CGRect(origin: CGPoint(), size: avatarSize))
}
c.restoreGState()
if isSelected {
c.setStrokeColor(accentColor.cgColor)
let lineWidth: CGFloat = 1.0 + UIScreenPixel
c.setLineWidth(lineWidth)
c.strokeEllipse(in: CGRect(origin: CGPoint(), size: avatarSize).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5))
}
}
return context?.generateImage()
}
items.append(.action(ContextMenuActionItem(text: peer.displayTitle(strings: environment.strings, displayOrder: presentationData.nameDisplayOrder), textLayout: .secondLineWithValue(peerLabel), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { [weak self] c, _ in
c?.dismiss(completion: {}) c?.dismiss(completion: {})
guard let self else { guard let self, let currentMode = self.currentMode, let component = self.component else {
return
}
if self.currentTargetPeer?.id == peer.id {
return return
} }
self.currentTargetPeer = peer self.currentTargetPeer = peer
switch currentMode {
case .join:
self.currentTargetPeer = peer
case let .active(active):
self.isChangingTargetPeer = true
self.changeTargetPeerDisposable?.dispose()
self.changeTargetPeerDisposable = (component.context.engine.peers.connectStarRefBot(id: peer.id, botId: component.sourcePeer.id)
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
guard let self else {
return
}
self.isChangingTargetPeer = false
self.currentMode = .active(JoinAffiliateProgramScreen.Mode.Active(
targetPeer: peer,
bot: result,
copyLink: active.copyLink
))
self.state?.updated(transition: .immediate)
}, error: { [weak self] _ in
guard let self else {
return
}
self.isChangingTargetPeer = false
self.state?.updated(transition: .immediate)
})
}
self.state?.updated(transition: .immediate) self.state?.updated(transition: .immediate)
}))) })))
} }
@ -389,7 +460,11 @@ private final class JoinAffiliateProgramScreenComponent: Component {
let sideInset: CGFloat = 16.0 + environment.safeInsets.left let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let currentMode = self.currentMode ?? component.mode
if self.component == nil { if self.component == nil {
self.currentMode = component.mode
var loadPossibleTargetPeers = false var loadPossibleTargetPeers = false
switch component.mode { switch component.mode {
case let .join(join): case let .join(join):
@ -461,7 +536,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
let clippingY: CGFloat let clippingY: CGFloat
if let currentTargetPeer = self.currentTargetPeer, case .join = component.mode { if let currentTargetPeer = self.currentTargetPeer, case .join = currentMode {
contentHeight += 34.0 contentHeight += 34.0
let sourceAvatarSize = self.sourceAvatar.update( let sourceAvatarSize = self.sourceAvatar.update(
@ -592,7 +667,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
transition.setFrame(view: linkIconView, frame: linkIconFrame) transition.setFrame(view: linkIconView, frame: linkIconFrame)
} }
if active.userCount != 0 { if active.bot.participants != 0 {
let linkIconBadgeSize = self.linkIconBadge.update( let linkIconBadgeSize = self.linkIconBadge.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(BorderedBadgeComponent( component: AnyComponent(BorderedBadgeComponent(
@ -605,7 +680,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
scaleFactor: 1.0 scaleFactor: 1.0
))), ))),
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "\(active.userCount)", font: Font.bold(14.0), textColor: .white)) text: .plain(NSAttributedString(string: "\(active.bot.participants)", font: Font.bold(14.0), textColor: .white))
))) )))
], spacing: 4.0)), ], spacing: 4.0)),
insets: UIEdgeInsets(top: 4.0, left: 9.0, bottom: 4.0, right: 8.0), insets: UIEdgeInsets(top: 4.0, left: 9.0, bottom: 4.0, right: 8.0),
@ -637,7 +712,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
let titleString: String let titleString: String
let subtitleString: String let subtitleString: String
let termsString: String let termsString: String
switch component.mode { switch currentMode {
case .join: case .join:
titleString = "Affiliate Program" titleString = "Affiliate Program"
subtitleString = "**\(component.sourcePeer.compactDisplayTitle)** will share **\(commissionTitle)** of the revenue from each user you refer to it for **\(durationTitle)**." subtitleString = "**\(component.sourcePeer.compactDisplayTitle)** will share **\(commissionTitle)** of the revenue from each user you refer to it for **\(durationTitle)**."
@ -651,12 +726,12 @@ private final class JoinAffiliateProgramScreenComponent: Component {
timeString = "for **\(durationTitle)** after they follow your link." timeString = "for **\(durationTitle)** after they follow your link."
} }
subtitleString = "Share this link with your users to earn a **\(commissionTitle)** commission on their spending in **\(component.sourcePeer.compactDisplayTitle)** \(timeString)." subtitleString = "Share this link with your users to earn a **\(commissionTitle)** commission on their spending in **\(component.sourcePeer.compactDisplayTitle)** \(timeString)."
if active.userCount == 0 { if active.bot.participants == 0 {
termsString = "No one opened \(component.sourcePeer.compactDisplayTitle) through this link yet." termsString = "No one opened \(component.sourcePeer.compactDisplayTitle) through this link yet."
} else if active.userCount == 1 { } else if active.bot.participants == 1 {
termsString = "1 user opened \(component.sourcePeer.compactDisplayTitle) through this link." termsString = "1 user opened \(component.sourcePeer.compactDisplayTitle) through this link."
} else { } else {
termsString = "\(active.userCount) users opened \(component.sourcePeer.compactDisplayTitle) through this link." termsString = "\(active.bot.participants) users opened \(component.sourcePeer.compactDisplayTitle) through this link."
} }
} }
let titleSize = self.title.update( let titleSize = self.title.update(
@ -777,8 +852,8 @@ private final class JoinAffiliateProgramScreenComponent: Component {
} }
contentHeight += 12.0 contentHeight += 12.0
if case let .active(active) = component.mode { if case let .active(active) = currentMode {
var cleanLink = active.link var cleanLink = active.bot.url
let removePrefixes: [String] = ["http://", "https://"] let removePrefixes: [String] = ["http://", "https://"]
for prefix in removePrefixes { for prefix in removePrefixes {
if cleanLink.hasPrefix(prefix) { if cleanLink.hasPrefix(prefix) {
@ -805,7 +880,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
return return
} }
self.environment?.controller()?.dismiss() self.environment?.controller()?.dismiss()
active.copyLink() active.copyLink(active.bot)
}, },
animateAlpha: true, animateAlpha: true,
animateScale: false, animateScale: false,
@ -826,7 +901,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
} }
let actionButtonTitle: String let actionButtonTitle: String
switch component.mode { switch currentMode {
case .join: case .join:
actionButtonTitle = "Join Program" actionButtonTitle = "Join Program"
case .active: case .active:
@ -864,7 +939,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
join.completion(currentTargetPeer) join.completion(currentTargetPeer)
} }
case let .active(active): case let .active(active):
active.copyLink() active.copyLink(active.bot)
} }
} }
)), )),
@ -928,7 +1003,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset) self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset)
if case .active = component.mode { if case .active = currentMode {
let toast: ComponentView<Empty> let toast: ComponentView<Empty>
if let current = self.toast { if let current = self.toast {
toast = current toast = current

View File

@ -8620,16 +8620,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
if let result { if let result {
mode = .active(JoinAffiliateProgramScreenMode.Active( mode = .active(JoinAffiliateProgramScreenMode.Active(
targetPeer: accountPeer, targetPeer: accountPeer,
link: result.url, bot: result,
userCount: Int(result.participants), copyLink: { [weak self] result in
copyLink: { [weak self] in
guard let self else { guard let self else {
return return
} }
//TODO:localize //TODO:localize
UIPasteboard.general.string = result.url UIPasteboard.general.string = result.url
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: "Link copied to clipboard", text: "Share this link and earn **\(result.commissionPermille / 10)%** of what people who use it spend in **\(EnginePeer.user(peer).compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: "Link copied to clipboard", text: "Share this link and earn **\(result.commissionPermille / 10)%** of what people who use it spend in **\(result.peer.compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
} }
)) ))
} else { } else {
@ -8654,15 +8653,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
programDuration: bot.durationMonths, programDuration: bot.durationMonths,
mode: .active(JoinAffiliateProgramScreenMode.Active( mode: .active(JoinAffiliateProgramScreenMode.Active(
targetPeer: targetPeer, targetPeer: targetPeer,
link: bot.url, bot: bot,
userCount: Int(bot.participants), copyLink: { [weak self] result in
copyLink: { [weak self] in
guard let self else { guard let self else {
return return
} }
UIPasteboard.general.string = bot.url UIPasteboard.general.string = result.url
let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 }) let presentationData = self.context.sharedContext.currentPresentationData.with({ $0 })
self.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) self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: "Link copied to clipboard", text: "Share this link and earn **\(result.commissionPermille / 10)%** of what people who use it spend in **\(result.peer.compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
} }
)) ))
)) ))

View File

@ -33,7 +33,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let context: AccountContext let context: AccountContext
let subject: StarsTransactionScreen.Subject let subject: StarsTransactionScreen.Subject
let cancel: (Bool) -> Void let cancel: (Bool) -> Void
let openPeer: (EnginePeer) -> Void let openPeer: (EnginePeer, Bool) -> Void
let openMessage: (EngineMessage.Id) -> Void let openMessage: (EngineMessage.Id) -> Void
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
let openAppExamples: () -> Void let openAppExamples: () -> Void
@ -44,7 +44,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
context: AccountContext, context: AccountContext,
subject: StarsTransactionScreen.Subject, subject: StarsTransactionScreen.Subject,
cancel: @escaping (Bool) -> Void, cancel: @escaping (Bool) -> Void,
openPeer: @escaping (EnginePeer) -> Void, openPeer: @escaping (EnginePeer, Bool) -> Void,
openMessage: @escaping (EngineMessage.Id) -> Void, openMessage: @escaping (EngineMessage.Id) -> Void,
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void, openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
openAppExamples: @escaping () -> Void, openAppExamples: @escaping () -> Void,
@ -95,6 +95,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
if case let .peer(peer) = transaction.peer { if case let .peer(peer) = transaction.peer {
peerIds.append(peer.id) peerIds.append(peer.id)
} }
if let starrefPeerId = transaction.starrefPeerId {
peerIds.append(starrefPeerId)
}
case let .receipt(receipt): case let .receipt(receipt):
peerIds.append(receipt.botPaymentId) peerIds.append(receipt.botPaymentId)
case let .gift(message): case let .gift(message):
@ -232,6 +235,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
var giveawayMessageId: MessageId? var giveawayMessageId: MessageId?
var isBoost = false var isBoost = false
var giftAnimation: TelegramMediaFile? var giftAnimation: TelegramMediaFile?
var isRefProgram = false
var delayedCloseOnOpenPeer = true var delayedCloseOnOpenPeer = true
switch subject { switch subject {
@ -399,6 +403,23 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} }
transactionPeer = transaction.peer transactionPeer = transaction.peer
isGift = true isGift = true
} else if let starrefCommissionPermille = transaction.starrefCommissionPermille {
//TODO:localize
isRefProgram = true
if transaction.starrefPeerId == nil {
titleText = "\(starrefCommissionPermille / 10)% Commission"
} else {
titleText = transaction.title ?? "Product"
}
descriptionText = ""
count = transaction.count
countOnTop = false
transactionId = transaction.id
date = transaction.date
transactionPeer = transaction.peer
if case let .peer(peer) = transaction.peer {
toPeer = peer
}
} else if transaction.flags.contains(.isReaction) { } else if transaction.flags.contains(.isReaction) {
titleText = strings.Stars_Transaction_Reaction_Title titleText = strings.Stars_Transaction_Reaction_Title
descriptionText = "" descriptionText = ""
@ -722,7 +743,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
) )
) )
)) ))
} else if let toPeer { } else if let toPeer, !isRefProgram {
let title: String let title: String
if isSubscription { if isSubscription {
if isBotSubscription { if isBotSubscription {
@ -751,7 +772,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
), ),
action: { action: {
if delayedCloseOnOpenPeer { if delayedCloseOnOpenPeer {
component.openPeer(toPeer) component.openPeer(toPeer, false)
Queue.mainQueue().after(1.0, { Queue.mainQueue().after(1.0, {
component.cancel(false) component.cancel(false)
}) })
@ -846,6 +867,133 @@ private final class StarsTransactionSheetContent: CombinedComponent {
)) ))
} }
if case let .transaction(transaction, _) = subject {
//TODO:localize
if transaction.starrefCommissionPermille != nil {
if transaction.starrefPeerId == nil {
tableItems.append(.init(
id: "reason",
title: "Reason",
component: AnyComponent(
Button(
content: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Affiliate Program", font: tableFont, textColor: tableLinkColor))
)),
action: {
if let toPeer {
component.openPeer(toPeer, true)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
}
)
),
insets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 5.0)
))
}
if let toPeer, transaction.starrefPeerId == nil {
tableItems.append(.init(
id: "miniapp",
title: "Mini App",
component: AnyComponent(
Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
peer: toPeer
)
),
action: {
if delayedCloseOnOpenPeer {
component.openPeer(toPeer, false)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
} else {
if let controller = controller() as? StarsTransactionScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController {
chatController.playShakeAnimation()
}
component.cancel(true)
}
}
)
)
))
}
}
if let starRefPeerId = transaction.starrefPeerId, let starRefPeer = state.peerMap[starRefPeerId] {
//TODO:localize
tableItems.append(.init(
id: "to",
title: "Affiliate",
component: AnyComponent(
Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
peer: starRefPeer
)
),
action: {
if delayedCloseOnOpenPeer {
component.openPeer(starRefPeer, false)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
} else {
if let controller = controller() as? StarsTransactionScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController {
chatController.playShakeAnimation()
}
component.cancel(true)
}
}
)
)
))
if let toPeer {
tableItems.append(.init(
id: "referred",
title: "Referred User",
component: AnyComponent(
Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
peer: toPeer
)
),
action: {
if delayedCloseOnOpenPeer {
component.openPeer(toPeer, true)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
} else {
if let controller = controller() as? StarsTransactionScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController {
chatController.playShakeAnimation()
}
component.cancel(true)
}
}
)
)
))
}
}
if let starrefCommissionPermille = transaction.starrefCommissionPermille, transaction.starrefPeerId != nil {
tableItems.append(.init(
id: "commission",
title: "Commission",
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "\(starrefCommissionPermille / 10)%", font: tableFont, textColor: tableTextColor))
)),
insets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 5.0)
))
}
}
if let transactionId { if let transactionId {
tableItems.append(.init( tableItems.append(.init(
id: "transaction", id: "transaction",
@ -1200,7 +1348,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
let context: AccountContext let context: AccountContext
let subject: StarsTransactionScreen.Subject let subject: StarsTransactionScreen.Subject
let openPeer: (EnginePeer) -> Void let openPeer: (EnginePeer, Bool) -> Void
let openMessage: (EngineMessage.Id) -> Void let openMessage: (EngineMessage.Id) -> Void
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
let openAppExamples: () -> Void let openAppExamples: () -> Void
@ -1210,7 +1358,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
init( init(
context: AccountContext, context: AccountContext,
subject: StarsTransactionScreen.Subject, subject: StarsTransactionScreen.Subject,
openPeer: @escaping (EnginePeer) -> Void, openPeer: @escaping (EnginePeer, Bool) -> Void,
openMessage: @escaping (EngineMessage.Id) -> Void, openMessage: @escaping (EngineMessage.Id) -> Void,
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void, openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
openAppExamples: @escaping () -> Void, openAppExamples: @escaping () -> Void,
@ -1363,7 +1511,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
) { ) {
self.context = context self.context = context
var openPeerImpl: ((EnginePeer) -> Void)? var openPeerImpl: ((EnginePeer, Bool) -> Void)?
var openMessageImpl: ((EngineMessage.Id) -> Void)? var openMessageImpl: ((EngineMessage.Id) -> Void)?
var openMediaImpl: (([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? var openMediaImpl: (([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)?
var openAppExamplesImpl: (() -> Void)? var openAppExamplesImpl: (() -> Void)?
@ -1375,8 +1523,8 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
component: StarsTransactionSheetComponent( component: StarsTransactionSheetComponent(
context: context, context: context,
subject: subject, subject: subject,
openPeer: { peerId in openPeer: { peerId, isProfile in
openPeerImpl?(peerId) openPeerImpl?(peerId, isProfile)
}, },
openMessage: { messageId in openMessage: { messageId in
openMessageImpl?(messageId) openMessageImpl?(messageId)
@ -1402,7 +1550,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
self.navigationPresentation = .flatModal self.navigationPresentation = .flatModal
self.automaticallyControlPresentationContextLayout = false self.automaticallyControlPresentationContextLayout = false
openPeerImpl = { [weak self] peer in openPeerImpl = { [weak self] peer, isProfile in
guard let self, let navigationController = self.navigationController as? NavigationController else { guard let self, let navigationController = self.navigationController as? NavigationController else {
return return
} }
@ -1415,7 +1563,13 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
guard let peer else { guard let peer else {
return return
} }
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: true, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true)) if isProfile {
if let controller = context.sharedContext.makePeerInfoController(context: context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
navigationController.pushViewController(controller)
}
} else {
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, chatController: nil, context: context, chatLocation: .peer(peer), subject: nil, botStart: nil, updateTextInputState: nil, keepStack: .always, useExisting: true, purposefulAction: nil, scrollToEndIfExists: false, activateMessageSearch: nil, animated: true))
}
}) })
} }