diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 976f495fc1..b6342426a5 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12361,6 +12361,8 @@ Sorry for the inconvenience."; "Stars.BotRevenue.Withdraw.Balance" = "Available Balance"; "Stars.BotRevenue.Withdraw.Withdraw" = "Withdraw via Fragment"; +"Stars.BotRevenue.Withdraw.WithdrawShort" = "Withdraw"; +"Stars.BotRevenue.Withdraw.BuyAds" = "Buy Ads"; "Stars.BotRevenue.Withdraw.Info" = "You can collect rewards for Stars using Fragment, or use Stars to advertise your bot. [Learn More >]()"; "Stars.BotRevenue.Withdraw.Info_URL" = "https://telegram.org/tos"; @@ -12416,6 +12418,8 @@ Sorry for the inconvenience."; "Monetization.StarsTransactions" = "Stars Transactions"; "Monetization.BalanceStarsWithdraw" = "Withdraw via Fragment"; +"Monetization.BalanceStarsWithdrawShort" = "Withdraw"; +"Monetization.BalanceStarsBuyAds" = "Buy Ads"; "Monetization.Balance.StarsInfo" = "You can withdraw Stars using Fragment, or use Stars to advertise your channel. [Learn More >]()"; "Monetization.Balance.StarsInfo_URL" = "https://telegram.org"; diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index e95b6709e4..ecca85afe6 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -48,6 +48,7 @@ private final class ChannelStatsControllerArguments { let requestTonWithdraw: () -> Void let requestStarsWithdraw: () -> Void let showTimeoutTooltip: (Int32) -> Void + let buyAds: () -> Void let openMonetizationIntro: () -> Void let openMonetizationInfo: () -> Void let openTonTransaction: (RevenueStatsTransactionsContext.State.Transaction) -> Void @@ -57,7 +58,7 @@ private final class ChannelStatsControllerArguments { let presentCpmLocked: () -> Void let dismissInput: () -> Void - init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPostStats: @escaping (EnginePeer, StatsPostItem) -> Void, openStory: @escaping (EngineStoryItem, UIView) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openBoost: @escaping (ChannelBoostersContext.State.Boost) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void, updateStarsSelected: @escaping (Bool) -> Void, requestTonWithdraw: @escaping () -> Void, requestStarsWithdraw: @escaping () -> Void, showTimeoutTooltip: @escaping (Int32) -> Void, openMonetizationIntro: @escaping () -> Void, openMonetizationInfo: @escaping () -> Void, openTonTransaction: @escaping (RevenueStatsTransactionsContext.State.Transaction) -> Void, openStarsTransaction: @escaping (StarsContext.State.Transaction) -> Void, expandTransactions: @escaping () -> Void, updateCpmEnabled: @escaping (Bool) -> Void, presentCpmLocked: @escaping () -> Void, dismissInput: @escaping () -> Void) { + init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal, openPostStats: @escaping (EnginePeer, StatsPostItem) -> Void, openStory: @escaping (EngineStoryItem, UIView) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openBoost: @escaping (ChannelBoostersContext.State.Boost) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (PrepaidGiveaway) -> Void, updateGiftsSelected: @escaping (Bool) -> Void, updateStarsSelected: @escaping (Bool) -> Void, requestTonWithdraw: @escaping () -> Void, requestStarsWithdraw: @escaping () -> Void, showTimeoutTooltip: @escaping (Int32) -> Void, buyAds: @escaping () -> Void, openMonetizationIntro: @escaping () -> Void, openMonetizationInfo: @escaping () -> Void, openTonTransaction: @escaping (RevenueStatsTransactionsContext.State.Transaction) -> Void, openStarsTransaction: @escaping (StarsContext.State.Transaction) -> Void, expandTransactions: @escaping () -> Void, updateCpmEnabled: @escaping (Bool) -> Void, presentCpmLocked: @escaping () -> Void, dismissInput: @escaping () -> Void) { self.context = context self.loadDetailedGraph = loadDetailedGraph self.openPostStats = openPostStats @@ -74,6 +75,7 @@ private final class ChannelStatsControllerArguments { self.requestTonWithdraw = requestTonWithdraw self.requestStarsWithdraw = requestStarsWithdraw self.showTimeoutTooltip = showTimeoutTooltip + self.buyAds = buyAds self.openMonetizationIntro = openMonetizationIntro self.openMonetizationInfo = openMonetizationInfo self.openTonTransaction = openTonTransaction @@ -1087,7 +1089,9 @@ private enum StatsEntry: ItemListNodeEntry { arguments.requestStarsWithdraw() } }, - buyAdsAction: nil, + buyAdsAction: canWithdraw ? { + arguments.buyAds() + } : nil, sectionId: self.section, style: .blocks ) @@ -1799,6 +1803,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD var requestTonWithdrawImpl: (() -> Void)? var requestStarsWithdrawImpl: (() -> Void)? var showTimeoutTooltipImpl: ((Int32) -> Void)? + var buyAdsImpl: (() -> Void)? var updateStatusBarImpl: ((StatusBarStyle) -> Void)? var dismissInputImpl: (() -> Void)? @@ -1933,6 +1938,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD showTimeoutTooltip: { timestamp in showTimeoutTooltipImpl?(timestamp) }, + buyAds: { + buyAdsImpl?() + }, openMonetizationIntro: { let controller = MonetizationIntroScreen(context: context, openMore: {}) pushImpl?(controller) @@ -2442,6 +2450,16 @@ public func channelStatsController(context: AccountContext, updatedPresentationD } } } + buyAdsImpl = { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let _ = (context.engine.peers.requestStarsRevenueAdsAccountlUrl(peerId: peerId) + |> deliverOnMainQueue).startStandalone(next: { url in + guard let url else { + return + } + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) + }) + } openTonTransactionImpl = { transaction in let _ = (peer.get() |> take(1) diff --git a/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift b/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift index 591754a6eb..b63f1e4c79 100644 --- a/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift +++ b/submodules/StatisticsUI/Sources/MonetizationBalanceItem.swift @@ -95,6 +95,7 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { private let balanceTextNode: TextNode private let valueTextNode: TextNode private var button = ComponentView() + private var buyButton = ComponentView() private let activateArea: AccessibilityAreaNode @@ -348,8 +349,14 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { timer.invalidate() } } + + var actionTitle = isStars ? item.presentationData.strings.Monetization_BalanceStarsWithdraw : item.presentationData.strings.Monetization_BalanceWithdraw + var withdrawWidth = width - leftInset - rightInset + if let _ = item.buyAdsAction { + withdrawWidth = (withdrawWidth - 10.0) / 2.0 + actionTitle = item.presentationData.strings.Monetization_BalanceStarsWithdrawShort + } - let actionTitle = isStars ? item.presentationData.strings.Monetization_BalanceStarsWithdraw : item.presentationData.strings.Monetization_BalanceWithdraw let content: AnyComponentWithIdentity if remainingCooldownSeconds > 0 { content = AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent( @@ -364,7 +371,7 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { } else { content = AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: actionTitle, font: Font.semibold(17.0), color: item.presentationData.theme.list.itemCheckColors.foregroundColor))) } - + let buttonSize = self.button.update( transition: .immediate, component: AnyComponent(ButtonComponent( @@ -385,7 +392,7 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { } )), environment: {}, - containerSize: CGSize(width: width - leftInset - rightInset, height: 50.0) + containerSize: CGSize(width: withdrawWidth, height: 50.0) ) if let buttonView = self.button.view { if buttonView.superview == nil { @@ -394,6 +401,40 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode { let buttonFrame = CGRect(origin: CGPoint(x: leftInset, y: origin), size: buttonSize) buttonView.frame = buttonFrame } + + if let _ = item.buyAdsAction { + let buyButtonSize = self.buyButton.update( + transition: .immediate, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + color: item.presentationData.theme.list.itemCheckColors.fillColor, + foreground: item.presentationData.theme.list.itemCheckColors.foregroundColor, + pressedColor: item.presentationData.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: item.presentationData.strings.Monetization_BalanceStarsBuyAds, font: Font.semibold(17.0), color: item.presentationData.theme.list.itemCheckColors.foregroundColor))), + isEnabled: true, + allowActionWhenDisabled: false, + displaysProgress: false, + action: { [weak self] in + guard let self, let item = self.item, item.isEnabled else { + return + } + item.buyAdsAction?() + } + )), + environment: {}, + containerSize: CGSize(width: withdrawWidth, height: 50.0) + ) + if let buttonView = self.buyButton.view { + if buttonView.superview == nil { + self.view.addSubview(buttonView) + } + let buttonFrame = CGRect(origin: CGPoint(x: leftInset + withdrawWidth + 10.0, y: origin), size: buyButtonSize) + buttonView.frame = buttonFrame + } + } else if let buttonView = self.buyButton.view { + buttonView.removeFromSuperview() + } } else if let buttonView = self.button.view { buttonView.removeFromSuperview() } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift index 5c60e5e82b..243e28cd2a 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsBalanceComponent.swift @@ -21,7 +21,8 @@ final class StarsBalanceComponent: Component { let actionAvailable: Bool let actionIsEnabled: Bool let actionCooldownUntilTimestamp: Int32? - let buy: () -> Void + let action: () -> Void + let buyAds: (() -> Void)? init( theme: PresentationTheme, @@ -33,7 +34,8 @@ final class StarsBalanceComponent: Component { actionAvailable: Bool, actionIsEnabled: Bool, actionCooldownUntilTimestamp: Int32? = nil, - buy: @escaping () -> Void + action: @escaping () -> Void, + buyAds: (() -> Void)? ) { self.theme = theme self.strings = strings @@ -44,7 +46,8 @@ final class StarsBalanceComponent: Component { self.actionAvailable = actionAvailable self.actionIsEnabled = actionIsEnabled self.actionCooldownUntilTimestamp = actionCooldownUntilTimestamp - self.buy = buy + self.action = action + self.buyAds = buyAds } static func ==(lhs: StarsBalanceComponent, rhs: StarsBalanceComponent) -> Bool { @@ -83,6 +86,7 @@ final class StarsBalanceComponent: Component { private let title = ComponentView() private let subtitle = ComponentView() private var button = ComponentView() + private var buyAdsButton = ComponentView() private var component: StarsBalanceComponent? private weak var state: EmptyComponentState? @@ -187,11 +191,18 @@ final class StarsBalanceComponent: Component { if component.actionAvailable { contentHeight += 12.0 + var actionTitle = component.actionTitle + var withdrawWidth = availableSize.width - sideInset * 2.0 + if let _ = component.buyAds { + withdrawWidth = (withdrawWidth - 10.0) / 2.0 + actionTitle = component.strings.Stars_BotRevenue_Withdraw_WithdrawShort + } + let content: AnyComponentWithIdentity if remainingCooldownSeconds > 0 { content = AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent( VStack([ - AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(Text(text: component.actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))), + AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(Text(text: actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))), AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(HStack([ AnyComponentWithIdentity(id: 1, component: AnyComponent(BundleIconComponent(name: "Chat List/StatusLockIcon", tintColor: component.theme.list.itemCheckColors.fillColor.mixedWith(component.theme.list.itemCheckColors.foregroundColor, alpha: 0.7)))), AnyComponentWithIdentity(id: 0, component: AnyComponent(Text(text: stringForRemainingTime(remainingCooldownSeconds), font: Font.with(size: 11.0, weight: .medium, traits: [.monospacedNumbers]), color: component.theme.list.itemCheckColors.fillColor.mixedWith(component.theme.list.itemCheckColors.foregroundColor, alpha: 0.7)))) @@ -199,7 +210,7 @@ final class StarsBalanceComponent: Component { ], spacing: 1.0) )) } else { - content = AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: component.actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))) + content = AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))) } let buttonSize = self.button.update( @@ -218,11 +229,11 @@ final class StarsBalanceComponent: Component { guard let self, let component = self.component else { return } - component.buy() + component.action() } )), environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0) + containerSize: CGSize(width: withdrawWidth, height: 50.0) ) if let buttonView = self.button.view { if buttonView.superview == nil { @@ -231,6 +242,40 @@ final class StarsBalanceComponent: Component { let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: buttonSize) buttonView.frame = buttonFrame } + + if let _ = component.buyAds { + let buttonSize = self.buyAdsButton.update( + transition: transition, + component: AnyComponent(ButtonComponent( + background: ButtonComponent.Background( + color: component.theme.list.itemCheckColors.fillColor, + foreground: component.theme.list.itemCheckColors.foregroundColor, + pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) + ), + content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(Text(text: component.strings.Stars_BotRevenue_Withdraw_BuyAds, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor))), + isEnabled: component.actionIsEnabled, + allowActionWhenDisabled: false, + displaysProgress: false, + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.buyAds?() + } + )), + environment: {}, + containerSize: CGSize(width: withdrawWidth, height: 50.0) + ) + if let buttonView = self.buyAdsButton.view { + if buttonView.superview == nil { + self.addSubview(buttonView) + } + let buttonFrame = CGRect(origin: CGPoint(x: sideInset + withdrawWidth + 10.0, y: contentHeight), size: buttonSize) + buttonView.frame = buttonFrame + } + } + + contentHeight += buttonSize.height } contentHeight += sideInset diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift index 09af2d82c9..1c836183e1 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsStatisticsScreen.swift @@ -30,8 +30,9 @@ final class StarsStatisticsScreenComponent: Component { let revenueContext: StarsRevenueStatsContext let transactionsContext: StarsTransactionsContext let openTransaction: (StarsContext.State.Transaction) -> Void - let buy: () -> Void + let withdraw: () -> Void let showTimeoutTooltip: (Int32) -> Void + let buyAds: () -> Void init( context: AccountContext, @@ -39,16 +40,18 @@ final class StarsStatisticsScreenComponent: Component { revenueContext: StarsRevenueStatsContext, transactionsContext: StarsTransactionsContext, openTransaction: @escaping (StarsContext.State.Transaction) -> Void, - buy: @escaping () -> Void, - showTimeoutTooltip: @escaping (Int32) -> Void + withdraw: @escaping () -> Void, + showTimeoutTooltip: @escaping (Int32) -> Void, + buyAds: @escaping () -> Void ) { self.context = context self.peerId = peerId self.revenueContext = revenueContext self.transactionsContext = transactionsContext self.openTransaction = openTransaction - self.buy = buy + self.withdraw = withdraw self.showTimeoutTooltip = showTimeoutTooltip + self.buyAds = buyAds } static func ==(lhs: StarsStatisticsScreenComponent, rhs: StarsStatisticsScreenComponent) -> Bool { @@ -474,7 +477,7 @@ final class StarsStatisticsScreenComponent: Component { actionAvailable: true, actionIsEnabled: self.starsState?.balances.withdrawEnabled ?? true, actionCooldownUntilTimestamp: self.starsState?.balances.nextWithdrawalTimestamp, - buy: { [weak self] in + action: { [weak self] in guard let self, let component = self.component else { return } @@ -486,11 +489,17 @@ final class StarsStatisticsScreenComponent: Component { if remainingCooldownSeconds > 0 { component.showTimeoutTooltip(cooldownUntilTimestamp) } else { - component.buy() + component.withdraw() } } else { - component.buy() + component.withdraw() } + }, + buyAds: { [weak self] in + guard let self, let component = self.component else { + return + } + component.buyAds() } ) ))] @@ -621,6 +630,7 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { self.transactionsContext = context.engine.payments.peerStarsTransactionsContext(subject: .peer(peerId), mode: .all) var withdrawImpl: (() -> Void)? + var buyAdsImpl: (() -> Void)? var showTimeoutTooltipImpl: ((Int32) -> Void)? var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)? super.init(context: context, component: StarsStatisticsScreenComponent( @@ -631,11 +641,14 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { openTransaction: { transaction in openTransactionImpl?(transaction) }, - buy: { + withdraw: { withdrawImpl?() }, showTimeoutTooltip: { timestamp in showTimeoutTooltipImpl?(timestamp) + }, + buyAds: { + buyAdsImpl?() } ), navigationBarAppearance: .transparent) @@ -756,6 +769,17 @@ public final class StarsStatisticsScreen: ViewControllerComponentContainer { } } + buyAdsImpl = { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let _ = (context.engine.peers.requestStarsRevenueAdsAccountlUrl(peerId: peerId) + |> deliverOnMainQueue).startStandalone(next: { url in + guard let url else { + return + } + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) + }) + } + self.transactionsContext.loadMore() } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 8142f627b2..cd66fc1390 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -525,12 +525,13 @@ final class StarsTransactionsScreenComponent: Component { actionTitle: environment.strings.Stars_Intro_Buy, actionAvailable: !premiumConfiguration.areStarsDisabled, actionIsEnabled: true, - buy: { [weak self] in + action: { [weak self] in guard let self, let component = self.component else { return } component.buy() - } + }, + buyAds: nil ) ))] )),