mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stars ref
This commit is contained in:
parent
6c06c0e805
commit
1322c4364e
@ -881,6 +881,38 @@ public final class BotPreviewEditorTransitionOut {
|
||||
public protocol MiniAppListScreenInitialData: AnyObject {
|
||||
}
|
||||
|
||||
public enum JoinAffiliateProgramScreenMode {
|
||||
public final class Join {
|
||||
public let initialTargetPeer: EnginePeer
|
||||
public let canSelectTargetPeer: Bool
|
||||
public let completion: (EnginePeer) -> Void
|
||||
|
||||
public init(initialTargetPeer: EnginePeer, canSelectTargetPeer: Bool, completion: @escaping (EnginePeer) -> Void) {
|
||||
self.initialTargetPeer = initialTargetPeer
|
||||
self.canSelectTargetPeer = canSelectTargetPeer
|
||||
self.completion = completion
|
||||
}
|
||||
}
|
||||
|
||||
public final class Active {
|
||||
public let targetPeer: EnginePeer
|
||||
public let link: String
|
||||
public let userCount: Int
|
||||
public let copyLink: () -> Void
|
||||
|
||||
public init(targetPeer: EnginePeer, link: String, userCount: Int, copyLink: @escaping () -> Void) {
|
||||
self.targetPeer = targetPeer
|
||||
self.link = link
|
||||
self.userCount = userCount
|
||||
self.copyLink = copyLink
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case join(Join)
|
||||
case active(Active)
|
||||
}
|
||||
|
||||
public protocol SharedAccountContext: AnyObject {
|
||||
var sharedContainerPath: String { get }
|
||||
var basePath: String { get }
|
||||
@ -1074,6 +1106,8 @@ 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 makeDebugSettingsController(context: AccountContext?) -> ViewController?
|
||||
|
||||
func navigateToCurrentCall()
|
||||
|
@ -33,6 +33,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -9,6 +9,7 @@ import AvatarNode
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TextNodeWithEntities
|
||||
import ListItemComponentAdaptor
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
||||
|
||||
@ -46,7 +47,7 @@ public enum ItemListDisclosureItemDetailLabelColor {
|
||||
case destructive
|
||||
}
|
||||
|
||||
public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
|
||||
let presentationData: ItemListPresentationData
|
||||
let icon: UIImage?
|
||||
let context: AccountContext?
|
||||
@ -56,6 +57,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
let titleColor: ItemListDisclosureItemTitleColor
|
||||
let titleFont: ItemListDisclosureItemTitleFont
|
||||
let titleIcon: UIImage?
|
||||
let titleBadge: String?
|
||||
let enabled: Bool
|
||||
let label: String
|
||||
let attributedLabel: NSAttributedString?
|
||||
@ -71,7 +73,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
public let tag: ItemListItemTag?
|
||||
public let shimmeringIndex: Int?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, noInsets: Bool = false, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
|
||||
public init(presentationData: ItemListPresentationData, icon: UIImage? = nil, context: AccountContext? = nil, iconPeer: EnginePeer? = nil, title: String, attributedTitle: NSAttributedString? = nil, enabled: Bool = true, titleColor: ItemListDisclosureItemTitleColor = .primary, titleFont: ItemListDisclosureItemTitleFont = .regular, titleIcon: UIImage? = nil, titleBadge: String? = nil, label: String, attributedLabel: NSAttributedString? = nil, labelStyle: ItemListDisclosureLabelStyle = .text, additionalDetailLabel: String? = nil, additionalDetailLabelColor: ItemListDisclosureItemDetailLabelColor = .generic, sectionId: ItemListSectionId, style: ItemListStyle, disclosureStyle: ItemListDisclosureStyle = .arrow, noInsets: Bool = false, action: (() -> Void)?, clearHighlightAutomatically: Bool = true, tag: ItemListItemTag? = nil, shimmeringIndex: Int? = nil) {
|
||||
self.presentationData = presentationData
|
||||
self.icon = icon
|
||||
self.context = context
|
||||
@ -81,6 +83,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
self.titleColor = titleColor
|
||||
self.titleFont = titleFont
|
||||
self.titleIcon = titleIcon
|
||||
self.titleBadge = titleBadge
|
||||
self.enabled = enabled
|
||||
self.labelStyle = labelStyle
|
||||
self.label = label
|
||||
@ -140,6 +143,27 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
||||
self.action?()
|
||||
}
|
||||
}
|
||||
|
||||
public func item() -> ListViewItem {
|
||||
return self
|
||||
}
|
||||
|
||||
public static func ==(lhs: ItemListDisclosureItem, rhs: ItemListDisclosureItem) -> Bool {
|
||||
if lhs.presentationData != rhs.presentationData {
|
||||
return false
|
||||
}
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.label != rhs.label {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private let badgeFont = Font.regular(15.0)
|
||||
@ -162,6 +186,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let labelBadgeNode: ASImageNode
|
||||
let labelImageNode: ASImageNode
|
||||
|
||||
var titleBadgeNode: ASImageNode?
|
||||
var titleBadgeTextNode: TextNode?
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: ItemListDisclosureItem?
|
||||
@ -260,6 +287,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let makeAdditionalDetailLabelLayout = TextNode.asyncLayout(self.additionalDetailLabelNode)
|
||||
|
||||
let makeTitleBadgeTextNodeLayout = TextNode.asyncLayout(self.titleBadgeTextNode)
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
let currentHasBadge = self.labelBadgeNode.image != nil
|
||||
@ -374,6 +403,13 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
maxTitleWidth -= 12.0
|
||||
}
|
||||
|
||||
var titleBadgeTextNodeLayout: (TextNodeLayout, () -> TextNode)?
|
||||
if let titleBadge = item.titleBadge {
|
||||
let titleBadgeTextNodeLayoutValue = makeTitleBadgeTextNodeLayout(TextNodeLayoutArguments(attributedString: item.attributedTitle ?? NSAttributedString(string: titleBadge, font: Font.medium(11.0), textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
titleBadgeTextNodeLayout = titleBadgeTextNodeLayoutValue
|
||||
maxTitleWidth -= 5.0 + titleBadgeTextNodeLayoutValue.0.size.width
|
||||
}
|
||||
|
||||
let titleArguments = TextNodeLayoutArguments(attributedString: item.attributedTitle ?? NSAttributedString(string: item.title, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: item.attributedTitle != nil ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())
|
||||
let (titleLayoutAndApply) = item.context == nil ? makeTitleLayout(titleArguments) : nil
|
||||
let (titleWithEntitiesLayoutAndApply) = item.context != nil ? makeTitleWithEntitiesLayout(titleArguments) : nil
|
||||
@ -680,6 +716,41 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
additionalDetailLabelNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let (badgeTextLayout, badgeTextApply) = titleBadgeTextNodeLayout {
|
||||
let titleBadgeNode: ASImageNode
|
||||
if let current = strongSelf.titleBadgeNode {
|
||||
titleBadgeNode = current
|
||||
} else {
|
||||
titleBadgeNode = ASImageNode()
|
||||
strongSelf.titleBadgeNode = titleBadgeNode
|
||||
strongSelf.addSubnode(titleBadgeNode)
|
||||
|
||||
titleBadgeNode.image = generateFilledRoundedRectImage(size: CGSize(width: 16.0, height: 16.0), cornerRadius: 5.0, color: item.presentationData.theme.list.itemCheckColors.fillColor)?.stretchableImage(withLeftCapWidth: 6, topCapHeight: 6)
|
||||
}
|
||||
|
||||
let titleBadgeTextNode = badgeTextApply()
|
||||
if titleBadgeTextNode.supernode == nil {
|
||||
strongSelf.addSubnode(titleBadgeTextNode)
|
||||
}
|
||||
let badgeSideInset: CGFloat = 5.0
|
||||
let badgeVerticalInset: CGFloat = 2.0
|
||||
let badgeSize = CGSize(width: badgeTextLayout.size.width + badgeSideInset * 2.0, height: badgeTextLayout.size.height + badgeVerticalInset * 2.0)
|
||||
let titleBadgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + 5.0, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize)
|
||||
let titleBadgeTextFrame = CGRect(origin: CGPoint(x: titleBadgeFrame.minX + badgeSideInset, y: titleBadgeFrame.minY + badgeVerticalInset), size: badgeTextLayout.size)
|
||||
|
||||
titleBadgeNode.frame = titleBadgeFrame
|
||||
titleBadgeTextNode.frame = titleBadgeTextFrame
|
||||
} else {
|
||||
if let titleBadgeTextNode = strongSelf.titleBadgeTextNode {
|
||||
strongSelf.titleBadgeTextNode = nil
|
||||
titleBadgeTextNode.removeFromSupernode()
|
||||
}
|
||||
if let titleBadgeNode = strongSelf.titleBadgeNode {
|
||||
strongSelf.titleBadgeNode = nil
|
||||
titleBadgeNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let titleIcon = item.titleIcon {
|
||||
if strongSelf.titleIconNode.supernode == nil {
|
||||
strongSelf.addSubnode(strongSelf.titleIconNode)
|
||||
|
@ -56,9 +56,10 @@ private final class ChannelStatsControllerArguments {
|
||||
let expandTransactions: (Bool) -> Void
|
||||
let updateCpmEnabled: (Bool) -> Void
|
||||
let presentCpmLocked: () -> Void
|
||||
let openEarnStars: () -> 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, 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 (Bool) -> 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, 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 (Bool) -> Void, updateCpmEnabled: @escaping (Bool) -> Void, presentCpmLocked: @escaping () -> Void, openEarnStars: @escaping () -> Void, dismissInput: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.loadDetailedGraph = loadDetailedGraph
|
||||
self.openPostStats = openPostStats
|
||||
@ -83,6 +84,7 @@ private final class ChannelStatsControllerArguments {
|
||||
self.expandTransactions = expandTransactions
|
||||
self.updateCpmEnabled = updateCpmEnabled
|
||||
self.presentCpmLocked = presentCpmLocked
|
||||
self.openEarnStars = openEarnStars
|
||||
self.dismissInput = dismissInput
|
||||
}
|
||||
}
|
||||
@ -119,6 +121,8 @@ private enum StatsSection: Int32 {
|
||||
case adsStarsBalance
|
||||
case adsTransactions
|
||||
case adsCpm
|
||||
|
||||
case earnStars
|
||||
}
|
||||
|
||||
enum StatsPostItem: Equatable {
|
||||
@ -249,6 +253,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case adsStarsBalance(PresentationTheme, StarsRevenueStats, Bool, Bool, Int32?)
|
||||
case adsStarsBalanceInfo(PresentationTheme, String)
|
||||
|
||||
case earnStarsInfo
|
||||
case adsTransactionsTitle(PresentationTheme, String)
|
||||
case adsTransactionsTabs(PresentationTheme, String, String, Bool)
|
||||
case adsTransaction(Int32, PresentationTheme, RevenueStatsTransactionsContext.State.Transaction)
|
||||
@ -314,6 +319,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return StatsSection.adsTonBalance.rawValue
|
||||
case .adsStarsBalanceTitle, .adsStarsBalance, .adsStarsBalanceInfo:
|
||||
return StatsSection.adsStarsBalance.rawValue
|
||||
case .earnStarsInfo:
|
||||
return StatsSection.earnStars.rawValue
|
||||
case .adsTransactionsTitle, .adsTransactionsTabs, .adsTransaction, .adsStarsTransaction, .adsTransactionsExpand:
|
||||
return StatsSection.adsTransactions.rawValue
|
||||
case .adsCpmToggle, .adsCpmInfo:
|
||||
@ -445,14 +452,16 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return 20014
|
||||
case .adsStarsBalanceInfo:
|
||||
return 20015
|
||||
case .adsTransactionsTitle:
|
||||
case .earnStarsInfo:
|
||||
return 20016
|
||||
case .adsTransactionsTabs:
|
||||
case .adsTransactionsTitle:
|
||||
return 20017
|
||||
case .adsTransactionsTabs:
|
||||
return 20018
|
||||
case let .adsTransaction(index, _, _):
|
||||
return 20018 + index
|
||||
return 20019 + index
|
||||
case let .adsStarsTransaction(index, _, _):
|
||||
return 30017 + index
|
||||
return 30018 + index
|
||||
case .adsTransactionsExpand:
|
||||
return 40000
|
||||
case .adsCpmToggle:
|
||||
@ -830,6 +839,12 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .earnStarsInfo:
|
||||
if case .earnStarsInfo = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .adsTransactionsTitle(lhsTheme, lhsText):
|
||||
if case let .adsTransactionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -1203,6 +1218,11 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
}, activatedWhileDisabled: {
|
||||
arguments.presentCpmLocked()
|
||||
})
|
||||
case .earnStarsInfo:
|
||||
//TODO:localize
|
||||
return ItemListDisclosureItem(presentationData: 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: self.section, style: .blocks, action: {
|
||||
arguments.openEarnStars()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1680,6 +1700,9 @@ private func monetizationEntries(
|
||||
|
||||
if displayStarsTransactions {
|
||||
if !addedTransactionsTabs {
|
||||
//TODO:localize
|
||||
entries.append(.earnStarsInfo)
|
||||
|
||||
entries.append(.adsTransactionsTitle(presentationData.theme, presentationData.strings.Monetization_StarsTransactions.uppercased()))
|
||||
}
|
||||
|
||||
@ -2071,6 +2094,12 @@ public func channelStatsController(
|
||||
pushImpl?(controller)
|
||||
})
|
||||
},
|
||||
openEarnStars: {
|
||||
let _ = (context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: context, peerId: peerId, mode: .connectedPrograms)
|
||||
|> deliverOnMainQueue).startStandalone(next: { initialData in
|
||||
pushImpl?(context.sharedContext.makeAffiliateProgramSetupScreen(context: context, initialData: initialData))
|
||||
})
|
||||
},
|
||||
dismissInput: {
|
||||
dismissInputImpl?()
|
||||
})
|
||||
|
@ -907,8 +907,6 @@ func _internal_connectStarRefBot(account: Account, id: EnginePeer.Id, botId: Eng
|
||||
}
|
||||
}
|
||||
|
||||
//payments.editConnectedStarRefBot flags:# revoked:flags.0?true peer:InputPeer link:string = payments.ConnectedStarRefBots;
|
||||
|
||||
func _internal_removeConnectedStarRefBot(account: Account, id: EnginePeer.Id, link: String) -> Signal<Never, ConnectStarRefBotError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(id).flatMap(apiInputPeer)
|
||||
@ -957,3 +955,58 @@ func _internal_removeConnectedStarRefBot(account: Account, id: EnginePeer.Id, li
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_getStarRefBotConnection(account: Account, id: EnginePeer.Id, targetId: EnginePeer.Id) -> Signal<TelegramConnectedStarRefBotList.Item?, NoError> {
|
||||
return account.postbox.transaction { transaction -> (Api.InputUser?, Api.InputPeer?) in
|
||||
return (
|
||||
transaction.getPeer(id).flatMap(apiInputUser),
|
||||
transaction.getPeer(targetId).flatMap(apiInputPeer)
|
||||
)
|
||||
}
|
||||
|> mapToSignal { inputPeer, targetPeer -> Signal<TelegramConnectedStarRefBotList.Item?, NoError> in
|
||||
guard let inputPeer, let targetPeer else {
|
||||
return .single(nil)
|
||||
}
|
||||
return account.network.request(Api.functions.payments.getConnectedStarRefBot(peer: targetPeer, bot: inputPeer))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.payments.ConnectedStarRefBots?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<TelegramConnectedStarRefBotList.Item?, NoError> in
|
||||
guard let result else {
|
||||
return .single(nil)
|
||||
}
|
||||
return account.postbox.transaction { transaction -> TelegramConnectedStarRefBotList.Item? in
|
||||
switch result {
|
||||
case let .connectedStarRefBots(_, connectedBots, users):
|
||||
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: AccumulatedPeers(users: users))
|
||||
|
||||
if let bot = connectedBots.first {
|
||||
switch bot {
|
||||
case let .connectedBotStarRef(flags, url, date, botId, commissionPermille, durationMonths, participants, revenue):
|
||||
let isRevoked = (flags & (1 << 1)) != 0
|
||||
if isRevoked {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let botPeer = transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId))) else {
|
||||
return nil
|
||||
}
|
||||
return TelegramConnectedStarRefBotList.Item(
|
||||
peer: EnginePeer(botPeer),
|
||||
url: url,
|
||||
timestamp: date,
|
||||
commissionPermille: commissionPermille,
|
||||
durationMonths: durationMonths,
|
||||
participants: participants,
|
||||
revenue: revenue
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1652,6 +1652,10 @@ public extension TelegramEngine {
|
||||
public func removeConnectedStarRefBot(id: EnginePeer.Id, link: String) -> Signal<Never, ConnectStarRefBotError> {
|
||||
return _internal_removeConnectedStarRefBot(account: self.account, id: id, link: link)
|
||||
}
|
||||
|
||||
public func getStarRefBotConnection(id: EnginePeer.Id, targetId: EnginePeer.Id) -> Signal<TelegramConnectedStarRefBotList.Item?, NoError> {
|
||||
return _internal_getStarRefBotConnection(account: self.account, id: id, targetId: targetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,10 +16,12 @@ public final class ListItemComponentAdaptor: Component {
|
||||
private let isEqualImpl: (AnyObject) -> Bool
|
||||
private let itemImpl: () -> ListViewItem
|
||||
private let params: ListViewItemLayoutParams
|
||||
private let action: (() -> Void)?
|
||||
|
||||
public init<ItemGeneratorType: ItemGenerator>(
|
||||
itemGenerator: ItemGeneratorType,
|
||||
params: ListViewItemLayoutParams
|
||||
params: ListViewItemLayoutParams,
|
||||
action: (() -> Void)? = nil
|
||||
) {
|
||||
self.itemGenerator = itemGenerator
|
||||
self.isEqualImpl = { other in
|
||||
@ -33,6 +35,7 @@ public final class ListItemComponentAdaptor: Component {
|
||||
return itemGenerator.item()
|
||||
}
|
||||
self.params = params
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: ListItemComponentAdaptor, rhs: ListItemComponentAdaptor) -> Bool {
|
||||
@ -42,13 +45,28 @@ public final class ListItemComponentAdaptor: Component {
|
||||
if lhs.params != rhs.params {
|
||||
return false
|
||||
}
|
||||
if (lhs.action == nil) != (rhs.action == nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private var button: HighlightTrackingButton?
|
||||
public var itemNode: ListViewItemNode?
|
||||
|
||||
private var component: ListItemComponentAdaptor?
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action?()
|
||||
}
|
||||
|
||||
func update(component: ListItemComponentAdaptor, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let item = component.itemImpl()
|
||||
|
||||
if let itemNode = self.itemNode {
|
||||
@ -84,7 +102,32 @@ public final class ListItemComponentAdaptor: Component {
|
||||
apply(ListViewItemApply(isOnScreen: true))
|
||||
}
|
||||
)
|
||||
|
||||
if let resultSize {
|
||||
itemNode.isUserInteractionEnabled = component.action == nil
|
||||
if component.action != nil {
|
||||
let button: HighlightTrackingButton
|
||||
if let current = self.button {
|
||||
button = current
|
||||
} else {
|
||||
button = HighlightTrackingButton()
|
||||
self.button = button
|
||||
self.addSubview(button)
|
||||
button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
button.highligthedChanged = { [weak self] isHighlighted in
|
||||
guard let self, let itemNode = self.itemNode else {
|
||||
return
|
||||
}
|
||||
itemNode.setHighlighted(isHighlighted, at: itemNode.bounds.center, animated: !isHighlighted)
|
||||
}
|
||||
}
|
||||
|
||||
transition.setFrame(view: button, frame: CGRect(origin: CGPoint(), size: resultSize))
|
||||
} else if let button = self.button {
|
||||
self.button = nil
|
||||
button.removeFromSuperview()
|
||||
}
|
||||
|
||||
transition.setFrame(view: itemNode.view, frame: CGRect(origin: CGPoint(), size: resultSize))
|
||||
return resultSize
|
||||
} else {
|
||||
@ -107,6 +150,29 @@ public final class ListItemComponentAdaptor: Component {
|
||||
}
|
||||
)
|
||||
if let itemNode {
|
||||
itemNode.isUserInteractionEnabled = component.action == nil
|
||||
if component.action != nil {
|
||||
let button: HighlightTrackingButton
|
||||
if let current = self.button {
|
||||
button = current
|
||||
} else {
|
||||
button = HighlightTrackingButton()
|
||||
self.button = button
|
||||
self.addSubview(button)
|
||||
button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
button.highligthedChanged = { [weak self] isHighlighted in
|
||||
guard let self, let itemNode = self.itemNode else {
|
||||
return
|
||||
}
|
||||
itemNode.setHighlighted(isHighlighted, at: itemNode.bounds.center, animated: !isHighlighted)
|
||||
}
|
||||
}
|
||||
transition.setFrame(view: button, frame: CGRect(origin: CGPoint(), size: itemNode.bounds.size))
|
||||
} else if let button = self.button {
|
||||
self.button = nil
|
||||
button.removeFromSuperview()
|
||||
}
|
||||
|
||||
self.itemNode = itemNode
|
||||
self.addSubnode(itemNode)
|
||||
|
||||
|
@ -182,7 +182,7 @@ final class AffiliateProgramSetupScreenComponent: Component {
|
||||
self.environment?.controller()?.present(tableAlert(
|
||||
theme: presentationData.theme,
|
||||
title: "Warning",
|
||||
text: "This change is irreversible. You won't be able to reduce commission or duration. You can only increase these parameters or end the program, which will disable all previously shared referral links.",
|
||||
text: "Once you start the affiliate program, you won't be able to decrease its commission or duration. You can only increase these parameters or end the program, whuch will disable all previously distributed referral links.",
|
||||
table: TableComponent(theme: environment.theme, items: [
|
||||
TableComponent.Item(id: 0, title: "Commission", component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: commissionTitle, font: Font.regular(17.0), textColor: environment.theme.actionSheet.primaryTextColor))
|
||||
@ -362,7 +362,7 @@ If you end your affiliate program:
|
||||
sourcePeer: bot.peer,
|
||||
commissionPermille: bot.commissionPermille,
|
||||
programDuration: bot.durationMonths,
|
||||
mode: .active(JoinAffiliateProgramScreen.Active(
|
||||
mode: .active(JoinAffiliateProgramScreenMode.Active(
|
||||
targetPeer: targetPeer,
|
||||
link: bot.url,
|
||||
userCount: Int(bot.participants),
|
||||
@ -452,7 +452,7 @@ If you end your affiliate program:
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: false)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
@ -1403,7 +1403,7 @@ If you end your affiliate program:
|
||||
sourcePeer: botPeer,
|
||||
commissionPermille: item.commissionPermille,
|
||||
programDuration: item.durationMonths,
|
||||
mode: .join(JoinAffiliateProgramScreen.Join(
|
||||
mode: .join(JoinAffiliateProgramScreenMode.Join(
|
||||
initialTargetPeer: targetPeer,
|
||||
canSelectTargetPeer: false,
|
||||
completion: { [weak self] _ in
|
||||
@ -1646,16 +1646,18 @@ private final class ListContextExtractedContentSource: ContextExtractedContentSo
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceView: UIView
|
||||
private let actionsOnTop: Bool
|
||||
|
||||
init(controller: ViewController, sourceView: UIView) {
|
||||
init(controller: ViewController, sourceView: UIView, actionsOnTop: Bool) {
|
||||
self.controller = controller
|
||||
self.sourceView = sourceView
|
||||
self.actionsOnTop = actionsOnTop
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: self.actionsOnTop ? .top : .bottom)
|
||||
}
|
||||
}
|
||||
|
@ -326,6 +326,49 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
private func displayTargetSelectionMenu(sourceView: UIView) {
|
||||
guard let component = self.component, let environment = self.environment, let controller = environment.controller() else {
|
||||
return
|
||||
}
|
||||
guard case let .join(join) = component.mode else {
|
||||
return
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
||||
|
||||
let peers: [EnginePeer] = [
|
||||
join.initialTargetPeer
|
||||
]
|
||||
|
||||
let avatarSize = CGSize(width: 30.0, height: 30.0)
|
||||
|
||||
for peer in peers {
|
||||
let peerLabel: String
|
||||
if peer.id == component.context.account.peerId {
|
||||
peerLabel = "personal account"
|
||||
} else if case .channel = peer {
|
||||
peerLabel = "channel"
|
||||
} 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
|
||||
c?.dismiss(completion: {})
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.currentTargetPeer = peer
|
||||
self.state?.updated(transition: .immediate)
|
||||
})))
|
||||
}
|
||||
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: true)), items: .single(ContextController.Items(id: AnyHashable(0), content: .list(items))), gesture: nil)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
func update(component: JoinAffiliateProgramScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
@ -690,7 +733,12 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
peer: currentTargetPeer,
|
||||
isSelectable: isTargetPeerSelectable
|
||||
action: isTargetPeerSelectable ? { [weak self] sourceView in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.displayTargetSelectionMenu(sourceView: sourceView)
|
||||
} : nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
@ -947,36 +995,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
||||
}
|
||||
|
||||
public class JoinAffiliateProgramScreen: ViewControllerComponentContainer {
|
||||
public final class Join {
|
||||
public let initialTargetPeer: EnginePeer
|
||||
public let canSelectTargetPeer: Bool
|
||||
public let completion: (EnginePeer) -> Void
|
||||
|
||||
public init(initialTargetPeer: EnginePeer, canSelectTargetPeer: Bool, completion: @escaping (EnginePeer) -> Void) {
|
||||
self.initialTargetPeer = initialTargetPeer
|
||||
self.canSelectTargetPeer = canSelectTargetPeer
|
||||
self.completion = completion
|
||||
}
|
||||
}
|
||||
|
||||
public final class Active {
|
||||
public let targetPeer: EnginePeer
|
||||
public let link: String
|
||||
public let userCount: Int
|
||||
public let copyLink: () -> Void
|
||||
|
||||
public init(targetPeer: EnginePeer, link: String, userCount: Int, copyLink: @escaping () -> Void) {
|
||||
self.targetPeer = targetPeer
|
||||
self.link = link
|
||||
self.userCount = userCount
|
||||
self.copyLink = copyLink
|
||||
}
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
case join(Join)
|
||||
case active(Active)
|
||||
}
|
||||
public typealias Mode = JoinAffiliateProgramScreenMode
|
||||
|
||||
private let context: AccountContext
|
||||
|
||||
@ -1042,20 +1061,20 @@ private final class PeerBadgeComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let peer: EnginePeer
|
||||
let isSelectable: Bool
|
||||
let action: ((UIView) -> Void)?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
peer: EnginePeer,
|
||||
isSelectable: Bool
|
||||
action: ((UIView) -> Void)?
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.peer = peer
|
||||
self.isSelectable = isSelectable
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerBadgeComponent, rhs: PeerBadgeComponent) -> Bool {
|
||||
@ -1071,38 +1090,53 @@ private final class PeerBadgeComponent: Component {
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.isSelectable != rhs.isSelectable {
|
||||
if (lhs.action == nil) != (rhs.action == nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
final class View: HighlightableButton {
|
||||
private let background = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
private var avatarNode: AvatarNode?
|
||||
private var selectorIcon: ComponentView<Empty>?
|
||||
|
||||
private var component: PeerBadgeComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action?(self)
|
||||
}
|
||||
|
||||
func update(component: PeerBadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
self.isEnabled = component.action != nil
|
||||
|
||||
let height: CGFloat = 32.0
|
||||
let avatarPadding: CGFloat = 1.0
|
||||
|
||||
let avatarDiameter = height - avatarPadding * 2.0
|
||||
let avatarTextSpacing: CGFloat = 4.0
|
||||
let rightTextInset: CGFloat = component.isSelectable ? 26.0 : 12.0
|
||||
let rightTextInset: CGFloat = component.action != nil ? 26.0 : 12.0
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.medium(15.0), textColor: component.isSelectable ? component.theme.list.itemInputField.primaryColor : component.theme.list.itemInputField.primaryColor))
|
||||
text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.medium(15.0), textColor: component.action != nil ? component.theme.list.itemInputField.primaryColor : component.theme.list.itemInputField.primaryColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - avatarPadding - avatarDiameter - avatarTextSpacing - rightTextInset, height: height)
|
||||
@ -1110,6 +1144,7 @@ private final class PeerBadgeComponent: Component {
|
||||
let titleFrame = CGRect(origin: CGPoint(x: avatarPadding + avatarDiameter + avatarTextSpacing, y: floorToScreenPixels((height - titleSize.height) * 0.5)), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
@ -1120,6 +1155,7 @@ private final class PeerBadgeComponent: Component {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
|
||||
avatarNode.isUserInteractionEnabled = false
|
||||
avatarNode.displaysAsynchronously = false
|
||||
self.avatarNode = avatarNode
|
||||
self.addSubview(avatarNode.view)
|
||||
@ -1132,7 +1168,7 @@ private final class PeerBadgeComponent: Component {
|
||||
|
||||
let size = CGSize(width: avatarPadding + avatarDiameter + avatarTextSpacing + titleSize.width + rightTextInset, height: height)
|
||||
|
||||
if component.isSelectable {
|
||||
if component.action != nil {
|
||||
let selectorIcon: ComponentView<Empty>
|
||||
if let current = self.selectorIcon {
|
||||
selectorIcon = current
|
||||
@ -1150,6 +1186,7 @@ private final class PeerBadgeComponent: Component {
|
||||
let selectorIconFrame = CGRect(origin: CGPoint(x: size.width - 8.0 - selectorIconSize.width, y: floorToScreenPixels((size.height - selectorIconSize.height) * 0.5)), size: selectorIconSize)
|
||||
if let selectorIconView = selectorIcon.view {
|
||||
if selectorIconView.superview == nil {
|
||||
selectorIconView.isUserInteractionEnabled = false
|
||||
self.addSubview(selectorIconView)
|
||||
}
|
||||
transition.setFrame(view: selectorIconView, frame: selectorIconFrame)
|
||||
@ -1162,7 +1199,7 @@ private final class PeerBadgeComponent: Component {
|
||||
let _ = self.background.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(FilledRoundedRectangleComponent(
|
||||
color: component.isSelectable ? component.theme.list.itemAccentColor.withMultipliedAlpha(0.1) : component.theme.list.itemInputField.backgroundColor,
|
||||
color: component.action != nil ? component.theme.list.itemAccentColor.withMultipliedAlpha(0.1) : component.theme.list.itemInputField.backgroundColor,
|
||||
cornerRadius: .minEdge,
|
||||
smoothCorners: false
|
||||
)),
|
||||
@ -1171,6 +1208,7 @@ private final class PeerBadgeComponent: Component {
|
||||
)
|
||||
if let backgroundView = self.background.view {
|
||||
if backgroundView.superview == nil {
|
||||
backgroundView.isUserInteractionEnabled = false
|
||||
self.insertSubview(backgroundView, at: 0)
|
||||
}
|
||||
transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
@ -224,22 +224,110 @@ final class TableComponent: CombinedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
func tableAlert(theme: PresentationTheme, title: String, text: String, table: TableComponent, actions: [ComponentAlertAction]) -> ViewController {
|
||||
let content: AnyComponent<Empty> = AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: title, font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor)),
|
||||
private final class TableAlertContentComponet: CombinedComponent {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let text: String
|
||||
let table: TableComponent
|
||||
|
||||
init(theme: PresentationTheme, title: String, text: String, table: TableComponent) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.table = table
|
||||
}
|
||||
|
||||
static func ==(lhs: TableAlertContentComponet, rhs: TableAlertContentComponet) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.table != rhs.table {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public static var body: Body {
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let text = Child(MultilineTextComponent.self)
|
||||
let table = Child(TableComponent.self)
|
||||
|
||||
return { context in
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: context.component.title, font: Font.semibold(16.0), textColor: context.component.theme.actionSheet.primaryTextColor)),
|
||||
horizontalAlignment: .center
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: theme.actionSheet.primaryTextColor)),
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: 10000.0),
|
||||
transition: .immediate
|
||||
)
|
||||
let text = text.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: context.component.text, font: Font.regular(13.0), textColor: context.component.theme.actionSheet.primaryTextColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 2, component: AnyComponent(table)),
|
||||
], spacing: 10.0))
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: 10000.0),
|
||||
transition: .immediate
|
||||
)
|
||||
let table = table.update(
|
||||
component: context.component.table,
|
||||
availableSize: CGSize(width: context.availableSize.width, height: 10000.0),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
var size = CGSize(width: 0.0, height: 0.0)
|
||||
|
||||
size.width = max(size.width, title.size.width)
|
||||
size.width = max(size.width, text.size.width)
|
||||
size.width = max(size.width, table.size.width)
|
||||
|
||||
size.height += title.size.height
|
||||
size.height += 5.0
|
||||
size.height += text.size.height
|
||||
size.height += 14.0
|
||||
size.height += table.size.height
|
||||
size.height -= 3.0
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - title.size.width) * 0.5), y: contentHeight), size: title.size)
|
||||
contentHeight += title.size.height + 5.0
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((size.width - text.size.width) * 0.5), y: contentHeight), size: text.size)
|
||||
contentHeight += text.size.height + 14.0
|
||||
let tableFrame = CGRect(origin: CGPoint(x: floor((size.width - table.size.width) * 0.5), y: contentHeight), size: table.size)
|
||||
contentHeight += table.size.height
|
||||
|
||||
context.add(title
|
||||
.position(titleFrame.center)
|
||||
)
|
||||
context.add(text
|
||||
.position(textFrame.center)
|
||||
)
|
||||
context.add(table
|
||||
.position(tableFrame.center)
|
||||
)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tableAlert(theme: PresentationTheme, title: String, text: String, table: TableComponent, actions: [ComponentAlertAction]) -> ViewController {
|
||||
return componentAlertController(
|
||||
theme: AlertControllerTheme(presentationTheme: theme, fontSize: .regular),
|
||||
content: content,
|
||||
content: AnyComponent(TableAlertContentComponet(
|
||||
theme: theme,
|
||||
title: title,
|
||||
text: text,
|
||||
table: table
|
||||
)),
|
||||
actions: actions,
|
||||
actionLayout: .horizontal
|
||||
)
|
||||
|
@ -20,6 +20,7 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
|
||||
case semitransparentBadge(String, UIColor)
|
||||
case titleBadge(String, UIColor)
|
||||
case image(UIImage, CGSize)
|
||||
case labelBadge(String)
|
||||
|
||||
var text: String {
|
||||
switch self {
|
||||
@ -27,14 +28,14 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
|
||||
return ""
|
||||
case let .attributedText(text):
|
||||
return text.string
|
||||
case let .text(text), let .coloredText(text, _), let .badge(text, _), let .semitransparentBadge(text, _), let .titleBadge(text, _):
|
||||
case let .text(text), let .coloredText(text, _), let .badge(text, _), let .semitransparentBadge(text, _), let .titleBadge(text, _), let .labelBadge(text):
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
var badgeColor: UIColor? {
|
||||
switch self {
|
||||
case .none, .text, .coloredText, .image, .attributedText:
|
||||
case .none, .text, .coloredText, .image, .attributedText, .labelBadge:
|
||||
return nil
|
||||
case let .badge(_, color), let .semitransparentBadge(_, color), let .titleBadge(_, color):
|
||||
return color
|
||||
@ -170,6 +171,9 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
||||
} else if case .titleBadge = item.label {
|
||||
labelColorValue = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||
labelFont = Font.medium(11.0)
|
||||
} else if case .labelBadge = item.label {
|
||||
labelColorValue = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||
labelFont = Font.medium(12.0)
|
||||
} else if case let .coloredText(_, color) = item.label {
|
||||
switch color {
|
||||
case .generic:
|
||||
@ -274,6 +278,14 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
||||
if self.labelBadgeNode.supernode == nil {
|
||||
self.insertSubnode(self.labelBadgeNode, belowSubnode: self.labelNode)
|
||||
}
|
||||
} else if case let .labelBadge(text) = item.label, !text.isEmpty {
|
||||
let badgeColor = presentationData.theme.list.itemCheckColors.fillColor
|
||||
if previousItem?.label.badgeColor != badgeColor {
|
||||
self.labelBadgeNode.image = generateFilledRoundedRectImage(size: CGSize(width: 16.0, height: 16.0), cornerRadius: 5.0, color: badgeColor)?.stretchableImage(withLeftCapWidth: 6, topCapHeight: 6)
|
||||
}
|
||||
if self.labelBadgeNode.supernode == nil {
|
||||
self.insertSubnode(self.labelBadgeNode, belowSubnode: self.labelNode)
|
||||
}
|
||||
} else if item.additionalBadgeLabel != nil {
|
||||
if previousItem?.additionalBadgeLabel == nil {
|
||||
self.labelBadgeNode.image = generateFilledRoundedRectImage(size: CGSize(width: 16.0, height: 16.0), cornerRadius: 5.0, color: presentationData.theme.list.itemCheckColors.fillColor)?.stretchableImage(withLeftCapWidth: 6, topCapHeight: 6)
|
||||
@ -314,6 +326,8 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
||||
labelFrame = CGRect(origin: CGPoint(x: width - rightInset - badgeWidth + (badgeWidth - labelSize.width) / 2.0, y: floor((height - labelSize.height) / 2.0)), size: labelSize)
|
||||
} else if case .titleBadge = item.label {
|
||||
labelFrame = CGRect(origin: CGPoint(x: textFrame.maxX + 10.0, y: floor((height - labelSize.height) / 2.0) + 1.0), size: labelSize)
|
||||
} else if case .labelBadge = item.label {
|
||||
labelFrame = CGRect(origin: CGPoint(x: width - rightInset - badgeWidth + (badgeWidth - labelSize.width) / 2.0, y: floor((height - labelSize.height) / 2.0)), size: labelSize)
|
||||
} else {
|
||||
labelFrame = CGRect(origin: CGPoint(x: width - rightInset - labelSize.width, y: 12.0), size: labelSize)
|
||||
}
|
||||
@ -344,9 +358,11 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
||||
|
||||
let labelBadgeNodeFrame: CGRect
|
||||
if case let .image(_, imageSize) = item.label {
|
||||
labelBadgeNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - imageSize.width, y: floorToScreenPixels(textFrame.midY - imageSize.height / 2.0)), size:imageSize)
|
||||
labelBadgeNodeFrame = CGRect(origin: CGPoint(x: width - rightInset - imageSize.width, y: floorToScreenPixels(textFrame.midY - imageSize.height / 2.0)), size: imageSize)
|
||||
} else if case .titleBadge = item.label {
|
||||
labelBadgeNodeFrame = labelFrame.insetBy(dx: -4.0, dy: -2.0 + UIScreenPixel)
|
||||
} else if case .labelBadge = item.label {
|
||||
labelBadgeNodeFrame = labelFrame.insetBy(dx: -4.0, dy: -2.0 + UIScreenPixel)
|
||||
} else if let additionalLabelNode = self.additionalLabelNode {
|
||||
labelBadgeNodeFrame = additionalLabelNode.frame.insetBy(dx: -4.0, dy: -2.0 + UIScreenPixel)
|
||||
} else {
|
||||
|
@ -1215,6 +1215,7 @@ private enum InfoSection: Int, CaseIterable {
|
||||
case permissions
|
||||
case peerInfoTrailing
|
||||
case peerMembers
|
||||
case botAffiliateProgram
|
||||
}
|
||||
|
||||
private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction, nearbyPeerDistance: Int32?, reactionSourceMessageId: MessageId?, callMessages: [Message], chatLocation: ChatLocation, isOpenedFromChat: Bool, isMyProfile: Bool) -> [(AnyHashable, [PeerInfoScreenItem])] {
|
||||
@ -1425,6 +1426,23 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
|
||||
currentPeerInfoSection = .peerInfoTrailing
|
||||
}
|
||||
|
||||
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
|
||||
} else {
|
||||
if let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil {
|
||||
if items[.botAffiliateProgram] == nil {
|
||||
items[.botAffiliateProgram] = []
|
||||
}
|
||||
//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."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let businessHours = cachedData.businessHours {
|
||||
@ -1921,13 +1939,13 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
||||
interaction.editingOpenPublicLinkSetup()
|
||||
}))
|
||||
//TODO:localize
|
||||
let programTitleValue: String
|
||||
let programTitleValue: PeerInfoScreenDisclosureItem.Label
|
||||
if let cachedData = data.cachedData as? CachedUserData, let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil {
|
||||
programTitleValue = "\(starRefProgram.commissionPermille / 10)%"
|
||||
programTitleValue = .labelBadge("\(starRefProgram.commissionPermille / 10)%")
|
||||
} else {
|
||||
programTitleValue = "Off"
|
||||
programTitleValue = .text("Off")
|
||||
}
|
||||
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: .text(programTitleValue), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Affiliate Program", icon: PresentationResourcesSettings.affiliateProgram, action: {
|
||||
items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemAffiliateProgram, label: programTitleValue, additionalBadgeLabel: presentationData.strings.Settings_New, text: "Affiliate Program", icon: PresentationResourcesSettings.affiliateProgram, action: {
|
||||
interaction.editingOpenAffiliateProgram()
|
||||
}))
|
||||
|
||||
@ -8575,7 +8593,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
|
||||
private func editingOpenAffiliateProgram() {
|
||||
if let peer = self.data?.peer as? TelegramUser, peer.botInfo != nil {
|
||||
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
|
||||
guard let self else {
|
||||
@ -8584,8 +8603,85 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
let controller = self.context.sharedContext.makeAffiliateProgramSetupScreen(context: self.context, initialData: initialData)
|
||||
self.controller?.push(controller)
|
||||
})
|
||||
} else if let channel = self.data?.peer as? TelegramChannel {
|
||||
let _ = (self.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: self.context, peerId: channel.id, mode: .connectedPrograms)
|
||||
} else if let starRefProgram = (self.data?.cachedData as? CachedUserData)?.starRefProgram, starRefProgram.endDate == nil {
|
||||
self.activeActionDisposable.set((self.context.engine.peers.getStarRefBotConnection(id: peer.id, targetId: self.context.account.peerId)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] accountPeer in
|
||||
guard let self, let accountPeer else {
|
||||
return
|
||||
}
|
||||
let mode: JoinAffiliateProgramScreenMode
|
||||
if let result {
|
||||
mode = .active(JoinAffiliateProgramScreenMode.Active(
|
||||
targetPeer: accountPeer,
|
||||
link: result.url,
|
||||
userCount: Int(result.participants),
|
||||
copyLink: { [weak self] 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)
|
||||
}
|
||||
))
|
||||
} else {
|
||||
mode = .join(JoinAffiliateProgramScreenMode.Join(
|
||||
initialTargetPeer: accountPeer,
|
||||
canSelectTargetPeer: true,
|
||||
completion: { [weak self] targetPeer in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.peers.connectStarRefBot(id: targetPeer.id, botId: self.peerId)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let bot = result
|
||||
|
||||
self.controller?.push(self.context.sharedContext.makeAffiliateProgramJoinScreen(
|
||||
context: self.context,
|
||||
sourcePeer: bot.peer,
|
||||
commissionPermille: bot.commissionPermille,
|
||||
programDuration: bot.durationMonths,
|
||||
mode: .active(JoinAffiliateProgramScreenMode.Active(
|
||||
targetPeer: targetPeer,
|
||||
link: bot.url,
|
||||
userCount: Int(bot.participants),
|
||||
copyLink: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
UIPasteboard.general.string = bot.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?.push(self.context.sharedContext.makeAffiliateProgramJoinScreen(
|
||||
context: self.context,
|
||||
sourcePeer: .user(peer),
|
||||
commissionPermille: starRefProgram.commissionPermille,
|
||||
programDuration: starRefProgram.durationMonths,
|
||||
mode: mode
|
||||
))
|
||||
})
|
||||
}))
|
||||
}
|
||||
} else if let peer = self.data?.peer {
|
||||
let _ = (self.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: self.context, peerId: peer.id, mode: .connectedPrograms)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -21,6 +21,8 @@ import UndoUI
|
||||
import ListActionItemComponent
|
||||
import StarsAvatarComponent
|
||||
import TelegramStringFormatting
|
||||
import ListItemComponentAdaptor
|
||||
import ItemListUI
|
||||
|
||||
private let initialSubscriptionsDisplayedLimit: Int32 = 3
|
||||
|
||||
@ -102,6 +104,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
private let descriptionView = ComponentView<Empty>()
|
||||
|
||||
private let balanceView = ComponentView<Empty>()
|
||||
private let earnStarsSection = ComponentView<Empty>()
|
||||
|
||||
private let subscriptionsView = ComponentView<Empty>()
|
||||
|
||||
@ -657,6 +660,47 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
starTransition.setFrame(view: balanceView, frame: balanceFrame)
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
contentHeight += earnStarsSectionSize.height
|
||||
contentHeight += 44.0
|
||||
|
||||
let fontBaseDisplaySize = 17.0
|
||||
|
@ -2839,6 +2839,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
|
||||
|
Loading…
x
Reference in New Issue
Block a user