mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branches 'master' and 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
3778357baa
20 changed files with 818 additions and 216 deletions
|
|
@ -896,14 +896,12 @@ public enum JoinAffiliateProgramScreenMode {
|
|||
|
||||
public final class Active {
|
||||
public let targetPeer: EnginePeer
|
||||
public let link: String
|
||||
public let userCount: Int
|
||||
public let copyLink: () -> Void
|
||||
public let bot: TelegramConnectedStarRefBotList.Item
|
||||
public let copyLink: (TelegramConnectedStarRefBotList.Item) -> 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.link = link
|
||||
self.userCount = userCount
|
||||
self.bot = bot
|
||||
self.copyLink = copyLink
|
||||
}
|
||||
}
|
||||
|
|
@ -1106,7 +1104,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal<AffiliateProgramSetupScreenInitialData, NoError>
|
||||
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
|
||||
|
||||
func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, mode: JoinAffiliateProgramScreenMode) -> ViewController
|
||||
func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, revenuePerUser: Double, mode: JoinAffiliateProgramScreenMode) -> ViewController
|
||||
|
||||
func makeDebugSettingsController(context: AccountContext?) -> ViewController?
|
||||
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: P
|
|||
if let cutoutRect {
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
context.fillEllipse(in: cutoutRect)
|
||||
context.fillEllipse(in: cutoutRect.offsetBy(dx: 0.0, dy: size.height - cutoutRect.maxY - cutoutRect.height))
|
||||
}
|
||||
})
|
||||
let unroundedImage: UIImage?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public final class TransformContents<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
public typealias EnvironmentType = ChildEnvironment
|
||||
|
||||
private let content: AnyComponent<ChildEnvironment>
|
||||
private let fixedSize: CGSize?
|
||||
private let translation: CGPoint
|
||||
|
||||
public init(content: AnyComponent<ChildEnvironment>, fixedSize: CGSize? = nil, translation: CGPoint) {
|
||||
self.content = content
|
||||
self.fixedSize = fixedSize
|
||||
self.translation = translation
|
||||
}
|
||||
|
||||
public static func ==(lhs: TransformContents<ChildEnvironment>, rhs: TransformContents<ChildEnvironment>) -> Bool {
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
if lhs.fixedSize != rhs.fixedSize {
|
||||
return false
|
||||
}
|
||||
if lhs.translation != rhs.translation {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public static var body: Body {
|
||||
let child = Child(environment: ChildEnvironment.self)
|
||||
|
||||
return { context in
|
||||
let child = child.update(
|
||||
component: context.component.content,
|
||||
environment: { context.environment[ChildEnvironment.self] },
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let size = context.component.fixedSize ?? child.size
|
||||
|
||||
var childFrame = child.size.centered(in: CGRect(origin: CGPoint(), size: size))
|
||||
childFrame.origin.x += context.component.translation.x
|
||||
childFrame.origin.y += context.component.translation.y
|
||||
|
||||
context.add(child
|
||||
.position(childFrame.center)
|
||||
)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2529,7 +2529,16 @@ open class TextNode: ASDisplayNode, TextNodeProtocol {
|
|||
textColor = color
|
||||
}
|
||||
}
|
||||
if let textColor {
|
||||
if image.renderingMode == .alwaysOriginal {
|
||||
let imageRect = CGRect(origin: CGPoint(x: attachment.frame.midX - image.size.width * 0.5, y: attachment.frame.midY - image.size.height * 0.5 + 1.0), size: image.size).offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||
context.draw(image.cgImage!, in: imageRect)
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -imageRect.midX, y: -imageRect.midY)
|
||||
} else if let textColor {
|
||||
if let tintedImage = generateTintedImage(image: image, color: textColor) {
|
||||
let imageRect = CGRect(origin: CGPoint(x: attachment.frame.midX - tintedImage.size.width * 0.5, y: attachment.frame.midY - tintedImage.size.height * 0.5 + 1.0), size: tintedImage.size).offsetBy(dx: lineFrame.minX, dy: lineFrame.minY)
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
|
|
|
|||
|
|
@ -1575,7 +1575,8 @@ private func monetizationEntries(
|
|||
premiumConfiguration: PremiumConfiguration,
|
||||
monetizationConfiguration: MonetizationConfiguration,
|
||||
canViewRevenue: Bool,
|
||||
canViewStarsRevenue: Bool
|
||||
canViewStarsRevenue: Bool,
|
||||
canJoinRefPrograms: Bool
|
||||
) -> [StatsEntry] {
|
||||
var entries: [StatsEntry] = []
|
||||
|
||||
|
|
@ -1700,8 +1701,10 @@ private func monetizationEntries(
|
|||
|
||||
if displayStarsTransactions {
|
||||
if !addedTransactionsTabs {
|
||||
//TODO:localize
|
||||
entries.append(.earnStarsInfo)
|
||||
if canJoinRefPrograms {
|
||||
//TODO:localize
|
||||
entries.append(.earnStarsInfo)
|
||||
}
|
||||
|
||||
entries.append(.adsTransactionsTitle(presentationData.theme, presentationData.strings.Monetization_StarsTransactions.uppercased()))
|
||||
}
|
||||
|
|
@ -1767,7 +1770,8 @@ private func channelStatsControllerEntries(
|
|||
premiumConfiguration: PremiumConfiguration,
|
||||
monetizationConfiguration: MonetizationConfiguration,
|
||||
canViewRevenue: Bool,
|
||||
canViewStarsRevenue: Bool
|
||||
canViewStarsRevenue: Bool,
|
||||
canJoinRefPrograms: Bool
|
||||
) -> [StatsEntry] {
|
||||
switch state.section {
|
||||
case .stats:
|
||||
|
|
@ -1809,7 +1813,8 @@ private func channelStatsControllerEntries(
|
|||
premiumConfiguration: premiumConfiguration,
|
||||
monetizationConfiguration: monetizationConfiguration,
|
||||
canViewRevenue: canViewRevenue,
|
||||
canViewStarsRevenue: canViewStarsRevenue
|
||||
canViewStarsRevenue: canViewStarsRevenue,
|
||||
canJoinRefPrograms: canJoinRefPrograms
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2157,6 +2162,15 @@ public func channelStatsController(
|
|||
let (canViewStats, adsRestricted, _, canViewStarsRevenue) = peerData
|
||||
var canViewRevenue = peerData.2
|
||||
|
||||
var canJoinRefPrograms = false
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] {
|
||||
if let value = value as? Double {
|
||||
canJoinRefPrograms = value != 0.0
|
||||
} else if let value = value as? Bool {
|
||||
canJoinRefPrograms = value
|
||||
}
|
||||
}
|
||||
|
||||
let _ = canViewStatsValue.swap(canViewStats)
|
||||
|
||||
var isGroup = false
|
||||
|
|
@ -2262,7 +2276,7 @@ public func channelStatsController(
|
|||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(presentationData: presentationData, state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable, isGroup: isGroup, boostsOnly: boostsOnly, revenueState: revenueState?.stats, revenueTransactions: revenueTransactions, starsState: starsState?.stats, starsTransactions: starsTransactions, adsRestricted: adsRestricted, premiumConfiguration: premiumConfiguration, monetizationConfiguration: monetizationConfiguration, canViewRevenue: canViewRevenue, canViewStarsRevenue: canViewStarsRevenue), style: .blocks, emptyStateItem: emptyStateItem, headerItem: headerItem, crossfadeState: previous == nil, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(presentationData: presentationData, state: state, peer: peer, data: data, messages: messages, stories: stories, interactions: interactions, boostData: boostData, boostersState: boostersState, giftsState: giftsState, giveawayAvailable: premiumConfiguration.giveawayGiftsPurchaseAvailable, isGroup: isGroup, boostsOnly: boostsOnly, revenueState: revenueState?.stats, revenueTransactions: revenueTransactions, starsState: starsState?.stats, starsTransactions: starsTransactions, adsRestricted: adsRestricted, premiumConfiguration: premiumConfiguration, monetizationConfiguration: monetizationConfiguration, canViewRevenue: canViewRevenue, canViewStarsRevenue: canViewStarsRevenue, canJoinRefPrograms: canJoinRefPrograms), style: .blocks, emptyStateItem: emptyStateItem, headerItem: headerItem, crossfadeState: previous == nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -589,6 +589,18 @@ func _internal_removeChatManagingBot(account: Account, chatId: EnginePeer.Id) ->
|
|||
}
|
||||
}
|
||||
|
||||
public func formatPermille(_ value: Int32) -> String {
|
||||
return formatPermille(Int(value))
|
||||
}
|
||||
|
||||
public func formatPermille(_ value: Int) -> String {
|
||||
if value % 10 == 0 {
|
||||
return "\(value / 10)"
|
||||
} else {
|
||||
return String(format: "%.1f", Double(value) / 10.0)
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_updateStarRefProgram(account: Account, id: EnginePeer.Id, program: (commissionPermille: Int32, durationMonths: Int32?)?) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputUser? in
|
||||
return transaction.getPeer(id).flatMap(apiInputUser)
|
||||
|
|
@ -632,7 +644,6 @@ func _internal_updateStarRefProgram(account: Account, id: EnginePeer.Id, program
|
|||
}
|
||||
|
||||
public final class TelegramConnectedStarRefBotList : Equatable {
|
||||
|
||||
public final class Item: Equatable {
|
||||
public let peer: EnginePeer
|
||||
public let url: String
|
||||
|
|
|
|||
|
|
@ -281,11 +281,15 @@ public struct StarsAmount: Equatable, Comparable, Hashable, Codable, CustomStrin
|
|||
}
|
||||
|
||||
public var stringValue: String {
|
||||
return "\(totalValue)"
|
||||
}
|
||||
|
||||
public var totalValue: Double {
|
||||
if self.nanos == 0 {
|
||||
return "\(self.value)"
|
||||
return Double(self.value)
|
||||
} else {
|
||||
let totalValue = (Double(self.value) * 1e9 + Double(self.nanos)) / 1e9
|
||||
return "\(totalValue)"
|
||||
return totalValue
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,6 +306,10 @@ public struct StarsAmount: Equatable, Comparable, Hashable, Codable, CustomStrin
|
|||
}
|
||||
|
||||
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 overflow = totalNanos / 1_000_000_000
|
||||
let remainingNanos = totalNanos % 1_000_000_000
|
||||
|
|
@ -529,7 +537,7 @@ private final class StarsContextImpl {
|
|||
}
|
||||
var transactions = state.transactions
|
||||
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))
|
||||
|
|
@ -552,10 +560,6 @@ private extension StarsContext.State.Transaction {
|
|||
init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) {
|
||||
switch apiTransaction {
|
||||
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?
|
||||
|
|
@ -611,7 +615,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: 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 +690,9 @@ public final class StarsContext {
|
|||
public let subscriptionPeriod: Int32?
|
||||
public let starGift: StarGift?
|
||||
public let floodskipNumber: Int32?
|
||||
public let starrefCommissionPermille: Int32?
|
||||
public let starrefPeerId: PeerId?
|
||||
public let starrefAmount: StarsAmount?
|
||||
|
||||
public init(
|
||||
flags: Flags,
|
||||
|
|
@ -703,7 +710,10 @@ public final class StarsContext {
|
|||
media: [Media],
|
||||
subscriptionPeriod: Int32?,
|
||||
starGift: StarGift?,
|
||||
floodskipNumber: Int32?
|
||||
floodskipNumber: Int32?,
|
||||
starrefCommissionPermille: Int32?,
|
||||
starrefPeerId: PeerId?,
|
||||
starrefAmount: StarsAmount?
|
||||
) {
|
||||
self.flags = flags
|
||||
self.id = id
|
||||
|
|
@ -721,6 +731,9 @@ public final class StarsContext {
|
|||
self.subscriptionPeriod = subscriptionPeriod
|
||||
self.starGift = starGift
|
||||
self.floodskipNumber = floodskipNumber
|
||||
self.starrefCommissionPermille = starrefCommissionPermille
|
||||
self.starrefPeerId = starrefPeerId
|
||||
self.starrefAmount = starrefAmount
|
||||
}
|
||||
|
||||
public static func == (lhs: Transaction, rhs: Transaction) -> Bool {
|
||||
|
|
@ -772,6 +785,15 @@ public final class StarsContext {
|
|||
if lhs.floodskipNumber != rhs.floodskipNumber {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
|
|||
iconImage = incoming ? graphics.chatBubbleActionButtonIncomingMessageIconImage : graphics.chatBubbleActionButtonOutgoingMessageIconImage
|
||||
case let .url(value):
|
||||
var isApp = false
|
||||
if isTelegramMeLink(value), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: value) {
|
||||
if isTelegramMeLink(value), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, context: context, url: value) {
|
||||
if case .peer(_, .appStart) = internalUrl {
|
||||
isApp = true
|
||||
} else if case .peer(_, .attachBotStart) = internalUrl {
|
||||
|
|
|
|||
|
|
@ -798,7 +798,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
|
||||
var useInlineHLS = true
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data {
|
||||
if let value = data["ios_inline_hls"] as? Double {
|
||||
if let value = data["ios_inline_hls_v2"] as? Double {
|
||||
useInlineHLS = value != 0.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,12 @@ final class AffiliateProgramSetupScreenComponent: Component {
|
|||
return true
|
||||
}
|
||||
|
||||
private class ScrollView: UIScrollView {
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let scrollView: UIScrollView
|
||||
|
||||
|
|
@ -115,11 +121,11 @@ final class AffiliateProgramSetupScreenComponent: Component {
|
|||
|
||||
private var suggestedStarBotList: TelegramSuggestedStarRefBotList?
|
||||
private var suggestedStarBotListDisposable: Disposable?
|
||||
private var suggestedSortMode: TelegramSuggestedStarRefBotList.SortMode = .date
|
||||
private var suggestedSortMode: TelegramSuggestedStarRefBotList.SortMode = .profitability
|
||||
private var isSuggestedSortModeUpdating: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollView = UIScrollView()
|
||||
self.scrollView = ScrollView()
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
|
|
@ -170,10 +176,10 @@ final class AffiliateProgramSetupScreenComponent: Component {
|
|||
let programPermille: Int32 = Int32(self.commissionPermille)
|
||||
let programDuration: Int32? = self.durationValue == Int(Int32.max) ? nil : Int32(self.durationValue)
|
||||
|
||||
let commissionTitle: String = "\(programPermille / 10)%"
|
||||
let commissionTitle: String = "\(formatPermille(programPermille))%"
|
||||
let durationTitle: String
|
||||
if let durationMonths = programDuration {
|
||||
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (24 * 60 * 60))
|
||||
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (30 * 24 * 60 * 60))
|
||||
} else {
|
||||
durationTitle = "Lifetime"
|
||||
}
|
||||
|
|
@ -362,17 +368,17 @@ If you end your affiliate program:
|
|||
sourcePeer: bot.peer,
|
||||
commissionPermille: bot.commissionPermille,
|
||||
programDuration: bot.durationMonths,
|
||||
revenuePerUser: bot.participants == 0 ? 0.0 : Double(bot.revenue) / Double(bot.participants),
|
||||
mode: .active(JoinAffiliateProgramScreenMode.Active(
|
||||
targetPeer: targetPeer,
|
||||
link: bot.url,
|
||||
userCount: Int(bot.participants),
|
||||
copyLink: { [weak self] in
|
||||
bot: bot,
|
||||
copyLink: { [weak self] bot 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)
|
||||
self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: "Link copied to clipboard", text: "Share this link and earn **\(formatPermille(bot.commissionPermille))%** of what people who use it spend in **\(bot.peer.compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
))
|
||||
))
|
||||
|
|
@ -411,9 +417,9 @@ If you end your affiliate program:
|
|||
var items: [ContextMenuItem] = []
|
||||
|
||||
let availableModes: [(TelegramSuggestedStarRefBotList.SortMode, String)] = [
|
||||
(.date, "Date"),
|
||||
(.profitability, "Profitability"),
|
||||
(.revenue, "Revenue"),
|
||||
(.profitability, "Profitability")
|
||||
(.date, "Date")
|
||||
]
|
||||
for (mode, title) in availableModes {
|
||||
let isSelected = mode == self.suggestedSortMode
|
||||
|
|
@ -457,6 +463,20 @@ If you end your affiliate program:
|
|||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
private func openExistingAffiliatePrograms() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
let _ = (component.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: component.context, peerId: component.initialContent.peerId, mode: .connectedPrograms)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let setupScreen = component.context.sharedContext.makeAffiliateProgramSetupScreen(context: component.context, initialData: initialData)
|
||||
self.environment?.controller()?.push(setupScreen)
|
||||
})
|
||||
}
|
||||
|
||||
func update(component: AffiliateProgramSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
|
|
@ -537,13 +557,15 @@ If you end your affiliate program:
|
|||
self.commissionPermille = 10
|
||||
self.commissionSliderValue = 0.0
|
||||
self.commissionMinPermille = 10
|
||||
self.durationValue = 10
|
||||
self.durationValue = 1
|
||||
self.durationMinValue = 0
|
||||
}
|
||||
} else {
|
||||
self.commissionPermille = 10
|
||||
self.commissionSliderValue = 0.0
|
||||
self.commissionMinPermille = 10
|
||||
self.durationValue = 10
|
||||
self.durationValue = 1
|
||||
self.durationMinValue = 0
|
||||
}
|
||||
case .connectedPrograms:
|
||||
self.connectedStarBotListDisposable = (component.context.engine.peers.requestConnectedStarRefBots(
|
||||
|
|
@ -855,7 +877,7 @@ If you end your affiliate program:
|
|||
minValue: commissionMinSliderValue,
|
||||
lowerBoundTitle: "1%",
|
||||
upperBoundTitle: "90%",
|
||||
title: "\(self.commissionPermille / 10)%",
|
||||
title: "\(formatPermille(self.commissionPermille))%",
|
||||
valueUpdated: { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -992,8 +1014,7 @@ If you end your affiliate program:
|
|||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = self
|
||||
self.openExistingAffiliatePrograms()
|
||||
}
|
||||
)))
|
||||
],
|
||||
|
|
@ -1164,11 +1185,11 @@ If you end your affiliate program:
|
|||
for item in connectedStarBotList.items {
|
||||
let durationTitle: String
|
||||
if let durationMonths = item.durationMonths {
|
||||
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (24 * 60 * 60))
|
||||
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (30 * 24 * 60 * 60))
|
||||
} else {
|
||||
durationTitle = "Lifetime"
|
||||
}
|
||||
let commissionTitle = "\(item.commissionPermille / 10)%"
|
||||
let commissionTitle = "\(formatPermille(item.commissionPermille))%"
|
||||
|
||||
let itemContextAction: (EnginePeer, ContextExtractedContentContainingView, ContextGesture?) -> Void = { [weak self] peer, sourceView, gesture in
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
|
|
@ -1230,7 +1251,7 @@ If you end your affiliate program:
|
|||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
|
||||
UIPasteboard.general.string = item.url
|
||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: "Link copied to clipboard", text: "Share this link and earn **\(item.commissionPermille / 10)%** of what people who use it spend in **\(item.peer.compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
environment.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: "Link copied to clipboard", text: "Share this link and earn **\(formatPermille(item.commissionPermille))%** of what people who use it spend in **\(item.peer.compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
})))
|
||||
|
||||
itemList.append(.action(ContextMenuActionItem(text: "Leave", textColor: .destructive, icon: { theme in
|
||||
|
|
@ -1344,11 +1365,22 @@ If you end your affiliate program:
|
|||
}
|
||||
do {
|
||||
var suggestedSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
if suggestedStarBotListItems.isEmpty {
|
||||
suggestedSectionItems.append(AnyComponentWithIdentity(id: "empty", component: AnyComponent(ZStack([
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(Rectangle(color: .clear, width: nil, height: 100.0))),
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "No available programs yet.\nPlease check the page later.", font: Font.regular(15.0), textColor: environment.theme.list.itemSecondaryTextColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
)))
|
||||
]))))
|
||||
}
|
||||
for item in suggestedStarBotListItems {
|
||||
let commissionTitle = "\(item.program.commissionPermille / 10)%"
|
||||
let commissionTitle = "\(formatPermille(item.program.commissionPermille))%"
|
||||
let durationTitle: String
|
||||
if let durationMonths = item.program.durationMonths {
|
||||
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (24 * 60 * 60))
|
||||
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (30 * 24 * 60 * 60))
|
||||
} else {
|
||||
durationTitle = "Lifetime"
|
||||
}
|
||||
|
|
@ -1404,6 +1436,7 @@ If you end your affiliate program:
|
|||
sourcePeer: botPeer,
|
||||
commissionPermille: item.program.commissionPermille,
|
||||
programDuration: item.program.durationMonths,
|
||||
revenuePerUser: item.program.dailyRevenuePerUser?.totalValue ?? 0.0,
|
||||
mode: .join(JoinAffiliateProgramScreenMode.Join(
|
||||
initialTargetPeer: targetPeer,
|
||||
canSelectTargetPeer: false,
|
||||
|
|
@ -1440,31 +1473,34 @@ If you end your affiliate program:
|
|||
))))
|
||||
}
|
||||
|
||||
var suggestedHeaderItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
suggestedHeaderItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "PROGRAMS",
|
||||
font: Font.regular(13.0),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
))))
|
||||
if suggestedStarBotListItems.count > 1 {
|
||||
suggestedHeaderItems.append(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)
|
||||
}
|
||||
))))
|
||||
}
|
||||
|
||||
let suggestedProgramsSectionSize = self.suggestedProgramsSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
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)),
|
||||
header: AnyComponent(HStack(suggestedHeaderItems, spacing: 4.0, alignment: .alternatingLeftRight)),
|
||||
footer: nil,
|
||||
items: suggestedSectionItems,
|
||||
displaySeparators: true
|
||||
|
|
@ -1479,19 +1515,12 @@ If you end your affiliate program:
|
|||
self.scrollView.addSubview(suggestedProgramsSectionView)
|
||||
}
|
||||
transition.setFrame(view: suggestedProgramsSectionView, frame: suggestedProgramsSectionFrame)
|
||||
if !suggestedStarBotListItems.isEmpty {
|
||||
suggestedProgramsSectionView.isHidden = false
|
||||
} else {
|
||||
suggestedProgramsSectionView.isHidden = true
|
||||
}
|
||||
|
||||
suggestedProgramsSectionView.contentViewImpl.alpha = self.isSuggestedSortModeUpdating ? 0.6 : 1.0
|
||||
suggestedProgramsSectionView.contentViewImpl.isUserInteractionEnabled = !self.isSuggestedSortModeUpdating
|
||||
}
|
||||
if !suggestedStarBotListItems.isEmpty {
|
||||
contentHeight += suggestedProgramsSectionSize.height
|
||||
contentHeight += sectionSpacing
|
||||
}
|
||||
contentHeight += suggestedProgramsSectionSize.height
|
||||
contentHeight += sectionSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
let sourcePeer: EnginePeer
|
||||
let commissionPermille: Int32
|
||||
let programDuration: Int32?
|
||||
let revenuePerUser: Double
|
||||
let mode: JoinAffiliateProgramScreen.Mode
|
||||
|
||||
init(
|
||||
|
|
@ -35,12 +36,14 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
sourcePeer: EnginePeer,
|
||||
commissionPermille: Int32,
|
||||
programDuration: Int32?,
|
||||
revenuePerUser: Double,
|
||||
mode: JoinAffiliateProgramScreen.Mode
|
||||
) {
|
||||
self.context = context
|
||||
self.sourcePeer = sourcePeer
|
||||
self.commissionPermille = commissionPermille
|
||||
self.programDuration = programDuration
|
||||
self.revenuePerUser = revenuePerUser
|
||||
self.mode = mode
|
||||
}
|
||||
|
||||
|
|
@ -86,16 +89,18 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
private var toast: ComponentView<Empty>?
|
||||
|
||||
private let sourceAvatar = ComponentView<Empty>()
|
||||
private let sourceAvatarBadge = ComponentView<Empty>()
|
||||
private let targetAvatar = ComponentView<Empty>()
|
||||
private let targetAvatarBadge = ComponentView<Empty>()
|
||||
private let sourceTargetArrow = UIImageView()
|
||||
|
||||
private let linkIconBackground = ComponentView<Empty>()
|
||||
private let linkIcon = ComponentView<Empty>()
|
||||
private let linkIconBadge = ComponentView<Empty>()
|
||||
private var linkIconBadge: ComponentView<Empty>?
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
private var dailyRevenueText: ComponentView<Empty>?
|
||||
private let titleTransformContainer: UIView
|
||||
private let bottomPanelContainer: UIView
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
|
|
@ -119,11 +124,16 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
private var topOffsetDistance: CGFloat?
|
||||
|
||||
private var currentTargetPeer: EnginePeer?
|
||||
private var currentMode: JoinAffiliateProgramScreen.Mode?
|
||||
|
||||
private var possibleTargetPeers: [EnginePeer] = []
|
||||
private var possibleTargetPeersDisposable: Disposable?
|
||||
|
||||
private var changeTargetPeerDisposable: Disposable?
|
||||
private var isChangingTargetPeer: Bool = false
|
||||
|
||||
private var cachedCloseImage: UIImage?
|
||||
private var inlineTextStarImage: UIImage?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.bottomOverscrollLimit = 200.0
|
||||
|
|
@ -194,6 +204,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
|
||||
deinit {
|
||||
self.possibleTargetPeersDisposable?.dispose()
|
||||
self.changeTargetPeerDisposable?.dispose()
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
|
@ -337,7 +348,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
guard let component = self.component, let environment = self.environment, let controller = environment.controller() else {
|
||||
return
|
||||
}
|
||||
guard case let .join(join) = component.mode else {
|
||||
guard let currentTargetPeer = self.currentTargetPeer else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +357,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
|
||||
let peers: [EnginePeer] = self.possibleTargetPeers.isEmpty ? [
|
||||
join.initialTargetPeer
|
||||
currentTargetPeer
|
||||
] : self.possibleTargetPeers
|
||||
|
||||
let avatarSize = CGSize(width: 30.0, height: 30.0)
|
||||
|
|
@ -360,14 +371,80 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
} else {
|
||||
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: {})
|
||||
|
||||
guard let self else {
|
||||
guard let self, let currentMode = self.currentMode, let component = self.component else {
|
||||
return
|
||||
}
|
||||
if self.currentTargetPeer?.id == peer.id {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
})))
|
||||
}
|
||||
|
|
@ -389,7 +466,11 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
|
||||
let currentMode = self.currentMode ?? component.mode
|
||||
|
||||
if self.component == nil {
|
||||
self.currentMode = component.mode
|
||||
|
||||
var loadPossibleTargetPeers = false
|
||||
switch component.mode {
|
||||
case let .join(join):
|
||||
|
|
@ -461,7 +542,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
|
||||
let clippingY: CGFloat
|
||||
|
||||
if let currentTargetPeer = self.currentTargetPeer, case .join = component.mode {
|
||||
if let currentTargetPeer = self.currentTargetPeer, case .join = currentMode {
|
||||
contentHeight += 34.0
|
||||
|
||||
let sourceAvatarSize = self.sourceAvatar.update(
|
||||
|
|
@ -501,25 +582,71 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
transition.setFrame(view: targetAvatarView, frame: targetAvatarFrame)
|
||||
}
|
||||
|
||||
let badgeIconInset: CGFloat = 2.0
|
||||
if component.revenuePerUser != 0.0 {
|
||||
var revenueString = String(format: "%.1f", component.revenuePerUser)
|
||||
if revenueString.hasSuffix(".0") {
|
||||
revenueString = String(revenueString[revenueString.startIndex ..< revenueString.index(revenueString.endIndex, offsetBy: -2)])
|
||||
}
|
||||
let sourceAvatarBadgeSize = self.sourceAvatarBadge.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(BorderedBadgeComponent(
|
||||
backgroundColor: environment.theme.list.itemDisclosureActions.constructive.fillColor,
|
||||
cutoutColor: environment.theme.list.plainBackgroundColor,
|
||||
content: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(TransformContents(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Premium/PremiumStar",
|
||||
tintColor: environment.theme.list.itemDisclosureActions.constructive.foregroundColor,
|
||||
scaleFactor: 0.58
|
||||
)),
|
||||
fixedSize: CGSize(width: 13.0, height: 10.0),
|
||||
translation: CGPoint(x: 0.0, y: 1.0 + UIScreenPixel)
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: revenueString, font: Font.regular(13.0), textColor: environment.theme.list.itemDisclosureActions.constructive.foregroundColor))
|
||||
)))
|
||||
], spacing: 2.0)),
|
||||
insets: UIEdgeInsets(top: 3.0, left: 6.0, bottom: 3.0, right: 6.0),
|
||||
cutoutWidth: 1.0 + UIScreenPixel)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let sourceAvatarBadgeFrame = CGRect(origin: CGPoint(x: sourceAvatarFrame.minX + floor((sourceAvatarFrame.width - sourceAvatarBadgeSize.width) * 0.5), y: sourceAvatarFrame.maxY - 7.0 - floor(sourceAvatarBadgeSize.height * 0.5)), size: sourceAvatarBadgeSize)
|
||||
if let sourceAvatarBadgeView = self.sourceAvatarBadge.view {
|
||||
if sourceAvatarBadgeView.superview == nil {
|
||||
self.scrollContentView.addSubview(sourceAvatarBadgeView)
|
||||
}
|
||||
transition.setFrame(view: sourceAvatarBadgeView, frame: sourceAvatarBadgeFrame)
|
||||
}
|
||||
}
|
||||
|
||||
let targetAvatarBadgeSize = self.targetAvatarBadge.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(BorderedBadgeComponent(
|
||||
backgroundColor: UIColor(rgb: 0x8A7AFF),
|
||||
backgroundColor: environment.theme.list.itemCheckColors.fillColor,
|
||||
cutoutColor: environment.theme.list.plainBackgroundColor,
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Premium/PremiumStar",
|
||||
tintColor: .white,
|
||||
scaleFactor: 0.95
|
||||
)),
|
||||
insets: UIEdgeInsets(top: badgeIconInset, left: badgeIconInset, bottom: badgeIconInset, right: badgeIconInset),
|
||||
aspect: 1.0,
|
||||
cutoutWidth: 1.0 + UIScreenPixel
|
||||
)),
|
||||
content: AnyComponent(HStack([
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(TransformContents(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Media Editor/Link",
|
||||
tintColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
scaleFactor: 0.75
|
||||
)),
|
||||
translation: CGPoint(x: 0.0, y: 0.0)
|
||||
))
|
||||
),
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "\(formatPermille(component.commissionPermille))%", font: Font.regular(13.0), textColor: environment.theme.list.itemCheckColors.foregroundColor))
|
||||
)))
|
||||
], spacing: 2.0)),
|
||||
insets: UIEdgeInsets(top: 3.0, left: 6.0, bottom: 3.0, right: 6.0),
|
||||
cutoutWidth: 1.0 + UIScreenPixel)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let targetAvatarBadgeFrame = CGRect(origin: CGPoint(x: targetAvatarFrame.maxX + 3.0 - targetAvatarBadgeSize.width, y: targetAvatarFrame.maxY + 3.0 - targetAvatarBadgeSize.height), size: targetAvatarBadgeSize)
|
||||
let targetAvatarBadgeFrame = CGRect(origin: CGPoint(x: targetAvatarFrame.minX + floor((targetAvatarFrame.width - targetAvatarBadgeSize.width) * 0.5), y: targetAvatarFrame.maxY - 7.0 - floor(targetAvatarBadgeSize.height * 0.5)), size: targetAvatarBadgeSize)
|
||||
if let targetAvatarBadgeView = self.targetAvatarBadge.view {
|
||||
if targetAvatarBadgeView.superview == nil {
|
||||
self.scrollContentView.addSubview(targetAvatarBadgeView)
|
||||
|
|
@ -553,7 +680,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
}
|
||||
transition.setFrame(view: self.sourceTargetArrow, frame: sourceTargetArrowFrame)
|
||||
}
|
||||
} else if case let .active(active) = component.mode {
|
||||
} else if case let .active(active) = currentMode {
|
||||
contentHeight += 31.0
|
||||
|
||||
let linkIconBackgroundSize = self.linkIconBackground.update(
|
||||
|
|
@ -592,8 +719,18 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
transition.setFrame(view: linkIconView, frame: linkIconFrame)
|
||||
}
|
||||
|
||||
if active.userCount != 0 {
|
||||
let linkIconBadgeSize = self.linkIconBadge.update(
|
||||
if active.bot.participants != 0 {
|
||||
let linkIconBadge: ComponentView<Empty>
|
||||
var linkIconBadgeTransition = transition
|
||||
if let current = self.linkIconBadge {
|
||||
linkIconBadge = current
|
||||
} else {
|
||||
linkIconBadgeTransition = linkIconBadgeTransition.withAnimation(.none)
|
||||
linkIconBadge = ComponentView()
|
||||
self.linkIconBadge = linkIconBadge
|
||||
}
|
||||
|
||||
let linkIconBadgeSize = linkIconBadge.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BorderedBadgeComponent(
|
||||
backgroundColor: UIColor(rgb: 0x34C759),
|
||||
|
|
@ -605,7 +742,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
scaleFactor: 1.0
|
||||
))),
|
||||
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)),
|
||||
insets: UIEdgeInsets(top: 4.0, left: 9.0, bottom: 4.0, right: 8.0),
|
||||
|
|
@ -615,18 +752,21 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let linkIconBadgeFrame = CGRect(origin: CGPoint(x: linkIconBackgroundFrame.minX + floor((linkIconBackgroundFrame.width - linkIconBadgeSize.width) * 0.5), y: linkIconBackgroundFrame.maxY - floor(linkIconBadgeSize.height * 0.5)), size: linkIconBadgeSize)
|
||||
if let linkIconBadgeView = self.linkIconBadge.view {
|
||||
if let linkIconBadgeView = linkIconBadge.view {
|
||||
if linkIconBadgeView.superview == nil {
|
||||
self.scrollContentView.addSubview(linkIconBadgeView)
|
||||
}
|
||||
transition.setFrame(view: linkIconBadgeView, frame: linkIconBadgeFrame)
|
||||
linkIconBadgeTransition.setFrame(view: linkIconBadgeView, frame: linkIconBadgeFrame)
|
||||
}
|
||||
} else if let linkIconBadge = self.linkIconBadge {
|
||||
self.linkIconBadge = nil
|
||||
linkIconBadge.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
contentHeight += linkIconBackgroundSize.height + 21.0
|
||||
}
|
||||
|
||||
let commissionTitle = "\(component.commissionPermille / 10)%"
|
||||
let commissionTitle = "\(formatPermille(component.commissionPermille))%"
|
||||
let durationTitle: String
|
||||
if let durationMonths = component.programDuration {
|
||||
durationTitle = timeIntervalString(strings: environment.strings, value: durationMonths * (24 * 60 * 60))
|
||||
|
|
@ -635,12 +775,22 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
}
|
||||
|
||||
let titleString: String
|
||||
let subtitleString: String
|
||||
var subtitleString: String
|
||||
var dailyRevenueString: String?
|
||||
let termsString: String
|
||||
switch component.mode {
|
||||
switch currentMode {
|
||||
case .join:
|
||||
titleString = "Affiliate Program"
|
||||
subtitleString = "**\(component.sourcePeer.compactDisplayTitle)** will share **\(commissionTitle)** of the revenue from each user you refer to it for **\(durationTitle)**."
|
||||
|
||||
if component.revenuePerUser != 0.0 {
|
||||
var revenueString = String(format: "%.1f", component.revenuePerUser)
|
||||
if revenueString.hasSuffix(".0") {
|
||||
revenueString = String(revenueString[revenueString.startIndex ..< revenueString.index(revenueString.endIndex, offsetBy: -2)])
|
||||
}
|
||||
dailyRevenueString = "Daily revenue per user: #**\(revenueString)**"
|
||||
}
|
||||
|
||||
termsString = "By joining this program, you afree to the [terms and conditions](https://telegram.org/terms) of Affiliate Programs."
|
||||
case let .active(active):
|
||||
titleString = "Referral Link"
|
||||
|
|
@ -651,12 +801,12 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
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)."
|
||||
if active.userCount == 0 {
|
||||
if active.bot.participants == 0 {
|
||||
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."
|
||||
} 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(
|
||||
|
|
@ -711,11 +861,119 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
transition.setPosition(view: subtitleView, position: subtitleFrame.center)
|
||||
subtitleView.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size)
|
||||
}
|
||||
contentHeight += subtitleSize.height + 23.0
|
||||
contentHeight += subtitleSize.height
|
||||
|
||||
if let dailyRevenueString {
|
||||
let dailyRevenueText: ComponentView<Empty>
|
||||
if let current = self.dailyRevenueText {
|
||||
dailyRevenueText = current
|
||||
} else {
|
||||
dailyRevenueText = ComponentView()
|
||||
self.dailyRevenueText = dailyRevenueText
|
||||
}
|
||||
|
||||
var inlineTextStarImage: UIImage?
|
||||
if let current = self.inlineTextStarImage {
|
||||
inlineTextStarImage = current
|
||||
} else {
|
||||
if let image = UIImage(bundleImageName: "Premium/Stars/StarSmall") {
|
||||
let starInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
inlineTextStarImage = generateImage(CGSize(width: starInsets.left + image.size.width + starInsets.right, height: image.size.height), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
UIGraphicsPushContext(context)
|
||||
defer {
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
image.draw(at: CGPoint(x: starInsets.left, y: starInsets.top))
|
||||
})?.withRenderingMode(.alwaysOriginal)
|
||||
self.inlineTextStarImage = inlineTextStarImage
|
||||
}
|
||||
}
|
||||
|
||||
let attributedDailyRevenueString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(dailyRevenueString, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.itemPrimaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemAccentColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center))
|
||||
if let range = attributedDailyRevenueString.string.range(of: "#"), let starImage = inlineTextStarImage {
|
||||
final class RunDelegateData {
|
||||
let ascent: CGFloat
|
||||
let descent: CGFloat
|
||||
let width: CGFloat
|
||||
|
||||
init(ascent: CGFloat, descent: CGFloat, width: CGFloat) {
|
||||
self.ascent = ascent
|
||||
self.descent = descent
|
||||
self.width = width
|
||||
}
|
||||
}
|
||||
|
||||
let runDelegateData = RunDelegateData(
|
||||
ascent: Font.regular(15.0).ascender,
|
||||
descent: Font.regular(15.0).descender,
|
||||
width: starImage.size.width + 2.0
|
||||
)
|
||||
var callbacks = CTRunDelegateCallbacks(
|
||||
version: kCTRunDelegateCurrentVersion,
|
||||
dealloc: { dataRef in
|
||||
Unmanaged<RunDelegateData>.fromOpaque(dataRef).release()
|
||||
},
|
||||
getAscent: { dataRef in
|
||||
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||
return data.takeUnretainedValue().ascent
|
||||
},
|
||||
getDescent: { dataRef in
|
||||
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||
return data.takeUnretainedValue().descent
|
||||
},
|
||||
getWidth: { dataRef in
|
||||
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
|
||||
return data.takeUnretainedValue().width
|
||||
}
|
||||
)
|
||||
if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) {
|
||||
attributedDailyRevenueString.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: NSRange(range, in: attributedDailyRevenueString.string))
|
||||
}
|
||||
attributedDailyRevenueString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: attributedDailyRevenueString.string))
|
||||
attributedDailyRevenueString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: attributedDailyRevenueString.string))
|
||||
attributedDailyRevenueString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedDailyRevenueString.string))
|
||||
}
|
||||
|
||||
let dailyRevenueTextSize = dailyRevenueText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(attributedDailyRevenueString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
)
|
||||
contentHeight += 16.0
|
||||
let dailyRevenueTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - dailyRevenueTextSize.width) * 0.5), y: contentHeight), size: dailyRevenueTextSize)
|
||||
if let dailyRevenueTextView = dailyRevenueText.view {
|
||||
if dailyRevenueTextView.superview == nil {
|
||||
self.scrollContentView.addSubview(dailyRevenueTextView)
|
||||
}
|
||||
transition.setPosition(view: dailyRevenueTextView, position: dailyRevenueTextFrame.center)
|
||||
dailyRevenueTextView.bounds = CGRect(origin: CGPoint(), size: dailyRevenueTextFrame.size)
|
||||
}
|
||||
contentHeight += dailyRevenueTextSize.height
|
||||
} else if let dailyRevenueText = self.dailyRevenueText {
|
||||
self.dailyRevenueText = nil
|
||||
dailyRevenueText.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
contentHeight += 23.0
|
||||
|
||||
var displayTargetPeer = false
|
||||
var isTargetPeerSelectable = false
|
||||
switch component.mode {
|
||||
switch currentMode {
|
||||
case let .join(join):
|
||||
displayTargetPeer = join.canSelectTargetPeer
|
||||
isTargetPeerSelectable = join.canSelectTargetPeer
|
||||
|
|
@ -777,8 +1035,8 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
}
|
||||
contentHeight += 12.0
|
||||
|
||||
if case let .active(active) = component.mode {
|
||||
var cleanLink = active.link
|
||||
if case let .active(active) = currentMode {
|
||||
var cleanLink = active.bot.url
|
||||
let removePrefixes: [String] = ["http://", "https://"]
|
||||
for prefix in removePrefixes {
|
||||
if cleanLink.hasPrefix(prefix) {
|
||||
|
|
@ -801,11 +1059,11 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
minSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
contentInsets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component, case let .active(active) = component.mode else {
|
||||
guard let self, case let .active(active) = self.currentMode else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.dismiss()
|
||||
active.copyLink()
|
||||
active.copyLink(active.bot)
|
||||
},
|
||||
animateAlpha: true,
|
||||
animateScale: false,
|
||||
|
|
@ -820,13 +1078,14 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
self.scrollContentView.addSubview(linkTextView)
|
||||
}
|
||||
transition.setFrame(view: linkTextView, frame: linkTextFrame)
|
||||
transition.setAlpha(view: linkTextView, alpha: self.isChangingTargetPeer ? 0.6 : 1.0)
|
||||
}
|
||||
contentHeight += linkTextSize.height
|
||||
contentHeight += 24.0
|
||||
}
|
||||
|
||||
let actionButtonTitle: String
|
||||
switch component.mode {
|
||||
switch currentMode {
|
||||
case .join:
|
||||
actionButtonTitle = "Join Program"
|
||||
case .active:
|
||||
|
|
@ -853,18 +1112,18 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
guard let self, let currentMode = self.currentMode else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.dismiss()
|
||||
|
||||
switch component.mode {
|
||||
switch currentMode {
|
||||
case let .join(join):
|
||||
if let currentTargetPeer = self.currentTargetPeer {
|
||||
join.completion(currentTargetPeer)
|
||||
}
|
||||
case let .active(active):
|
||||
active.copyLink()
|
||||
active.copyLink(active.bot)
|
||||
}
|
||||
}
|
||||
)),
|
||||
|
|
@ -928,7 +1187,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||
|
||||
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>
|
||||
if let current = self.toast {
|
||||
toast = current
|
||||
|
|
@ -1027,6 +1286,7 @@ public class JoinAffiliateProgramScreen: ViewControllerComponentContainer {
|
|||
sourcePeer: EnginePeer,
|
||||
commissionPermille: Int32,
|
||||
programDuration: Int32?,
|
||||
revenuePerUser: Double,
|
||||
mode: Mode
|
||||
) {
|
||||
self.context = context
|
||||
|
|
@ -1036,6 +1296,7 @@ public class JoinAffiliateProgramScreen: ViewControllerComponentContainer {
|
|||
sourcePeer: sourcePeer,
|
||||
commissionPermille: commissionPermille,
|
||||
programDuration: programDuration,
|
||||
revenuePerUser: revenuePerUser,
|
||||
mode: mode
|
||||
), navigationBarAppearance: .none)
|
||||
|
||||
|
|
|
|||
|
|
@ -1430,17 +1430,28 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||
} else {
|
||||
if let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil {
|
||||
if items[.botAffiliateProgram] == nil {
|
||||
items[.botAffiliateProgram] = []
|
||||
var canJoinRefProgram = false
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] {
|
||||
if let value = value as? Double {
|
||||
canJoinRefProgram = value != 0.0
|
||||
} else if let value = value as? Bool {
|
||||
canJoinRefProgram = value
|
||||
}
|
||||
}
|
||||
|
||||
if canJoinRefProgram {
|
||||
if items[.botAffiliateProgram] == nil {
|
||||
items[.botAffiliateProgram] = []
|
||||
}
|
||||
//TODO:localize
|
||||
let programTitleValue: String
|
||||
programTitleValue = "\(formatPermille(starRefProgram.commissionPermille))%"
|
||||
//TODO:localize
|
||||
items[.botAffiliateProgram]!.append(PeerInfoScreenDisclosureItem(id: 0, label: .labelBadge(programTitleValue), additionalBadgeLabel: nil, text: "Affiliate Program", icon: PresentationResourcesSettings.affiliateProgram, action: {
|
||||
interaction.editingOpenAffiliateProgram()
|
||||
}))
|
||||
items[.botAffiliateProgram]!.append(PeerInfoScreenCommentItem(id: 1, text: "Share a link to \(EnginePeer.user(user).compactDisplayTitle) with your friends and and earn \(formatPermille(starRefProgram.commissionPermille))% of their spending there."))
|
||||
}
|
||||
//TODO:localize
|
||||
let programTitleValue: String
|
||||
programTitleValue = "\(starRefProgram.commissionPermille / 10)%"
|
||||
//TODO:localize
|
||||
items[.botAffiliateProgram]!.append(PeerInfoScreenDisclosureItem(id: 0, label: .labelBadge(programTitleValue), additionalBadgeLabel: nil, text: "Affiliate Program", icon: PresentationResourcesSettings.affiliateProgram, action: {
|
||||
interaction.editingOpenAffiliateProgram()
|
||||
}))
|
||||
items[.botAffiliateProgram]!.append(PeerInfoScreenCommentItem(id: 1, text: "Share a link to \(EnginePeer.user(user).compactDisplayTitle) with your friends and and earn \(starRefProgram.commissionPermille / 10)% of their spending there."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1938,16 +1949,28 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Bot_Username, icon: PresentationResourcesSettings.bot, action: {
|
||||
interaction.editingOpenPublicLinkSetup()
|
||||
}))
|
||||
//TODO:localize
|
||||
let programTitleValue: PeerInfoScreenDisclosureItem.Label
|
||||
if let cachedData = data.cachedData as? CachedUserData, let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil {
|
||||
programTitleValue = .labelBadge("\(starRefProgram.commissionPermille / 10)%")
|
||||
} else {
|
||||
programTitleValue = .text("Off")
|
||||
|
||||
var canSetupRefProgram = false
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_program_allowed"] {
|
||||
if let value = value as? Double {
|
||||
canSetupRefProgram = value != 0.0
|
||||
} else if let value = value as? Bool {
|
||||
canSetupRefProgram = value
|
||||
}
|
||||
}
|
||||
|
||||
if canSetupRefProgram {
|
||||
//TODO:localize
|
||||
let programTitleValue: PeerInfoScreenDisclosureItem.Label
|
||||
if let cachedData = data.cachedData as? CachedUserData, let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil {
|
||||
programTitleValue = .labelBadge("\(formatPermille(starRefProgram.commissionPermille))%")
|
||||
} else {
|
||||
programTitleValue = .text("Off")
|
||||
}
|
||||
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: programTitleValue, additionalBadgeLabel: presentationData.strings.Settings_New, text: "Affiliate Program", icon: PresentationResourcesSettings.affiliateProgram, action: {
|
||||
interaction.editingOpenAffiliateProgram()
|
||||
}))
|
||||
}
|
||||
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: programTitleValue, additionalBadgeLabel: presentationData.strings.Settings_New, text: "Affiliate Program", icon: PresentationResourcesSettings.affiliateProgram, action: {
|
||||
interaction.editingOpenAffiliateProgram()
|
||||
}))
|
||||
|
||||
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemIntro, text: presentationData.strings.PeerInfo_Bot_EditIntro, icon: UIImage(bundleImageName: "Peer Info/BotIntro"), action: {
|
||||
interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-intro", behavior: .interactive)))
|
||||
|
|
@ -2199,9 +2222,20 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||
}
|
||||
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliatePrograms, label: .text(""), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Affiliate Programs", icon: PresentationResourcesSettings.affiliateProgram, action: {
|
||||
interaction.editingOpenAffiliateProgram()
|
||||
}))
|
||||
var canJoinRefProgram = false
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] {
|
||||
if let value = value as? Double {
|
||||
canJoinRefProgram = value != 0.0
|
||||
} else if let value = value as? Bool {
|
||||
canJoinRefProgram = value
|
||||
}
|
||||
}
|
||||
|
||||
if canJoinRefProgram {
|
||||
items[.peerAdditionalSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliatePrograms, label: .text(""), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Affiliate Programs", icon: PresentationResourcesSettings.affiliateProgram, action: {
|
||||
interaction.editingOpenAffiliateProgram()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if isCreator { //if let cachedData = data.cachedData as? CachedChannelData, cachedData.flags.contains(.canDeleteHistory) {
|
||||
|
|
@ -8596,7 +8630,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
if let peer = self.data?.peer as? TelegramUser, let botInfo = peer.botInfo {
|
||||
if botInfo.flags.contains(.canEdit) {
|
||||
let _ = (self.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: self.context, peerId: peer.id, mode: .editProgram)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
|
@ -8620,16 +8654,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
if let result {
|
||||
mode = .active(JoinAffiliateProgramScreenMode.Active(
|
||||
targetPeer: accountPeer,
|
||||
link: result.url,
|
||||
userCount: Int(result.participants),
|
||||
copyLink: { [weak self] in
|
||||
bot: result,
|
||||
copyLink: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
//TODO:localize
|
||||
UIPasteboard.general.string = result.url
|
||||
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 **\(formatPermille(result.commissionPermille))%** of what people who use it spend in **\(result.peer.compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
))
|
||||
} else {
|
||||
|
|
@ -8652,17 +8685,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
sourcePeer: bot.peer,
|
||||
commissionPermille: bot.commissionPermille,
|
||||
programDuration: bot.durationMonths,
|
||||
revenuePerUser: bot.participants == 0 ? 0.0 : Double(bot.revenue) / Double(bot.participants),
|
||||
mode: .active(JoinAffiliateProgramScreenMode.Active(
|
||||
targetPeer: targetPeer,
|
||||
link: bot.url,
|
||||
userCount: Int(bot.participants),
|
||||
copyLink: { [weak self] in
|
||||
bot: bot,
|
||||
copyLink: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
UIPasteboard.general.string = bot.url
|
||||
UIPasteboard.general.string = result.url
|
||||
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 **\(formatPermille(result.commissionPermille))%** of what people who use it spend in **\(result.peer.compactDisplayTitle)**!"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
}
|
||||
))
|
||||
))
|
||||
|
|
@ -8675,6 +8708,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
sourcePeer: .user(peer),
|
||||
commissionPermille: starRefProgram.commissionPermille,
|
||||
programDuration: starRefProgram.durationMonths,
|
||||
revenuePerUser: starRefProgram.dailyRevenuePerUser?.totalValue ?? 0.0,
|
||||
mode: mode
|
||||
))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||
let context: AccountContext
|
||||
let subject: StarsTransactionScreen.Subject
|
||||
let cancel: (Bool) -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openPeer: (EnginePeer, Bool) -> Void
|
||||
let openMessage: (EngineMessage.Id) -> Void
|
||||
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
|
||||
let openAppExamples: () -> Void
|
||||
|
|
@ -44,7 +44,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||
context: AccountContext,
|
||||
subject: StarsTransactionScreen.Subject,
|
||||
cancel: @escaping (Bool) -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openPeer: @escaping (EnginePeer, Bool) -> Void,
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void,
|
||||
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
|
||||
openAppExamples: @escaping () -> Void,
|
||||
|
|
@ -95,6 +95,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||
if case let .peer(peer) = transaction.peer {
|
||||
peerIds.append(peer.id)
|
||||
}
|
||||
if let starrefPeerId = transaction.starrefPeerId {
|
||||
peerIds.append(starrefPeerId)
|
||||
}
|
||||
case let .receipt(receipt):
|
||||
peerIds.append(receipt.botPaymentId)
|
||||
case let .gift(message):
|
||||
|
|
@ -232,6 +235,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||
var giveawayMessageId: MessageId?
|
||||
var isBoost = false
|
||||
var giftAnimation: TelegramMediaFile?
|
||||
var isRefProgram = false
|
||||
|
||||
var delayedCloseOnOpenPeer = true
|
||||
switch subject {
|
||||
|
|
@ -399,6 +403,23 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||
}
|
||||
transactionPeer = transaction.peer
|
||||
isGift = true
|
||||
} else if let starrefCommissionPermille = transaction.starrefCommissionPermille {
|
||||
//TODO:localize
|
||||
isRefProgram = true
|
||||
if transaction.starrefPeerId == nil {
|
||||
titleText = "\(formatPermille(starrefCommissionPermille))% 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) {
|
||||
titleText = strings.Stars_Transaction_Reaction_Title
|
||||
descriptionText = ""
|
||||
|
|
@ -722,7 +743,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||
)
|
||||
)
|
||||
))
|
||||
} else if let toPeer {
|
||||
} else if let toPeer, !isRefProgram {
|
||||
let title: String
|
||||
if isSubscription {
|
||||
if isBotSubscription {
|
||||
|
|
@ -751,7 +772,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||
),
|
||||
action: {
|
||||
if delayedCloseOnOpenPeer {
|
||||
component.openPeer(toPeer)
|
||||
component.openPeer(toPeer, false)
|
||||
Queue.mainQueue().after(1.0, {
|
||||
component.cancel(false)
|
||||
})
|
||||
|
|
@ -845,6 +866,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: "\(formatPermille(starrefCommissionPermille))%", font: tableFont, textColor: tableTextColor))
|
||||
)),
|
||||
insets: UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 5.0)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if let transactionId {
|
||||
tableItems.append(.init(
|
||||
|
|
@ -1200,7 +1348,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
|
|||
|
||||
let context: AccountContext
|
||||
let subject: StarsTransactionScreen.Subject
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openPeer: (EnginePeer, Bool) -> Void
|
||||
let openMessage: (EngineMessage.Id) -> Void
|
||||
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
|
||||
let openAppExamples: () -> Void
|
||||
|
|
@ -1210,7 +1358,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
|
|||
init(
|
||||
context: AccountContext,
|
||||
subject: StarsTransactionScreen.Subject,
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openPeer: @escaping (EnginePeer, Bool) -> Void,
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void,
|
||||
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
|
||||
openAppExamples: @escaping () -> Void,
|
||||
|
|
@ -1363,7 +1511,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
|||
) {
|
||||
self.context = context
|
||||
|
||||
var openPeerImpl: ((EnginePeer) -> Void)?
|
||||
var openPeerImpl: ((EnginePeer, Bool) -> Void)?
|
||||
var openMessageImpl: ((EngineMessage.Id) -> Void)?
|
||||
var openMediaImpl: (([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)?
|
||||
var openAppExamplesImpl: (() -> Void)?
|
||||
|
|
@ -1375,8 +1523,8 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
|||
component: StarsTransactionSheetComponent(
|
||||
context: context,
|
||||
subject: subject,
|
||||
openPeer: { peerId in
|
||||
openPeerImpl?(peerId)
|
||||
openPeer: { peerId, isProfile in
|
||||
openPeerImpl?(peerId, isProfile)
|
||||
},
|
||||
openMessage: { messageId in
|
||||
openMessageImpl?(messageId)
|
||||
|
|
@ -1402,7 +1550,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
|||
self.navigationPresentation = .flatModal
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
|
||||
openPeerImpl = { [weak self] peer in
|
||||
openPeerImpl = { [weak self] peer, isProfile in
|
||||
guard let self, let navigationController = self.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
|
|
@ -1415,7 +1563,13 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
|||
guard let peer else {
|
||||
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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -662,46 +662,57 @@ final class StarsTransactionsScreenComponent: Component {
|
|||
contentHeight += balanceSize.height
|
||||
contentHeight += 34.0
|
||||
|
||||
let earnStarsSectionSize = self.earnStarsSection.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: nil,
|
||||
footer: nil,
|
||||
items: [
|
||||
//TODO:localize
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor(
|
||||
itemGenerator: ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), icon: PresentationResourcesSettings.earnStars, title: "Earn Stars", titleBadge: presentationData.strings.Settings_New, label: "Distribute links to mini apps and earn a share of their revenue in Stars.", labelStyle: .multilineDetailText, sectionId: 0, style: .blocks, action: {
|
||||
}),
|
||||
params: ListViewItemLayoutParams(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let _ = (component.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: component.context, peerId: component.context.account.peerId, mode: .connectedPrograms)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||
var canJoinRefProgram = false
|
||||
if let data = component.context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_connect_allowed"] {
|
||||
if let value = value as? Double {
|
||||
canJoinRefProgram = value != 0.0
|
||||
} else if let value = value as? Bool {
|
||||
canJoinRefProgram = value
|
||||
}
|
||||
}
|
||||
|
||||
if canJoinRefProgram {
|
||||
let earnStarsSectionSize = self.earnStarsSection.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: nil,
|
||||
footer: nil,
|
||||
items: [
|
||||
//TODO:localize
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemComponentAdaptor(
|
||||
itemGenerator: ItemListDisclosureItem(presentationData: ItemListPresentationData(presentationData), icon: PresentationResourcesSettings.earnStars, title: "Earn Stars", titleBadge: presentationData.strings.Settings_New, label: "Distribute links to mini apps and earn a share of their revenue in Stars.", labelStyle: .multilineDetailText, sectionId: 0, style: .blocks, action: {
|
||||
}),
|
||||
params: ListViewItemLayoutParams(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, availableHeight: 10000.0, isStandalone: true),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let setupScreen = component.context.sharedContext.makeAffiliateProgramSetupScreen(context: component.context, initialData: initialData)
|
||||
self.controller?()?.push(setupScreen)
|
||||
})
|
||||
}
|
||||
)))
|
||||
]
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInsets, height: availableSize.height)
|
||||
)
|
||||
let earnStarsSectionFrame = CGRect(origin: CGPoint(x: sideInsets * 0.5, y: contentHeight), size: earnStarsSectionSize)
|
||||
if let earnStarsSectionView = self.earnStarsSection.view {
|
||||
if earnStarsSectionView.superview == nil {
|
||||
self.scrollView.addSubview(earnStarsSectionView)
|
||||
let _ = (component.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: component.context, peerId: component.context.account.peerId, mode: .connectedPrograms)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let setupScreen = component.context.sharedContext.makeAffiliateProgramSetupScreen(context: component.context, initialData: initialData)
|
||||
self.controller?()?.push(setupScreen)
|
||||
})
|
||||
}
|
||||
)))
|
||||
]
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInsets, height: availableSize.height)
|
||||
)
|
||||
let earnStarsSectionFrame = CGRect(origin: CGPoint(x: sideInsets * 0.5, y: contentHeight), size: earnStarsSectionSize)
|
||||
if let earnStarsSectionView = self.earnStarsSection.view {
|
||||
if earnStarsSectionView.superview == nil {
|
||||
self.scrollView.addSubview(earnStarsSectionView)
|
||||
}
|
||||
starTransition.setFrame(view: earnStarsSectionView, frame: earnStarsSectionFrame)
|
||||
}
|
||||
starTransition.setFrame(view: earnStarsSectionView, frame: earnStarsSectionFrame)
|
||||
contentHeight += earnStarsSectionSize.height
|
||||
contentHeight += 44.0
|
||||
}
|
||||
contentHeight += earnStarsSectionSize.height
|
||||
contentHeight += 44.0
|
||||
|
||||
let fontBaseDisplaySize = 17.0
|
||||
var subscriptionsItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ func openWebAppImpl(
|
|||
|
||||
var fullSize = false
|
||||
var isFullscreen = false
|
||||
if isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, url: url), case .peer(_, .appStart) = internalUrl {
|
||||
if isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: context.sharedContext, context: context, url: url), case .peer(_, .appStart) = internalUrl {
|
||||
if url.contains("mode=fullscreen") {
|
||||
isFullscreen = true
|
||||
fullSize = true
|
||||
|
|
|
|||
|
|
@ -1147,7 +1147,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||
self.allAdMessages = (messages.first, [], 0)
|
||||
} else {
|
||||
var adPeerName: String?
|
||||
if let adAttribute = messages.first?.adAttribute, let parsedUrl = parseAdUrl(sharedContext: self.context.sharedContext, url: adAttribute.url), case let .peer(reference, _) = parsedUrl, case let .name(peerName) = reference {
|
||||
if let adAttribute = messages.first?.adAttribute, let parsedUrl = parseAdUrl(sharedContext: self.context.sharedContext, context: self.context, url: adAttribute.url), case let .peer(reference, _) = parsedUrl, case let .name(peerName) = reference {
|
||||
adPeerName = peerName
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public func parseConfirmationCodeUrl(sharedContext: SharedAccountContext, url: U
|
|||
}
|
||||
}
|
||||
if url.scheme == "tg" {
|
||||
if let host = url.host, let query = url.query, let parsedUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query) {
|
||||
if let host = url.host, let query = url.query, let parsedUrl = parseInternalUrl(sharedContext: sharedContext, context: nil, query: host + "?" + query) {
|
||||
switch parsedUrl {
|
||||
case let .confirmationCode(code):
|
||||
return code
|
||||
|
|
|
|||
|
|
@ -2840,8 +2840,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return AffiliateProgramSetupScreen(context: context, initialContent: initialData)
|
||||
}
|
||||
|
||||
public func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, mode: JoinAffiliateProgramScreenMode) -> ViewController {
|
||||
return JoinAffiliateProgramScreen(context: context, sourcePeer: sourcePeer, commissionPermille: commissionPermille, programDuration: programDuration, mode: mode)
|
||||
public func makeAffiliateProgramJoinScreen(context: AccountContext, sourcePeer: EnginePeer, commissionPermille: Int32, programDuration: Int32?, revenuePerUser: Double, mode: JoinAffiliateProgramScreenMode) -> ViewController {
|
||||
return JoinAffiliateProgramScreen(context: context, sourcePeer: sourcePeer, commissionPermille: commissionPermille, programDuration: programDuration, revenuePerUser: revenuePerUser, mode: mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ private enum ParsedUrl {
|
|||
case internalUrl(ParsedInternalUrl)
|
||||
}
|
||||
|
||||
public func parseInternalUrl(sharedContext: SharedAccountContext, query: String) -> ParsedInternalUrl? {
|
||||
public func parseInternalUrl(sharedContext: SharedAccountContext, context: AccountContext?, query: String) -> ParsedInternalUrl? {
|
||||
var query = query
|
||||
if query.hasPrefix("s/") {
|
||||
query = String(query[query.index(query.startIndex, offsetBy: 2)...])
|
||||
|
|
@ -270,7 +270,12 @@ public func parseInternalUrl(sharedContext: SharedAccountContext, query: String)
|
|||
}
|
||||
return .peer(.name(peerName), .attachBotStart(value, startAttach))
|
||||
} else if queryItem.name == "start" {
|
||||
let linkRefPrefix = "_tgref_"
|
||||
var linkRefPrefix = "_tgr_"
|
||||
if let context {
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["starref_start_param_prefixes"] as? String {
|
||||
linkRefPrefix = value
|
||||
}
|
||||
}
|
||||
if value.hasPrefix(linkRefPrefix) {
|
||||
let referrer = String(value[value.index(value.startIndex, offsetBy: linkRefPrefix.count)...])
|
||||
return .peer(.name(peerName), .referrer(referrer))
|
||||
|
|
@ -1139,14 +1144,14 @@ public func parseProxyUrl(sharedContext: SharedAccountContext, url: String) -> (
|
|||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .proxy(host, port, username, password, secret) = internalUrl {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: nil, query: String(url[basePrefix.endIndex...])), case let .proxy(host, port, username, password, secret) = internalUrl {
|
||||
return (host, port, username, password, secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .proxy(host, port, username, password, secret) = internalUrl {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: nil, query: host + "?" + query), case let .proxy(host, port, username, password, secret) = internalUrl {
|
||||
return (host, port, username, password, secret)
|
||||
}
|
||||
}
|
||||
|
|
@ -1160,14 +1165,14 @@ public func parseStickerPackUrl(sharedContext: SharedAccountContext, url: String
|
|||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .stickerPack(name, _) = internalUrl {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: nil, query: String(url[basePrefix.endIndex...])), case let .stickerPack(name, _) = internalUrl {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .stickerPack(name, _) = internalUrl {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: nil, query: host + "?" + query), case let .stickerPack(name, _) = internalUrl {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
|
@ -1181,14 +1186,14 @@ public func parseWallpaperUrl(sharedContext: SharedAccountContext, url: String)
|
|||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case let .wallpaper(wallpaper) = internalUrl {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: nil, query: String(url[basePrefix.endIndex...])), case let .wallpaper(wallpaper) = internalUrl {
|
||||
return wallpaper
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case let .wallpaper(wallpaper) = internalUrl {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: nil, query: host + "?" + query), case let .wallpaper(wallpaper) = internalUrl {
|
||||
return wallpaper
|
||||
}
|
||||
}
|
||||
|
|
@ -1196,20 +1201,20 @@ public func parseWallpaperUrl(sharedContext: SharedAccountContext, url: String)
|
|||
return nil
|
||||
}
|
||||
|
||||
public func parseAdUrl(sharedContext: SharedAccountContext, url: String) -> ParsedInternalUrl? {
|
||||
public func parseAdUrl(sharedContext: SharedAccountContext, context: AccountContext, url: String) -> ParsedInternalUrl? {
|
||||
let schemes = ["http://", "https://", ""]
|
||||
for basePath in baseTelegramMePaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])), case .peer = internalUrl {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: context, query: String(url[basePrefix.endIndex...])), case .peer = internalUrl {
|
||||
return internalUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: host + "?" + query), case .peer = internalUrl {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: context, query: host + "?" + query), case .peer = internalUrl {
|
||||
return internalUrl
|
||||
}
|
||||
}
|
||||
|
|
@ -1217,13 +1222,13 @@ public func parseAdUrl(sharedContext: SharedAccountContext, url: String) -> Pars
|
|||
return nil
|
||||
}
|
||||
|
||||
public func parseFullInternalUrl(sharedContext: SharedAccountContext, url: String) -> ParsedInternalUrl? {
|
||||
public func parseFullInternalUrl(sharedContext: SharedAccountContext, context: AccountContext, url: String) -> ParsedInternalUrl? {
|
||||
let schemes = ["http://", "https://", ""]
|
||||
for basePath in baseTelegramMePaths {
|
||||
for scheme in schemes {
|
||||
let basePrefix = scheme + basePath + "/"
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, query: String(url[basePrefix.endIndex...])) {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: sharedContext, context: context, query: String(url[basePrefix.endIndex...])) {
|
||||
return internalUrl
|
||||
}
|
||||
}
|
||||
|
|
@ -1309,7 +1314,7 @@ public func resolveUrlImpl(context: AccountContext, peerId: PeerId?, url: String
|
|||
}
|
||||
}
|
||||
if url.lowercased().hasPrefix(basePrefix) {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: context.sharedContext, query: String(url[basePrefix.endIndex...])) {
|
||||
if let internalUrl = parseInternalUrl(sharedContext: context.sharedContext, context: context, query: String(url[basePrefix.endIndex...])) {
|
||||
return resolveInternalUrl(context: context, url: internalUrl)
|
||||
|> map { result -> ResolveUrlResult in
|
||||
switch result {
|
||||
|
|
|
|||
|
|
@ -456,7 +456,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
})
|
||||
} else {
|
||||
if let url = controller.url, isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: self.context.sharedContext, url: url), case .peer(_, .appStart) = internalUrl {
|
||||
if let url = controller.url, isTelegramMeLink(url), let internalUrl = parseFullInternalUrl(sharedContext: self.context.sharedContext, context: self.context, url: url), case .peer(_, .appStart) = internalUrl {
|
||||
let _ = (self.context.sharedContext.resolveUrl(context: self.context, peerId: controller.peerId, url: url, skipUrlAuth: false)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] result in
|
||||
guard let self, let controller = self.controller else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue