mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 17:30:12 +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 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 {
|
public protocol SharedAccountContext: AnyObject {
|
||||||
var sharedContainerPath: String { get }
|
var sharedContainerPath: String { get }
|
||||||
var basePath: 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 makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal<AffiliateProgramSetupScreenInitialData, NoError>
|
||||||
func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController
|
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 makeDebugSettingsController(context: AccountContext?) -> ViewController?
|
||||||
|
|
||||||
func navigateToCurrentCall()
|
func navigateToCurrentCall()
|
||||||
|
|||||||
@ -33,6 +33,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||||
"//submodules/Components/ComponentDisplayAdapters",
|
"//submodules/Components/ComponentDisplayAdapters",
|
||||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||||
|
"//submodules/TelegramUI/Components/ListItemComponentAdaptor",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import AvatarNode
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import TextNodeWithEntities
|
import TextNodeWithEntities
|
||||||
|
import ListItemComponentAdaptor
|
||||||
|
|
||||||
private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ public enum ItemListDisclosureItemDetailLabelColor {
|
|||||||
case destructive
|
case destructive
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
public class ItemListDisclosureItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator {
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let icon: UIImage?
|
let icon: UIImage?
|
||||||
let context: AccountContext?
|
let context: AccountContext?
|
||||||
@ -56,6 +57,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
|||||||
let titleColor: ItemListDisclosureItemTitleColor
|
let titleColor: ItemListDisclosureItemTitleColor
|
||||||
let titleFont: ItemListDisclosureItemTitleFont
|
let titleFont: ItemListDisclosureItemTitleFont
|
||||||
let titleIcon: UIImage?
|
let titleIcon: UIImage?
|
||||||
|
let titleBadge: String?
|
||||||
let enabled: Bool
|
let enabled: Bool
|
||||||
let label: String
|
let label: String
|
||||||
let attributedLabel: NSAttributedString?
|
let attributedLabel: NSAttributedString?
|
||||||
@ -71,7 +73,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
|||||||
public let tag: ItemListItemTag?
|
public let tag: ItemListItemTag?
|
||||||
public let shimmeringIndex: Int?
|
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.presentationData = presentationData
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -81,6 +83,7 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
|||||||
self.titleColor = titleColor
|
self.titleColor = titleColor
|
||||||
self.titleFont = titleFont
|
self.titleFont = titleFont
|
||||||
self.titleIcon = titleIcon
|
self.titleIcon = titleIcon
|
||||||
|
self.titleBadge = titleBadge
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
self.labelStyle = labelStyle
|
self.labelStyle = labelStyle
|
||||||
self.label = label
|
self.label = label
|
||||||
@ -140,6 +143,27 @@ public class ItemListDisclosureItem: ListViewItem, ItemListItem {
|
|||||||
self.action?()
|
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)
|
private let badgeFont = Font.regular(15.0)
|
||||||
@ -162,6 +186,9 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
let labelBadgeNode: ASImageNode
|
let labelBadgeNode: ASImageNode
|
||||||
let labelImageNode: ASImageNode
|
let labelImageNode: ASImageNode
|
||||||
|
|
||||||
|
var titleBadgeNode: ASImageNode?
|
||||||
|
var titleBadgeTextNode: TextNode?
|
||||||
|
|
||||||
private let activateArea: AccessibilityAreaNode
|
private let activateArea: AccessibilityAreaNode
|
||||||
|
|
||||||
private var item: ItemListDisclosureItem?
|
private var item: ItemListDisclosureItem?
|
||||||
@ -260,6 +287,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
let makeAdditionalDetailLabelLayout = TextNode.asyncLayout(self.additionalDetailLabelNode)
|
let makeAdditionalDetailLabelLayout = TextNode.asyncLayout(self.additionalDetailLabelNode)
|
||||||
|
|
||||||
|
let makeTitleBadgeTextNodeLayout = TextNode.asyncLayout(self.titleBadgeTextNode)
|
||||||
|
|
||||||
let currentItem = self.item
|
let currentItem = self.item
|
||||||
|
|
||||||
let currentHasBadge = self.labelBadgeNode.image != nil
|
let currentHasBadge = self.labelBadgeNode.image != nil
|
||||||
@ -374,6 +403,13 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
maxTitleWidth -= 12.0
|
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 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 (titleLayoutAndApply) = item.context == nil ? makeTitleLayout(titleArguments) : nil
|
||||||
let (titleWithEntitiesLayoutAndApply) = item.context != nil ? makeTitleWithEntitiesLayout(titleArguments) : nil
|
let (titleWithEntitiesLayoutAndApply) = item.context != nil ? makeTitleWithEntitiesLayout(titleArguments) : nil
|
||||||
@ -680,6 +716,41 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
additionalDetailLabelNode.removeFromSupernode()
|
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 let titleIcon = item.titleIcon {
|
||||||
if strongSelf.titleIconNode.supernode == nil {
|
if strongSelf.titleIconNode.supernode == nil {
|
||||||
strongSelf.addSubnode(strongSelf.titleIconNode)
|
strongSelf.addSubnode(strongSelf.titleIconNode)
|
||||||
|
|||||||
@ -56,9 +56,10 @@ private final class ChannelStatsControllerArguments {
|
|||||||
let expandTransactions: (Bool) -> Void
|
let expandTransactions: (Bool) -> Void
|
||||||
let updateCpmEnabled: (Bool) -> Void
|
let updateCpmEnabled: (Bool) -> Void
|
||||||
let presentCpmLocked: () -> Void
|
let presentCpmLocked: () -> Void
|
||||||
|
let openEarnStars: () -> Void
|
||||||
let dismissInput: () -> 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.context = context
|
||||||
self.loadDetailedGraph = loadDetailedGraph
|
self.loadDetailedGraph = loadDetailedGraph
|
||||||
self.openPostStats = openPostStats
|
self.openPostStats = openPostStats
|
||||||
@ -83,6 +84,7 @@ private final class ChannelStatsControllerArguments {
|
|||||||
self.expandTransactions = expandTransactions
|
self.expandTransactions = expandTransactions
|
||||||
self.updateCpmEnabled = updateCpmEnabled
|
self.updateCpmEnabled = updateCpmEnabled
|
||||||
self.presentCpmLocked = presentCpmLocked
|
self.presentCpmLocked = presentCpmLocked
|
||||||
|
self.openEarnStars = openEarnStars
|
||||||
self.dismissInput = dismissInput
|
self.dismissInput = dismissInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,6 +121,8 @@ private enum StatsSection: Int32 {
|
|||||||
case adsStarsBalance
|
case adsStarsBalance
|
||||||
case adsTransactions
|
case adsTransactions
|
||||||
case adsCpm
|
case adsCpm
|
||||||
|
|
||||||
|
case earnStars
|
||||||
}
|
}
|
||||||
|
|
||||||
enum StatsPostItem: Equatable {
|
enum StatsPostItem: Equatable {
|
||||||
@ -249,6 +253,7 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
case adsStarsBalance(PresentationTheme, StarsRevenueStats, Bool, Bool, Int32?)
|
case adsStarsBalance(PresentationTheme, StarsRevenueStats, Bool, Bool, Int32?)
|
||||||
case adsStarsBalanceInfo(PresentationTheme, String)
|
case adsStarsBalanceInfo(PresentationTheme, String)
|
||||||
|
|
||||||
|
case earnStarsInfo
|
||||||
case adsTransactionsTitle(PresentationTheme, String)
|
case adsTransactionsTitle(PresentationTheme, String)
|
||||||
case adsTransactionsTabs(PresentationTheme, String, String, Bool)
|
case adsTransactionsTabs(PresentationTheme, String, String, Bool)
|
||||||
case adsTransaction(Int32, PresentationTheme, RevenueStatsTransactionsContext.State.Transaction)
|
case adsTransaction(Int32, PresentationTheme, RevenueStatsTransactionsContext.State.Transaction)
|
||||||
@ -314,6 +319,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
return StatsSection.adsTonBalance.rawValue
|
return StatsSection.adsTonBalance.rawValue
|
||||||
case .adsStarsBalanceTitle, .adsStarsBalance, .adsStarsBalanceInfo:
|
case .adsStarsBalanceTitle, .adsStarsBalance, .adsStarsBalanceInfo:
|
||||||
return StatsSection.adsStarsBalance.rawValue
|
return StatsSection.adsStarsBalance.rawValue
|
||||||
|
case .earnStarsInfo:
|
||||||
|
return StatsSection.earnStars.rawValue
|
||||||
case .adsTransactionsTitle, .adsTransactionsTabs, .adsTransaction, .adsStarsTransaction, .adsTransactionsExpand:
|
case .adsTransactionsTitle, .adsTransactionsTabs, .adsTransaction, .adsStarsTransaction, .adsTransactionsExpand:
|
||||||
return StatsSection.adsTransactions.rawValue
|
return StatsSection.adsTransactions.rawValue
|
||||||
case .adsCpmToggle, .adsCpmInfo:
|
case .adsCpmToggle, .adsCpmInfo:
|
||||||
@ -445,14 +452,16 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
return 20014
|
return 20014
|
||||||
case .adsStarsBalanceInfo:
|
case .adsStarsBalanceInfo:
|
||||||
return 20015
|
return 20015
|
||||||
case .adsTransactionsTitle:
|
case .earnStarsInfo:
|
||||||
return 20016
|
return 20016
|
||||||
case .adsTransactionsTabs:
|
case .adsTransactionsTitle:
|
||||||
return 20017
|
return 20017
|
||||||
|
case .adsTransactionsTabs:
|
||||||
|
return 20018
|
||||||
case let .adsTransaction(index, _, _):
|
case let .adsTransaction(index, _, _):
|
||||||
return 20018 + index
|
return 20019 + index
|
||||||
case let .adsStarsTransaction(index, _, _):
|
case let .adsStarsTransaction(index, _, _):
|
||||||
return 30017 + index
|
return 30018 + index
|
||||||
case .adsTransactionsExpand:
|
case .adsTransactionsExpand:
|
||||||
return 40000
|
return 40000
|
||||||
case .adsCpmToggle:
|
case .adsCpmToggle:
|
||||||
@ -830,6 +839,12 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case .earnStarsInfo:
|
||||||
|
if case .earnStarsInfo = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case let .adsTransactionsTitle(lhsTheme, lhsText):
|
case let .adsTransactionsTitle(lhsTheme, lhsText):
|
||||||
if case let .adsTransactionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
if case let .adsTransactionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||||
return true
|
return true
|
||||||
@ -1203,6 +1218,11 @@ private enum StatsEntry: ItemListNodeEntry {
|
|||||||
}, activatedWhileDisabled: {
|
}, activatedWhileDisabled: {
|
||||||
arguments.presentCpmLocked()
|
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 displayStarsTransactions {
|
||||||
if !addedTransactionsTabs {
|
if !addedTransactionsTabs {
|
||||||
|
//TODO:localize
|
||||||
|
entries.append(.earnStarsInfo)
|
||||||
|
|
||||||
entries.append(.adsTransactionsTitle(presentationData.theme, presentationData.strings.Monetization_StarsTransactions.uppercased()))
|
entries.append(.adsTransactionsTitle(presentationData.theme, presentationData.strings.Monetization_StarsTransactions.uppercased()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2071,6 +2094,12 @@ public func channelStatsController(
|
|||||||
pushImpl?(controller)
|
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: {
|
dismissInput: {
|
||||||
dismissInputImpl?()
|
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> {
|
func _internal_removeConnectedStarRefBot(account: Account, id: EnginePeer.Id, link: String) -> Signal<Never, ConnectStarRefBotError> {
|
||||||
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
return transaction.getPeer(id).flatMap(apiInputPeer)
|
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> {
|
public func removeConnectedStarRefBot(id: EnginePeer.Id, link: String) -> Signal<Never, ConnectStarRefBotError> {
|
||||||
return _internal_removeConnectedStarRefBot(account: self.account, id: id, link: link)
|
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 isEqualImpl: (AnyObject) -> Bool
|
||||||
private let itemImpl: () -> ListViewItem
|
private let itemImpl: () -> ListViewItem
|
||||||
private let params: ListViewItemLayoutParams
|
private let params: ListViewItemLayoutParams
|
||||||
|
private let action: (() -> Void)?
|
||||||
|
|
||||||
public init<ItemGeneratorType: ItemGenerator>(
|
public init<ItemGeneratorType: ItemGenerator>(
|
||||||
itemGenerator: ItemGeneratorType,
|
itemGenerator: ItemGeneratorType,
|
||||||
params: ListViewItemLayoutParams
|
params: ListViewItemLayoutParams,
|
||||||
|
action: (() -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.itemGenerator = itemGenerator
|
self.itemGenerator = itemGenerator
|
||||||
self.isEqualImpl = { other in
|
self.isEqualImpl = { other in
|
||||||
@ -33,6 +35,7 @@ public final class ListItemComponentAdaptor: Component {
|
|||||||
return itemGenerator.item()
|
return itemGenerator.item()
|
||||||
}
|
}
|
||||||
self.params = params
|
self.params = params
|
||||||
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: ListItemComponentAdaptor, rhs: ListItemComponentAdaptor) -> Bool {
|
public static func ==(lhs: ListItemComponentAdaptor, rhs: ListItemComponentAdaptor) -> Bool {
|
||||||
@ -42,13 +45,28 @@ public final class ListItemComponentAdaptor: Component {
|
|||||||
if lhs.params != rhs.params {
|
if lhs.params != rhs.params {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if (lhs.action == nil) != (rhs.action == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIView {
|
public final class View: UIView {
|
||||||
|
private var button: HighlightTrackingButton?
|
||||||
public var itemNode: ListViewItemNode?
|
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 {
|
func update(component: ListItemComponentAdaptor, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
|
||||||
let item = component.itemImpl()
|
let item = component.itemImpl()
|
||||||
|
|
||||||
if let itemNode = self.itemNode {
|
if let itemNode = self.itemNode {
|
||||||
@ -84,7 +102,32 @@ public final class ListItemComponentAdaptor: Component {
|
|||||||
apply(ListViewItemApply(isOnScreen: true))
|
apply(ListViewItemApply(isOnScreen: true))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if let resultSize {
|
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))
|
transition.setFrame(view: itemNode.view, frame: CGRect(origin: CGPoint(), size: resultSize))
|
||||||
return resultSize
|
return resultSize
|
||||||
} else {
|
} else {
|
||||||
@ -107,6 +150,29 @@ public final class ListItemComponentAdaptor: Component {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if let itemNode {
|
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.itemNode = itemNode
|
||||||
self.addSubnode(itemNode)
|
self.addSubnode(itemNode)
|
||||||
|
|
||||||
|
|||||||
@ -182,7 +182,7 @@ final class AffiliateProgramSetupScreenComponent: Component {
|
|||||||
self.environment?.controller()?.present(tableAlert(
|
self.environment?.controller()?.present(tableAlert(
|
||||||
theme: presentationData.theme,
|
theme: presentationData.theme,
|
||||||
title: "Warning",
|
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: [
|
table: TableComponent(theme: environment.theme, items: [
|
||||||
TableComponent.Item(id: 0, title: "Commission", component: AnyComponent(MultilineTextComponent(
|
TableComponent.Item(id: 0, title: "Commission", component: AnyComponent(MultilineTextComponent(
|
||||||
text: .plain(NSAttributedString(string: commissionTitle, font: Font.regular(17.0), textColor: environment.theme.actionSheet.primaryTextColor))
|
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,
|
sourcePeer: bot.peer,
|
||||||
commissionPermille: bot.commissionPermille,
|
commissionPermille: bot.commissionPermille,
|
||||||
programDuration: bot.durationMonths,
|
programDuration: bot.durationMonths,
|
||||||
mode: .active(JoinAffiliateProgramScreen.Active(
|
mode: .active(JoinAffiliateProgramScreenMode.Active(
|
||||||
targetPeer: targetPeer,
|
targetPeer: targetPeer,
|
||||||
link: bot.url,
|
link: bot.url,
|
||||||
userCount: Int(bot.participants),
|
userCount: Int(bot.participants),
|
||||||
@ -452,7 +452,7 @@ If you end your affiliate program:
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
|
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)
|
controller.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1403,7 +1403,7 @@ If you end your affiliate program:
|
|||||||
sourcePeer: botPeer,
|
sourcePeer: botPeer,
|
||||||
commissionPermille: item.commissionPermille,
|
commissionPermille: item.commissionPermille,
|
||||||
programDuration: item.durationMonths,
|
programDuration: item.durationMonths,
|
||||||
mode: .join(JoinAffiliateProgramScreen.Join(
|
mode: .join(JoinAffiliateProgramScreenMode.Join(
|
||||||
initialTargetPeer: targetPeer,
|
initialTargetPeer: targetPeer,
|
||||||
canSelectTargetPeer: false,
|
canSelectTargetPeer: false,
|
||||||
completion: { [weak self] _ in
|
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 controller: ViewController
|
||||||
private let sourceView: UIView
|
private let sourceView: UIView
|
||||||
|
private let actionsOnTop: Bool
|
||||||
|
|
||||||
init(controller: ViewController, sourceView: UIView) {
|
init(controller: ViewController, sourceView: UIView, actionsOnTop: Bool) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.sourceView = sourceView
|
self.sourceView = sourceView
|
||||||
|
self.actionsOnTop = actionsOnTop
|
||||||
}
|
}
|
||||||
|
|
||||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
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 {
|
func update(component: JoinAffiliateProgramScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
defer {
|
defer {
|
||||||
@ -690,7 +733,12 @@ private final class JoinAffiliateProgramScreenComponent: Component {
|
|||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
peer: currentTargetPeer,
|
peer: currentTargetPeer,
|
||||||
isSelectable: isTargetPeerSelectable
|
action: isTargetPeerSelectable ? { [weak self] sourceView in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.displayTargetSelectionMenu(sourceView: sourceView)
|
||||||
|
} : nil
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
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 class JoinAffiliateProgramScreen: ViewControllerComponentContainer {
|
||||||
public final class Join {
|
public typealias Mode = JoinAffiliateProgramScreenMode
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
|
||||||
@ -1042,20 +1061,20 @@ private final class PeerBadgeComponent: Component {
|
|||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let peer: EnginePeer
|
let peer: EnginePeer
|
||||||
let isSelectable: Bool
|
let action: ((UIView) -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
isSelectable: Bool
|
action: ((UIView) -> Void)?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.isSelectable = isSelectable
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: PeerBadgeComponent, rhs: PeerBadgeComponent) -> Bool {
|
static func ==(lhs: PeerBadgeComponent, rhs: PeerBadgeComponent) -> Bool {
|
||||||
@ -1071,38 +1090,53 @@ private final class PeerBadgeComponent: Component {
|
|||||||
if lhs.peer != rhs.peer {
|
if lhs.peer != rhs.peer {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.isSelectable != rhs.isSelectable {
|
if (lhs.action == nil) != (rhs.action == nil) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView {
|
final class View: HighlightableButton {
|
||||||
private let background = ComponentView<Empty>()
|
private let background = ComponentView<Empty>()
|
||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
private var avatarNode: AvatarNode?
|
private var avatarNode: AvatarNode?
|
||||||
private var selectorIcon: ComponentView<Empty>?
|
private var selectorIcon: ComponentView<Empty>?
|
||||||
|
|
||||||
|
private var component: PeerBadgeComponent?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
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 {
|
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 height: CGFloat = 32.0
|
||||||
let avatarPadding: CGFloat = 1.0
|
let avatarPadding: CGFloat = 1.0
|
||||||
|
|
||||||
let avatarDiameter = height - avatarPadding * 2.0
|
let avatarDiameter = height - avatarPadding * 2.0
|
||||||
let avatarTextSpacing: CGFloat = 4.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(
|
let titleSize = self.title.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
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: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - avatarPadding - avatarDiameter - avatarTextSpacing - rightTextInset, height: height)
|
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)
|
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 let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
|
titleView.isUserInteractionEnabled = false
|
||||||
self.addSubview(titleView)
|
self.addSubview(titleView)
|
||||||
}
|
}
|
||||||
titleView.frame = titleFrame
|
titleView.frame = titleFrame
|
||||||
@ -1120,6 +1155,7 @@ private final class PeerBadgeComponent: Component {
|
|||||||
avatarNode = current
|
avatarNode = current
|
||||||
} else {
|
} else {
|
||||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
|
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
|
||||||
|
avatarNode.isUserInteractionEnabled = false
|
||||||
avatarNode.displaysAsynchronously = false
|
avatarNode.displaysAsynchronously = false
|
||||||
self.avatarNode = avatarNode
|
self.avatarNode = avatarNode
|
||||||
self.addSubview(avatarNode.view)
|
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)
|
let size = CGSize(width: avatarPadding + avatarDiameter + avatarTextSpacing + titleSize.width + rightTextInset, height: height)
|
||||||
|
|
||||||
if component.isSelectable {
|
if component.action != nil {
|
||||||
let selectorIcon: ComponentView<Empty>
|
let selectorIcon: ComponentView<Empty>
|
||||||
if let current = self.selectorIcon {
|
if let current = self.selectorIcon {
|
||||||
selectorIcon = current
|
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)
|
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 let selectorIconView = selectorIcon.view {
|
||||||
if selectorIconView.superview == nil {
|
if selectorIconView.superview == nil {
|
||||||
|
selectorIconView.isUserInteractionEnabled = false
|
||||||
self.addSubview(selectorIconView)
|
self.addSubview(selectorIconView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: selectorIconView, frame: selectorIconFrame)
|
transition.setFrame(view: selectorIconView, frame: selectorIconFrame)
|
||||||
@ -1162,7 +1199,7 @@ private final class PeerBadgeComponent: Component {
|
|||||||
let _ = self.background.update(
|
let _ = self.background.update(
|
||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(FilledRoundedRectangleComponent(
|
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,
|
cornerRadius: .minEdge,
|
||||||
smoothCorners: false
|
smoothCorners: false
|
||||||
)),
|
)),
|
||||||
@ -1171,6 +1208,7 @@ private final class PeerBadgeComponent: Component {
|
|||||||
)
|
)
|
||||||
if let backgroundView = self.background.view {
|
if let backgroundView = self.background.view {
|
||||||
if backgroundView.superview == nil {
|
if backgroundView.superview == nil {
|
||||||
|
backgroundView.isUserInteractionEnabled = false
|
||||||
self.insertSubview(backgroundView, at: 0)
|
self.insertSubview(backgroundView, at: 0)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
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 {
|
private final class TableAlertContentComponet: CombinedComponent {
|
||||||
let content: AnyComponent<Empty> = AnyComponent(VStack([
|
let theme: PresentationTheme
|
||||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent(
|
let title: String
|
||||||
text: .plain(NSAttributedString(string: title, font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor)),
|
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
|
horizontalAlignment: .center
|
||||||
))),
|
),
|
||||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent(
|
availableSize: CGSize(width: context.availableSize.width, height: 10000.0),
|
||||||
text: .plain(NSAttributedString(string: text, font: Font.regular(17.0), textColor: theme.actionSheet.primaryTextColor)),
|
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,
|
horizontalAlignment: .center,
|
||||||
maximumNumberOfLines: 0
|
maximumNumberOfLines: 0,
|
||||||
))),
|
lineSpacing: 0.2
|
||||||
AnyComponentWithIdentity(id: 2, component: AnyComponent(table)),
|
),
|
||||||
], spacing: 10.0))
|
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(
|
return componentAlertController(
|
||||||
theme: AlertControllerTheme(presentationTheme: theme, fontSize: .regular),
|
theme: AlertControllerTheme(presentationTheme: theme, fontSize: .regular),
|
||||||
content: content,
|
content: AnyComponent(TableAlertContentComponet(
|
||||||
|
theme: theme,
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
table: table
|
||||||
|
)),
|
||||||
actions: actions,
|
actions: actions,
|
||||||
actionLayout: .horizontal
|
actionLayout: .horizontal
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,6 +20,7 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
|
|||||||
case semitransparentBadge(String, UIColor)
|
case semitransparentBadge(String, UIColor)
|
||||||
case titleBadge(String, UIColor)
|
case titleBadge(String, UIColor)
|
||||||
case image(UIImage, CGSize)
|
case image(UIImage, CGSize)
|
||||||
|
case labelBadge(String)
|
||||||
|
|
||||||
var text: String {
|
var text: String {
|
||||||
switch self {
|
switch self {
|
||||||
@ -27,14 +28,14 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
|
|||||||
return ""
|
return ""
|
||||||
case let .attributedText(text):
|
case let .attributedText(text):
|
||||||
return text.string
|
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
|
return text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var badgeColor: UIColor? {
|
var badgeColor: UIColor? {
|
||||||
switch self {
|
switch self {
|
||||||
case .none, .text, .coloredText, .image, .attributedText:
|
case .none, .text, .coloredText, .image, .attributedText, .labelBadge:
|
||||||
return nil
|
return nil
|
||||||
case let .badge(_, color), let .semitransparentBadge(_, color), let .titleBadge(_, color):
|
case let .badge(_, color), let .semitransparentBadge(_, color), let .titleBadge(_, color):
|
||||||
return color
|
return color
|
||||||
@ -170,6 +171,9 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
|||||||
} else if case .titleBadge = item.label {
|
} else if case .titleBadge = item.label {
|
||||||
labelColorValue = presentationData.theme.list.itemCheckColors.foregroundColor
|
labelColorValue = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||||
labelFont = Font.medium(11.0)
|
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 {
|
} else if case let .coloredText(_, color) = item.label {
|
||||||
switch color {
|
switch color {
|
||||||
case .generic:
|
case .generic:
|
||||||
@ -274,6 +278,14 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
|||||||
if self.labelBadgeNode.supernode == nil {
|
if self.labelBadgeNode.supernode == nil {
|
||||||
self.insertSubnode(self.labelBadgeNode, belowSubnode: self.labelNode)
|
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 {
|
} else if item.additionalBadgeLabel != nil {
|
||||||
if previousItem?.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)
|
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)
|
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 {
|
} 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)
|
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 {
|
} else {
|
||||||
labelFrame = CGRect(origin: CGPoint(x: width - rightInset - labelSize.width, y: 12.0), size: labelSize)
|
labelFrame = CGRect(origin: CGPoint(x: width - rightInset - labelSize.width, y: 12.0), size: labelSize)
|
||||||
}
|
}
|
||||||
@ -347,6 +361,8 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
|||||||
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 {
|
} else if case .titleBadge = item.label {
|
||||||
labelBadgeNodeFrame = labelFrame.insetBy(dx: -4.0, dy: -2.0 + UIScreenPixel)
|
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 {
|
} else if let additionalLabelNode = self.additionalLabelNode {
|
||||||
labelBadgeNodeFrame = additionalLabelNode.frame.insetBy(dx: -4.0, dy: -2.0 + UIScreenPixel)
|
labelBadgeNodeFrame = additionalLabelNode.frame.insetBy(dx: -4.0, dy: -2.0 + UIScreenPixel)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1215,6 +1215,7 @@ private enum InfoSection: Int, CaseIterable {
|
|||||||
case permissions
|
case permissions
|
||||||
case peerInfoTrailing
|
case peerInfoTrailing
|
||||||
case peerMembers
|
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])] {
|
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
|
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 {
|
if let businessHours = cachedData.businessHours {
|
||||||
@ -1921,13 +1939,13 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
interaction.editingOpenPublicLinkSetup()
|
interaction.editingOpenPublicLinkSetup()
|
||||||
}))
|
}))
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let programTitleValue: String
|
let programTitleValue: PeerInfoScreenDisclosureItem.Label
|
||||||
if let cachedData = data.cachedData as? CachedUserData, let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil {
|
if let cachedData = data.cachedData as? CachedUserData, let starRefProgram = cachedData.starRefProgram, starRefProgram.endDate == nil {
|
||||||
programTitleValue = "\(starRefProgram.commissionPermille / 10)%"
|
programTitleValue = .labelBadge("\(starRefProgram.commissionPermille / 10)%")
|
||||||
} else {
|
} 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()
|
interaction.editingOpenAffiliateProgram()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -8575,7 +8593,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func editingOpenAffiliateProgram() {
|
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)
|
let _ = (self.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: self.context, peerId: peer.id, mode: .editProgram)
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -8584,8 +8603,85 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
let controller = self.context.sharedContext.makeAffiliateProgramSetupScreen(context: self.context, initialData: initialData)
|
let controller = self.context.sharedContext.makeAffiliateProgramSetupScreen(context: self.context, initialData: initialData)
|
||||||
self.controller?.push(controller)
|
self.controller?.push(controller)
|
||||||
})
|
})
|
||||||
} else if let channel = self.data?.peer as? TelegramChannel {
|
} else if let starRefProgram = (self.data?.cachedData as? CachedUserData)?.starRefProgram, starRefProgram.endDate == nil {
|
||||||
let _ = (self.context.sharedContext.makeAffiliateProgramSetupScreenInitialData(context: self.context, peerId: channel.id, mode: .connectedPrograms)
|
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
|
|> deliverOnMainQueue).startStandalone(next: { [weak self] initialData in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import UndoUI
|
|||||||
import ListActionItemComponent
|
import ListActionItemComponent
|
||||||
import StarsAvatarComponent
|
import StarsAvatarComponent
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
|
import ListItemComponentAdaptor
|
||||||
|
import ItemListUI
|
||||||
|
|
||||||
private let initialSubscriptionsDisplayedLimit: Int32 = 3
|
private let initialSubscriptionsDisplayedLimit: Int32 = 3
|
||||||
|
|
||||||
@ -102,6 +104,7 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
private let descriptionView = ComponentView<Empty>()
|
private let descriptionView = ComponentView<Empty>()
|
||||||
|
|
||||||
private let balanceView = ComponentView<Empty>()
|
private let balanceView = ComponentView<Empty>()
|
||||||
|
private let earnStarsSection = ComponentView<Empty>()
|
||||||
|
|
||||||
private let subscriptionsView = ComponentView<Empty>()
|
private let subscriptionsView = ComponentView<Empty>()
|
||||||
|
|
||||||
@ -657,6 +660,47 @@ final class StarsTransactionsScreenComponent: Component {
|
|||||||
starTransition.setFrame(view: balanceView, frame: balanceFrame)
|
starTransition.setFrame(view: balanceView, frame: balanceFrame)
|
||||||
}
|
}
|
||||||
contentHeight += balanceSize.height
|
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
|
contentHeight += 44.0
|
||||||
|
|
||||||
let fontBaseDisplaySize = 17.0
|
let fontBaseDisplaySize = 17.0
|
||||||
|
|||||||
@ -2839,6 +2839,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
public func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController {
|
public func makeAffiliateProgramSetupScreen(context: AccountContext, initialData: AffiliateProgramSetupScreenInitialData) -> ViewController {
|
||||||
return AffiliateProgramSetupScreen(context: context, initialContent: initialData)
|
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? {
|
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