Various improvements

This commit is contained in:
Ilya Laktyushin 2024-03-25 22:36:51 +04:00
parent 3d6c9b1745
commit 54a90be1f4
11 changed files with 235 additions and 39 deletions

View File

@ -11683,6 +11683,7 @@ Sorry for the inconvenience.";
"Monetization.OverviewTitle" = "PROCEEDS OVERVIEW";
"Monetization.BalanceTitle" = "AVAILABLE BALANCE";
"Monetization.BalanceInfo" = "You will be able to collect rewards using Fragment, a third-party platform used by the advertizer to pay for the ad. [Learn More >]()";
"Monetization.BalanceInfo_URL" = "https://telegram.org";
"Monetization.BalanceWithdraw" = "Withdraw via Fragment";
"Monetization.TransactionsTitle" = "TRANSACTION HISTORY";
@ -11695,6 +11696,8 @@ Sorry for the inconvenience.";
"Monetization.Transaction.Refund" = "Refund";
"Monetization.Transaction.Pending" = "Pending";
"Monetization.Transaction.Failed" = "Not Completed";
"Monetization.Transaction.ShowMoreTransactions_1" = "Show %@ More Transaction";
"Monetization.Transaction.ShowMoreTransactions_any" = "Show %@ More Transactions";
"Monetization.SwitchOffAds" = "Switch off Ads";
"Monetization.SwitchOffAdsInfo" = "You will not be eligible for any rewards if you switch off ads.";
@ -11723,4 +11726,6 @@ Sorry for the inconvenience.";
"Monetization.Intro.Info.Title" = "What's #TON?";
"Monetization.Intro.Info.Text" = "TON is a blockchain platform and cryptocurrency that Telegram uses for its record scalability and ultra low commissions on transactions. [Learn More >]()";
"Monetization.Intro.Info.Text_URL" = "https://ton.org";
"Monetization.Intro.Understood" = "Understood";

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import TelegramPresentationData
import SwitchNode
import AppBundle
import ComponentFlow
public enum ItemListSwitchItemNodeType {
case regular
@ -17,6 +18,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
let icon: UIImage?
let title: String
let text: String?
let titleBadgeComponent: AnyComponent<Empty>?
let value: Bool
let type: ItemListSwitchItemNodeType
let enableInteractiveChanges: Bool
@ -31,11 +33,12 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
let activatedWhileDisabled: () -> Void
public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, text: String? = nil, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) {
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, title: String, text: String? = nil, titleBadgeComponent: AnyComponent<Empty>? = nil, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, displayLocked: Bool = false, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) {
self.presentationData = presentationData
self.icon = icon
self.title = title
self.text = text
self.titleBadgeComponent = titleBadgeComponent
self.value = value
self.type = type
self.enableInteractiveChanges = enableInteractiveChanges
@ -134,6 +137,8 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
private let switchGestureNode: ASDisplayNode
private var disabledOverlayNode: ASDisplayNode?
private var titleBadgeComponentView: ComponentView<Empty>?
private var lockedIconNode: ASImageNode?
private let activateArea: AccessibilityAreaNode
@ -471,6 +476,32 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
lockedIconNode.removeFromSupernode()
}
if let component = item.titleBadgeComponent {
let componentView: ComponentView<Empty>
if let current = strongSelf.titleBadgeComponentView {
componentView = current
} else {
componentView = ComponentView<Empty>()
strongSelf.titleBadgeComponentView = componentView
}
let badgeSize = componentView.update(
transition: .immediate,
component: component,
environment: {},
containerSize: contentSize
)
if let view = componentView.view {
if view.superview == nil {
strongSelf.view.addSubview(view)
}
view.frame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + 7.0, y: floor((contentSize.height - badgeSize.height) / 2.0)), size: badgeSize)
}
} else if let componentView = strongSelf.titleBadgeComponentView {
strongSelf.titleBadgeComponentView = nil
componentView.view?.removeFromSuperview()
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: 44.0 + UIScreenPixel + UIScreenPixel))
}
})

View File

@ -39,6 +39,7 @@ swift_library(
"//submodules/TextFormat",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
"//submodules/Components/SheetComponent",

View File

@ -23,8 +23,11 @@ import ItemListPeerActionItem
import PremiumUI
import StoryContainerScreen
import TelegramNotices
import ComponentFlow
import BoostLevelIconComponent
private let initialBoostersDisplayedLimit: Int32 = 5
private let initialTransactionsDisplayedLimit: Int32 = 5
private final class ChannelStatsControllerArguments {
let context: AccountContext
@ -42,13 +45,14 @@ private final class ChannelStatsControllerArguments {
let requestWithdraw: () -> Void
let openMonetizationIntro: () -> Void
let openMonetizationInfo: () -> Void
let openTransaction: (RevenueStatsTransactionsContext.State.Transaction) -> Void
let expandTransactions: () -> Void
let updateCpmEnabled: (Bool) -> Void
let presentCpmLocked: () -> Void
let dismissInput: () -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, 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, requestWithdraw: @escaping () -> Void, openMonetizationIntro: @escaping () -> Void, openTransaction: @escaping (RevenueStatsTransactionsContext.State.Transaction) -> Void, expandTransactions: @escaping () -> Void, updateCpmEnabled: @escaping (Bool) -> Void, presentCpmLocked: @escaping () -> Void, dismissInput: @escaping () -> Void) {
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, 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, requestWithdraw: @escaping () -> Void, openMonetizationIntro: @escaping () -> Void, openMonetizationInfo: @escaping () -> Void, openTransaction: @escaping (RevenueStatsTransactionsContext.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
@ -63,6 +67,7 @@ private final class ChannelStatsControllerArguments {
self.updateGiftsSelected = updateGiftsSelected
self.requestWithdraw = requestWithdraw
self.openMonetizationIntro = openMonetizationIntro
self.openMonetizationInfo = openMonetizationInfo
self.openTransaction = openTransaction
self.expandTransactions = expandTransactions
self.updateCpmEnabled = updateCpmEnabled
@ -225,8 +230,9 @@ private enum StatsEntry: ItemListNodeEntry {
case adsTransactionsTitle(PresentationTheme, String)
case adsTransaction(Int32, PresentationTheme, RevenueStatsTransactionsContext.State.Transaction)
case adsTransactionsExpand(PresentationTheme, String)
case adsCpmToggle(PresentationTheme, String, Bool?)
case adsCpmToggle(PresentationTheme, String, Int32, Bool?)
case adsCpmInfo(PresentationTheme, String)
var section: ItemListSectionId {
@ -281,7 +287,7 @@ private enum StatsEntry: ItemListNodeEntry {
return StatsSection.adsProceeds.rawValue
case .adsBalanceTitle, .adsBalance, .adsBalanceInfo:
return StatsSection.adsBalance.rawValue
case .adsTransactionsTitle, .adsTransaction:
case .adsTransactionsTitle, .adsTransaction, .adsTransactionsExpand:
return StatsSection.adsTransactions.rawValue
case .adsCpmToggle, .adsCpmInfo:
return StatsSection.adsCpm.rawValue
@ -404,10 +410,12 @@ private enum StatsEntry: ItemListNodeEntry {
return 20010
case let .adsTransaction(index, _, _):
return 20011 + index
case .adsTransactionsExpand:
return 30000
case .adsCpmToggle:
return 21000
return 30001
case .adsCpmInfo:
return 21002
return 30002
}
}
@ -755,8 +763,14 @@ private enum StatsEntry: ItemListNodeEntry {
} else {
return false
}
case let .adsCpmToggle(lhsTheme, lhsText, lhsValue):
if case let .adsCpmToggle(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
case let .adsTransactionsExpand(lhsTheme, lhsText):
if case let .adsTransactionsExpand(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .adsCpmToggle(lhsTheme, lhsText, lhsMinLevel, lhsValue):
if case let .adsCpmToggle(rhsTheme, rhsText, rhsMinLevel, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsMinLevel == rhsMinLevel, lhsValue == rhsValue {
return true
} else {
return false
@ -806,7 +820,6 @@ private enum StatsEntry: ItemListNodeEntry {
let .boostersInfo(_, text),
let .boostLinkInfo(_, text),
let .boostGiftsInfo(_, text),
let .adsBalanceInfo(_, text),
let .adsCpmInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .overview(_, stats):
@ -961,6 +974,10 @@ private enum StatsEntry: ItemListNodeEntry {
sectionId: self.section,
style: .blocks
)
case let .adsBalanceInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
arguments.openMonetizationInfo()
})
case let .adsTransaction(_, theme, transaction):
let font = Font.regular(presentationData.fontSize.itemListBaseFontSize)
let smallLabelFont = Font.regular(floor(presentationData.fontSize.itemListBaseFontSize / 17.0 * 13.0))
@ -973,22 +990,22 @@ private enum StatsEntry: ItemListNodeEntry {
switch transaction {
case let .proceeds(_, fromDate, toDate):
title = NSAttributedString(string: presentationData.strings.Monetization_Transaction_Proceeds, font: font, textColor: theme.list.itemPrimaryTextColor)
detailText = "\(stringForMediumDate(timestamp: fromDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)) \(stringForMediumDate(timestamp: toDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat))"
detailText = "\(stringForMediumCompactDate(timestamp: fromDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)) \(stringForMediumCompactDate(timestamp: toDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat))"
case let .withdrawal(status, _, date, provider, _, _):
title = NSAttributedString(string: presentationData.strings.Monetization_Transaction_Withdrawal(provider).string, font: font, textColor: theme.list.itemPrimaryTextColor)
labelColor = theme.list.itemDestructiveColor
switch status {
case .succeed:
detailText = stringForMediumDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
detailText = stringForMediumCompactDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
case .failed:
detailText = stringForMediumDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat) + " \(presentationData.strings.Monetization_Transaction_Failed)"
detailText = stringForMediumCompactDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat) + " \(presentationData.strings.Monetization_Transaction_Failed)"
detailColor = .destructive
case .pending:
detailText = presentationData.strings.Monetization_Transaction_Pending
}
case let .refund(_, date, _):
title = NSAttributedString(string: presentationData.strings.Monetization_Transaction_Refund, font: font, textColor: theme.list.itemPrimaryTextColor)
detailText = stringForMediumDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
detailText = stringForMediumCompactDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
}
let label = amountAttributedString(formatBalanceText(transaction.amount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator, showPlus: true), integralFont: font, fractionalFont: smallLabelFont, color: labelColor).mutableCopy() as! NSMutableAttributedString
@ -997,8 +1014,19 @@ private enum StatsEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, title: "", attributedTitle: title, label: "", attributedLabel: label, labelStyle: .coloredText(labelColor), additionalDetailLabel: detailText, additionalDetailLabelColor: detailColor, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
arguments.openTransaction(transaction)
})
case let .adsCpmToggle(_, title, value):
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value == true, enableInteractiveChanges: value != nil, enabled: true, displayLocked: value == nil, sectionId: self.section, style: .blocks, updated: { updatedValue in
case let .adsTransactionsExpand(theme, title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandTransactions()
})
case let .adsCpmToggle(_, title, minLevel, value):
var badgeComponent: AnyComponent<Empty>?
if value == nil {
badgeComponent = AnyComponent(BoostLevelIconComponent(
strings: presentationData.strings,
level: Int(minLevel)
))
}
return ItemListSwitchItem(presentationData: presentationData, title: title, titleBadgeComponent: badgeComponent, value: value == true, enableInteractiveChanges: value != nil, enabled: true, displayLocked: value == nil, sectionId: self.section, style: .blocks, updated: { updatedValue in
if value != nil {
arguments.updateCpmEnabled(updatedValue)
} else {
@ -1022,19 +1050,25 @@ private struct ChannelStatsControllerState: Equatable {
let boostersExpanded: Bool
let moreBoostersDisplayed: Int32
let giftsSelected: Bool
let transactionsExpanded: Bool
let moreTransactionsDisplayed: Int32
init() {
self.section = .stats
self.boostersExpanded = false
self.moreBoostersDisplayed = 0
self.giftsSelected = false
self.transactionsExpanded = false
self.moreTransactionsDisplayed = 0
}
init(section: ChannelStatsSection, boostersExpanded: Bool, moreBoostersDisplayed: Int32, giftsSelected: Bool) {
init(section: ChannelStatsSection, boostersExpanded: Bool, moreBoostersDisplayed: Int32, giftsSelected: Bool, transactionsExpanded: Bool, moreTransactionsDisplayed: Int32) {
self.section = section
self.boostersExpanded = boostersExpanded
self.moreBoostersDisplayed = moreBoostersDisplayed
self.giftsSelected = giftsSelected
self.transactionsExpanded = transactionsExpanded
self.moreTransactionsDisplayed = moreTransactionsDisplayed
}
static func ==(lhs: ChannelStatsControllerState, rhs: ChannelStatsControllerState) -> Bool {
@ -1050,23 +1084,37 @@ private struct ChannelStatsControllerState: Equatable {
if lhs.giftsSelected != rhs.giftsSelected {
return false
}
if lhs.transactionsExpanded != rhs.transactionsExpanded {
return false
}
if lhs.moreTransactionsDisplayed != rhs.moreTransactionsDisplayed {
return false
}
return true
}
func withUpdatedSection(_ section: ChannelStatsSection) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: self.giftsSelected)
return ChannelStatsControllerState(section: section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: self.giftsSelected, transactionsExpanded: self.transactionsExpanded, moreTransactionsDisplayed: self.moreTransactionsDisplayed)
}
func withUpdatedBoostersExpanded(_ boostersExpanded: Bool) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: self.giftsSelected)
return ChannelStatsControllerState(section: self.section, boostersExpanded: boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: self.giftsSelected, transactionsExpanded: self.transactionsExpanded, moreTransactionsDisplayed: self.moreTransactionsDisplayed)
}
func withUpdatedMoreBoostersDisplayed(_ moreBoostersDisplayed: Int32) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: moreBoostersDisplayed, giftsSelected: self.giftsSelected)
return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: moreBoostersDisplayed, giftsSelected: self.giftsSelected, transactionsExpanded: self.transactionsExpanded, moreTransactionsDisplayed: self.moreTransactionsDisplayed)
}
func withUpdatedGiftsSelected(_ giftsSelected: Bool) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: giftsSelected)
return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: giftsSelected, transactionsExpanded: self.transactionsExpanded, moreTransactionsDisplayed: self.moreTransactionsDisplayed)
}
func withUpdatedTransactionsExpanded(_ transactionsExpanded: Bool) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: self.giftsSelected, transactionsExpanded: self.transactionsExpanded, moreTransactionsDisplayed: self.moreTransactionsDisplayed)
}
func withUpdatedMoreTransactionsDisplayed(_ moreTransactionsDisplayed: Int32) -> ChannelStatsControllerState {
return ChannelStatsControllerState(section: self.section, boostersExpanded: self.boostersExpanded, moreBoostersDisplayed: self.moreBoostersDisplayed, giftsSelected: self.giftsSelected, transactionsExpanded: self.transactionsExpanded, moreTransactionsDisplayed: moreTransactionsDisplayed)
}
}
@ -1309,9 +1357,11 @@ private func boostsEntries(
private func monetizationEntries(
presentationData: PresentationData,
state: ChannelStatsControllerState,
peer: EnginePeer?,
data: RevenueStats,
boostData: ChannelBoostStatus?,
transactions: RevenueStatsTransactionsContext.State,
transactionsInfo: RevenueStatsTransactionsContext.State,
adsRestricted: Bool,
animatedEmojis: [String: [StickerPackItem]],
premiumConfiguration: PremiumConfiguration
) -> [StatsEntry] {
@ -1333,25 +1383,50 @@ private func monetizationEntries(
entries.append(.adsProceedsTitle(presentationData.theme, presentationData.strings.Monetization_OverviewTitle))
entries.append(.adsProceedsOverview(presentationData.theme, data, diamond))
var withdrawalAvailable = false
if let peer, case let .channel(channel) = peer, channel.flags.contains(.isCreator) && data.availableBalance > 0 {
withdrawalAvailable = true
}
entries.append(.adsBalanceTitle(presentationData.theme, presentationData.strings.Monetization_BalanceTitle))
entries.append(.adsBalance(presentationData.theme, data, false, diamond))
entries.append(.adsBalance(presentationData.theme, data, withdrawalAvailable, diamond))
entries.append(.adsBalanceInfo(presentationData.theme, presentationData.strings.Monetization_BalanceInfo))
if !transactions.transactions.isEmpty {
if !transactionsInfo.transactions.isEmpty {
entries.append(.adsTransactionsTitle(presentationData.theme, presentationData.strings.Monetization_TransactionsTitle))
var transactions = transactionsInfo.transactions
var limit: Int32
if state.transactionsExpanded {
limit = 25 + state.moreTransactionsDisplayed
} else {
limit = initialTransactionsDisplayedLimit
}
transactions = Array(transactions.prefix(Int(limit)))
var i: Int32 = 0
for transaction in transactions.transactions {
for transaction in transactions {
entries.append(.adsTransaction(i, presentationData.theme, transaction))
i += 1
}
if transactions.count < transactionsInfo.count {
let moreCount: Int32
if !state.transactionsExpanded {
moreCount = min(20, transactionsInfo.count - Int32(transactions.count))
} else {
moreCount = min(500, transactionsInfo.count - Int32(transactions.count))
}
entries.append(.adsTransactionsExpand(presentationData.theme, presentationData.strings.Monetization_Transaction_ShowMoreTransactions(moreCount)))
}
}
var switchOffAdds: Bool? = nil
if let boostData, boostData.level >= premiumConfiguration.minChannelRestrictAdsLevel {
switchOffAdds = false
switchOffAdds = adsRestricted
}
entries.append(.adsCpmToggle(presentationData.theme, presentationData.strings.Monetization_SwitchOffAds, switchOffAdds))
entries.append(.adsCpmToggle(presentationData.theme, presentationData.strings.Monetization_SwitchOffAds, premiumConfiguration.minChannelRestrictAdsLevel, switchOffAdds))
entries.append(.adsCpmInfo(presentationData.theme, presentationData.strings.Monetization_SwitchOffAdsInfo))
return entries
@ -1374,6 +1449,7 @@ private func channelStatsControllerEntries(
animatedEmojis: [String: [StickerPackItem]],
revenueState: RevenueStats?,
revenueTransactions: RevenueStatsTransactionsContext.State,
adsRestricted: Bool,
premiumConfiguration: PremiumConfiguration
) -> [StatsEntry] {
switch state.section {
@ -1406,9 +1482,11 @@ private func channelStatsControllerEntries(
return monetizationEntries(
presentationData: presentationData,
state: state,
peer: peer,
data: revenueState,
boostData: boostData,
transactions: revenueTransactions,
transactionsInfo: revenueTransactions,
adsRestricted: adsRestricted,
animatedEmojis: animatedEmojis,
premiumConfiguration: premiumConfiguration
)
@ -1418,8 +1496,8 @@ private func channelStatsControllerEntries(
}
public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, section: ChannelStatsSection = .stats, boostStatus: ChannelBoostStatus? = nil, boostStatusUpdated: ((ChannelBoostStatus) -> Void)? = nil) -> ViewController {
let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false))
let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false, transactionsExpanded: false, moreTransactionsDisplayed: 0), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false, transactionsExpanded: false, moreTransactionsDisplayed: 0))
let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in
statePromise.set(stateValue.modify { f($0) })
}
@ -1471,6 +1549,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true)
let revenueContext = RevenueStatsContext(postbox: context.account.postbox, network: context.account.network, peerId: peerId)
let revenueState = Promise<RevenueStatsContextState?>()
revenueState.set(.single(nil) |> then(revenueContext.state |> map(Optional.init)))
let revenueTransactions = RevenueStatsTransactionsContext(account: context.account, peerId: peerId)
@ -1612,13 +1692,25 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let controller = MonetizationIntroScreen(context: context, openMore: {})
pushImpl?(controller)
},
openMonetizationInfo: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.Monetization_BalanceInfo_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
},
openTransaction: { transaction in
openTransactionImpl?(transaction)
},
expandTransactions: {
updateState { state in
if state.transactionsExpanded {
return state.withUpdatedMoreTransactionsDisplayed(state.moreTransactionsDisplayed + 50)
} else {
return state.withUpdatedTransactionsExpanded(true)
}
}
revenueTransactions.loadMore()
},
updateCpmEnabled: { value in
let _ = context.engine.peers.updateChannelRestrictAdMessages(peerId: peerId, value: value ? .restrict(minCpm: nil) : .unrestrict).start()
},
presentCpmLocked: {
let _ = combineLatest(
@ -1658,6 +1750,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let peer = Promise<EnginePeer?>()
peer.set(context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)))
let adsRestricted = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.AdsRestricted(id: peerId))
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
let previousData = Atomic<ChannelStats?>(value: nil)
@ -1672,13 +1766,14 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
boostDataPromise.get(),
boostsContext.state,
giftsContext.state,
revenueContext.state,
revenueState.get(),
revenueTransactions.state,
adsRestricted,
longLoadingSignal,
context.animatedEmojiStickers
)
|> deliverOnMainQueue
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, longLoading, animatedEmojiStickers -> (ItemListControllerState, (ItemListNodeState, Any)) in
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, adsRestricted, longLoading, animatedEmojiStickers -> (ItemListControllerState, (ItemListNodeState, Any)) in
var isGroup = false
if let peer, case let .channel(channel) = peer, case .group = channel.info {
isGroup = true
@ -1700,8 +1795,9 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
}
case .monetization:
emptyStateItem = nil
// emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
if revenueState == nil {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
}
}
var existingGroupingKeys = Set<Int64>()
@ -1762,7 +1858,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
}
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, animatedEmojis: animatedEmojiStickers, revenueState: revenueState.stats, revenueTransactions: revenueTransactions, premiumConfiguration: premiumConfiguration), 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, animatedEmojis: animatedEmojiStickers, revenueState: revenueState?.stats, revenueTransactions: revenueTransactions, adsRestricted: adsRestricted, premiumConfiguration: premiumConfiguration), style: .blocks, emptyStateItem: emptyStateItem, headerItem: headerItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments))
}
@ -1770,6 +1866,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
actionsDisposable.dispose()
let _ = statsContext.state
let _ = storyList.state
let _ = revenueContext.state
}
let controller = ItemListController(context: context, state: signal)

View File

@ -224,6 +224,7 @@ private final class SheetContent: CombinedComponent {
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let infoString = strings.Monetization_Intro_Info_Text
let infoAttributedString = parseMarkdownIntoAttributedString(infoString, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = infoAttributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
@ -234,7 +235,17 @@ private final class SheetContent: CombinedComponent {
text: .plain(infoAttributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2
lineSpacing: 0.2,
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { _, _ in
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Monetization_Intro_Info_Text_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
),
availableSize: CGSize(width: context.availableSize.width - (textSideInset + sideInset - 2.0) * 2.0, height: context.availableSize.height),
transition: .immediate

View File

@ -139,7 +139,7 @@ private final class SheetContent: CombinedComponent {
case let .proceeds(amount, fromDate, toDate):
amountString = amountAttributedString(formatBalanceText(amount, decimalSeparator: dateTimeFormat.decimalSeparator, showPlus: true), integralFont: integralFont, fractionalFont: fractionalFont, color: theme.list.itemDisclosureActions.constructive.fillColor).mutableCopy() as! NSMutableAttributedString
amountString.append(NSAttributedString(string: " TON", font: fractionalFont, textColor: theme.list.itemDisclosureActions.constructive.fillColor))
dateString = "\(stringForFullDate(timestamp: fromDate, strings: strings, dateTimeFormat: dateTimeFormat)) \(stringForFullDate(timestamp: toDate, strings: strings, dateTimeFormat: dateTimeFormat))"
dateString = "\(stringForMediumCompactDate(timestamp: fromDate, strings: strings, dateTimeFormat: dateTimeFormat)) \(stringForMediumCompactDate(timestamp: toDate, strings: strings, dateTimeFormat: dateTimeFormat))"
titleString = strings.Monetization_TransactionInfo_Proceeds
buttonTitle = strings.Common_OK
explorerUrl = nil

View File

@ -240,7 +240,9 @@ private final class RevenueStatsTransactionsContextImpl {
return .complete()
}
let offset = lastOffset ?? 0
let request = Api.functions.stats.getBroadcastRevenueTransactions(channel: inputChannel, offset: offset, limit: 50)
let limit: Int32 = lastOffset == nil ? 25 : 50
let request = Api.functions.stats.getBroadcastRevenueTransactions(channel: inputChannel, offset: offset, limit: limit)
let signal: Signal<Api.stats.BroadcastRevenueTransactions, MTRpcError>
if let statsDatacenterId = statsDatacenterId, account.network.datacenterId != statsDatacenterId {
signal = account.network.download(datacenterId: Int(statsDatacenterId), isMedia: false, tag: nil)

View File

@ -1812,5 +1812,33 @@ public extension TelegramEngine.EngineData.Item {
}
}
}
public struct AdsRestricted: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Bool
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else {
preconditionFailure()
}
if let cachedData = view.cachedPeerData as? CachedChannelData {
return cachedData.flags.contains(.adsRestricted)
} else {
return false
}
}
}
}
}

View File

@ -58,6 +58,26 @@ public func getDateTimeComponents(timestamp: Int32) -> (day: Int32, month: Int32
return (timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year, timeinfo.tm_hour, timeinfo.tm_min)
}
public func stringForMediumCompactDate(timestamp: Int32, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> String {
var t: time_t = Int(timestamp)
var timeinfo = tm()
localtime_r(&t, &timeinfo);
let day = timeinfo.tm_mday
let month = monthAtIndex(Int(timeinfo.tm_mon), strings: strings)
let timeString = stringForShortTimestamp(hours: Int32(timeinfo.tm_hour), minutes: Int32(timeinfo.tm_min), dateTimeFormat: dateTimeFormat)
let dateString: String
switch dateTimeFormat.dateFormat {
case .monthFirst:
dateString = String(format: "%@ %02d %@", month, day, timeString)
case .dayFirst:
dateString = String(format: "%02d %@ %@", day, month, timeString)
}
return dateString
}
public func stringForMediumDate(timestamp: Int32, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, withTime: Bool = true) -> String {
var t: time_t = Int(timestamp)
var timeinfo = tm()

View File

@ -107,7 +107,7 @@ public func stringForMonth(strings: PresentationStrings, month: Int32, ofYear ye
}
}
private func monthAtIndex(_ index: Int, strings: PresentationStrings) -> String {
func monthAtIndex(_ index: Int, strings: PresentationStrings) -> String {
switch index {
case 0:
return strings.Month_ShortJanuary

View File

@ -32,6 +32,7 @@ final class PeerInfoBirthdayOverlay: ASDisplayNode {
self.setupAnimations(size: size, birthday: birthday, sourceRect: sourceRect)
Queue.mainQueue().after(0.1) {
HapticFeedback().success()
self.view.addSubview(ConfettiView(frame: CGRect(origin: .zero, size: size)))
}
}