mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stars subscriptions
This commit is contained in:
parent
4a0d46047c
commit
df311bb022
@ -12676,3 +12676,9 @@ Sorry for the inconvenience.";
|
||||
"Stickers.CreateSticker" = "Create\nSticker";
|
||||
|
||||
"InviteLink.CreateNewInfo" = "You can create additional invite links that are limited by time, number of users, or require a paid subscription.";
|
||||
|
||||
"InviteLink.CopyShort" = "Copy";
|
||||
"InviteLink.ShareShort" = "Share";
|
||||
|
||||
"Stars.Subscription.Terms" = "By subscribing you agree to the [Terms of Service]().";
|
||||
"Stars.Subscription.Terms_URL" = "https://telegram.org/tos/stars";
|
||||
|
@ -1007,7 +1007,8 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, extendedMedia: [TelegramExtendedMedia], inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController
|
||||
func makeStarsTransactionScreen(context: AccountContext, transaction: StarsContext.State.Transaction, peer: EnginePeer) -> ViewController
|
||||
func makeStarsReceiptScreen(context: AccountContext, receipt: BotPaymentReceipt) -> ViewController
|
||||
func makeStarsSubscriptionScreen(context: AccountContext, subscription: StarsContext.State.Subscription) -> ViewController
|
||||
func makeStarsSubscriptionScreen(context: AccountContext, subscription: StarsContext.State.Subscription, update: @escaping (Bool) -> Void) -> ViewController
|
||||
func makeStarsSubscriptionScreen(context: AccountContext, peer: EnginePeer, pricing: StarsSubscriptionPricing, importer: PeerInvitationImportersState.Importer, usdRate: Double) -> ViewController
|
||||
func makeStarsStatisticsScreen(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) -> ViewController
|
||||
func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
|
@ -1225,6 +1225,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var avatarBadgeBackground: ASImageNode?
|
||||
let onlineNode: PeerOnlineMarkerNode
|
||||
var avatarTimerBadge: AvatarBadgeView?
|
||||
private var starView: StarView?
|
||||
let pinnedIconNode: ASImageNode
|
||||
var secretIconNode: ASImageNode?
|
||||
var verifiedIconView: ComponentHostView<Empty>?
|
||||
@ -1827,6 +1828,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if let item = self.item, case .chatList = item.index {
|
||||
self.onlineNode.setImage(PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .highlighted, voiceChat: self.onlineIsVoiceChat), color: nil, transition: transition)
|
||||
self.starView?.setOutlineColor(item.presentationData.theme.chatList.itemHighlightedBackgroundColor, transition: transition)
|
||||
}
|
||||
} else {
|
||||
if self.highlightedBackgroundNode.supernode != nil {
|
||||
@ -1845,12 +1847,16 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if let item = self.item {
|
||||
let onlineIcon: UIImage?
|
||||
let effectiveBackgroundColor: UIColor
|
||||
if item.isPinned {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: self.onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
} else {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: self.onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
}
|
||||
self.onlineNode.setImage(onlineIcon, color: nil, transition: transition)
|
||||
self.starView?.setOutlineColor(effectiveBackgroundColor, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2934,6 +2940,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
titleIconsWidth += currentMutedIconImage.size.width
|
||||
}
|
||||
|
||||
var isSubscription = false
|
||||
var isSecret = false
|
||||
if !isPeerGroup {
|
||||
if case let .chatList(index) = item.index, index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
@ -2978,6 +2985,9 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
break
|
||||
}
|
||||
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
|
||||
if peer.isSubscription {
|
||||
isSubscription = true
|
||||
}
|
||||
if case let .peer(peerData) = item.content, peerData.customMessageListData?.hidePeerStatus == true {
|
||||
currentCredibilityIconContent = nil
|
||||
} else if case .savedMessagesChats = item.chatListLocation, peer.id == item.context.account.peerId {
|
||||
@ -3635,15 +3645,39 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateSublayerTransformScale(node: strongSelf.onlineNode, scale: (1.0 - onlineInlineNavigationFraction) * 1.0 + onlineInlineNavigationFraction * 0.00001)
|
||||
|
||||
let onlineIcon: UIImage?
|
||||
let effectiveBackgroundColor: UIColor
|
||||
if strongSelf.reallyHighlighted {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .highlighted, voiceChat: onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemHighlightedBackgroundColor
|
||||
} else if case let .chatList(index) = item.index, index.pinningIndex != nil {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .pinned, voiceChat: onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.pinnedItemBackgroundColor
|
||||
} else {
|
||||
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular, voiceChat: onlineIsVoiceChat)
|
||||
effectiveBackgroundColor = item.presentationData.theme.chatList.itemBackgroundColor
|
||||
}
|
||||
strongSelf.onlineNode.setImage(onlineIcon, color: item.presentationData.theme.list.itemCheckColors.foregroundColor, transition: .immediate)
|
||||
|
||||
if isSubscription {
|
||||
let starView: StarView
|
||||
if let current = strongSelf.starView {
|
||||
starView = current
|
||||
} else {
|
||||
starView = StarView()
|
||||
strongSelf.starView = starView
|
||||
strongSelf.view.addSubview(starView)
|
||||
// strongSelf.mainContentContainerNode.view.addSubview(starView)
|
||||
}
|
||||
starView.outlineColor = effectiveBackgroundColor
|
||||
|
||||
let starSize = CGSize(width: 20.0, height: 20.0)
|
||||
let starFrame = CGRect(origin: CGPoint(x: avatarFrame.maxX - starSize.width + 1.0, y: avatarFrame.maxY - starSize.height + 1.0), size: starSize)
|
||||
transition.updateFrame(view: starView, frame: starFrame)
|
||||
} else if let starView = strongSelf.starView {
|
||||
strongSelf.starView = nil
|
||||
starView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let autoremoveTimeoutFraction: CGFloat
|
||||
if online {
|
||||
autoremoveTimeoutFraction = 0.0
|
||||
@ -4746,3 +4780,47 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class StarView: UIView {
|
||||
let outline = SimpleLayer()
|
||||
let foreground = SimpleLayer()
|
||||
|
||||
var outlineColor: UIColor = .white {
|
||||
didSet {
|
||||
self.outline.layerTintColor = self.outlineColor.cgColor
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.outline.contents = UIImage(bundleImageName: "Premium/Stars/StarMediumOutline")?.cgImage
|
||||
self.foreground.contents = UIImage(bundleImageName: "Premium/Stars/StarMedium")?.cgImage
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(self.outline)
|
||||
self.layer.addSublayer(self.foreground)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func setOutlineColor(_ color: UIColor, transition: ContainedViewLayoutTransition) {
|
||||
if case let .animated(duration, curve) = transition, color != self.outlineColor {
|
||||
let snapshotLayer = SimpleLayer()
|
||||
snapshotLayer.layerTintColor = self.outlineColor.cgColor
|
||||
snapshotLayer.contents = self.outline.contents
|
||||
snapshotLayer.frame = self.outline.bounds
|
||||
self.layer.insertSublayer(snapshotLayer, above: self.outline)
|
||||
snapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in
|
||||
snapshotLayer?.removeFromSuperlayer()
|
||||
})
|
||||
}
|
||||
self.outlineColor = color
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
self.outline.frame = self.bounds
|
||||
self.foreground.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ public enum ChatListNotice: Equatable {
|
||||
case birthdayPremiumGift(peers: [EnginePeer], birthdays: [EnginePeer.Id: TelegramBirthday])
|
||||
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
|
||||
case premiumGrace
|
||||
case starsSubscriptionLowBalance
|
||||
case starsSubscriptionLowBalance(amount: Int64)
|
||||
}
|
||||
|
||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
|
@ -262,10 +262,10 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
okButtonLayout = makeOkButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelConfirm, font: titleFont, textColor: item.theme.list.itemAccentColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
cancelButtonLayout = makeCancelButtonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_SessionReview_PanelReject, font: titleFont, textColor: item.theme.list.itemDestructiveColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
case .starsSubscriptionLowBalance:
|
||||
let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: "5 Stars needed for Astro Paws", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
|
||||
case let .starsSubscriptionLowBalance(amount):
|
||||
let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: "⭐️ \(amount) Stars needed for your subscriptions", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
|
||||
titleString = titleStringValue
|
||||
textString = NSAttributedString(string: "Insufficient funds to cover your subscription.", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
textString = NSAttributedString(string: "Insufficient funds to cover your subscriptions.", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
}
|
||||
|
||||
var leftInset: CGFloat = sideInset
|
||||
|
@ -79,7 +79,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
|
||||
|
||||
case subscriptionFeeToggle(PresentationTheme, String, Bool, Bool)
|
||||
case subscriptionFee(PresentationTheme, String, Bool, Int64?)
|
||||
case subscriptionFee(PresentationTheme, String, Bool, Int64?, String)
|
||||
case subscriptionFeeInfo(PresentationTheme, String)
|
||||
|
||||
case requestApproval(PresentationTheme, String, Bool, Bool)
|
||||
@ -182,8 +182,8 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .subscriptionFee(lhsTheme, lhsText, lhsValue, lhsEnabled):
|
||||
if case let .subscriptionFee(rhsTheme, rhsText, rhsValue, rhsEnabled) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled {
|
||||
case let .subscriptionFee(lhsTheme, lhsText, lhsValue, lhsEnabled, lhsLabel):
|
||||
if case let .subscriptionFee(rhsTheme, rhsText, rhsValue, rhsEnabled, rhsLabel) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsEnabled == rhsEnabled, lhsLabel == rhsLabel {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -288,7 +288,6 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
}, action: {})
|
||||
case let .titleInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
|
||||
case let .subscriptionFeeToggle(_, text, value, enabled):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: enabled, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.updateState { state in
|
||||
@ -302,13 +301,13 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
case let .subscriptionFee(_, placeholder, enabled, value):
|
||||
case let .subscriptionFee(_, placeholder, enabled, value, label):
|
||||
let title = NSMutableAttributedString(string: "⭐️", font: Font.semibold(18.0), textColor: .white)
|
||||
if let range = title.string.range(of: "⭐️") {
|
||||
title.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: title.string))
|
||||
title.addAttribute(.baselineOffset, value: -1.0, range: NSRange(range, in: title.string))
|
||||
}
|
||||
return ItemListSingleLineInputItem(context: arguments.context, presentationData: presentationData, title: title, text: value.flatMap { "\($0)" } ?? "", placeholder: placeholder, type: .number, spacing: 3.0, enabled: enabled, sectionId: self.section, textUpdated: { text in
|
||||
return ItemListSingleLineInputItem(context: arguments.context, presentationData: presentationData, title: title, text: value.flatMap { "\($0)" } ?? "", placeholder: placeholder, label: label, type: .number, spacing: 3.0, enabled: enabled, sectionId: self.section, textUpdated: { text in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
if let value = Int64(text) {
|
||||
@ -318,7 +317,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
}, action: {})
|
||||
}, action: {})
|
||||
case let .subscriptionFeeInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .requestApproval(_, text, value, enabled):
|
||||
@ -458,7 +457,7 @@ private enum InviteLinksEditEntry: ItemListNodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: InviteLinkEditControllerState, isGroup: Bool, isPublic: Bool, presentationData: PresentationData) -> [InviteLinksEditEntry] {
|
||||
private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state: InviteLinkEditControllerState, isGroup: Bool, isPublic: Bool, presentationData: PresentationData, starsState: StarsRevenueStats?) -> [InviteLinksEditEntry] {
|
||||
var entries: [InviteLinksEditEntry] = []
|
||||
|
||||
entries.append(.titleHeader(presentationData.theme, presentationData.strings.InviteLink_Create_LinkNameTitle.uppercased()))
|
||||
@ -471,7 +470,11 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
|
||||
//TODO:localize
|
||||
entries.append(.subscriptionFeeToggle(presentationData.theme, "Require Monthly Fee", state.subscriptionEnabled, isEditingEnabled))
|
||||
if state.subscriptionEnabled {
|
||||
entries.append(.subscriptionFee(presentationData.theme, "Stars amount per month", isEditingEnabled, state.subscriptionFee))
|
||||
var label: String = ""
|
||||
if let subscriptionFee, subscriptionFee > 0, let starsState {
|
||||
label = formatTonUsdValue(state.subscriptionFee, divide: false, rate: starsState.usdRate, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
}
|
||||
entries.append(.subscriptionFee(presentationData.theme, "Stars amount per month", isEditingEnabled, state.subscriptionFee, label))
|
||||
}
|
||||
let infoText: String
|
||||
if let _ = invite, state.subscriptionEnabled {
|
||||
@ -545,7 +548,7 @@ private struct InviteLinkEditControllerState: Equatable {
|
||||
var updating = false
|
||||
}
|
||||
|
||||
public func inviteLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, invite: ExportedInvitation?, completion: ((ExportedInvitation?) -> Void)? = nil) -> ViewController {
|
||||
public func inviteLinkEditController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, invite: ExportedInvitation?, starsState: StarsRevenueStats? = nil, completion: ((ExportedInvitation?) -> Void)? = nil) -> ViewController {
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
@ -759,7 +762,7 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(invite == nil ? presentationData.strings.InviteLink_Create_Title : presentationData.strings.InviteLink_Create_EditTitle), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkEditControllerEntries(invite: invite, state: state, isGroup: isGroup, isPublic: isPublic, presentationData: presentationData), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkEditControllerEntries(invite: invite, state: state, isGroup: isGroup, isPublic: isPublic, presentationData: presentationData, starsState: starsState), style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
case let .mainLinkHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .mainLink(_, invite, peers, importersCount, isPublic):
|
||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: importersCount, peers: peers, displayButton: true, displayImporters: !isPublic, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||
return ItemListPermanentInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: invite, count: importersCount, peers: peers, displayButton: true, separateButtons: true, displayImporters: !isPublic, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||
if let invite = invite {
|
||||
arguments.copyLink(invite)
|
||||
}
|
||||
@ -268,7 +268,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
private func inviteLinkListControllerEntries(presentationData: PresentationData, exportedInvitation: EngineExportedPeerInvitation?, peer: EnginePeer?, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, importers: PeerInvitationImportersState?, creators: [ExportedInvitationCreator], admin: ExportedInvitationCreator?, tick: Int32) -> [InviteLinksListEntry] {
|
||||
private func inviteLinkListControllerEntries(presentationData: PresentationData, exportedInvitation: EngineExportedPeerInvitation?, peer: EnginePeer?, invites: [ExportedInvitation]?, revokedInvites: [ExportedInvitation]?, importers: PeerInvitationImportersState?, creators: [ExportedInvitationCreator], admin: ExportedInvitationCreator?, tick: Int32, starsState: StarsRevenueStats?) -> [InviteLinksListEntry] {
|
||||
var entries: [InviteLinksListEntry] = []
|
||||
|
||||
if admin == nil {
|
||||
@ -393,12 +393,12 @@ private struct InviteLinkListControllerState: Equatable {
|
||||
var revokingPrivateLink: Bool
|
||||
}
|
||||
|
||||
public func inviteLinkListController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, admin: ExportedInvitationCreator?) -> ViewController {
|
||||
public func inviteLinkListController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, admin: ExportedInvitationCreator?, starsRevenueContext: StarsRevenueStatsContext? = nil) -> ViewController {
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||
var navigationController: (() -> NavigationController?)?
|
||||
|
||||
|
||||
var dismissTooltipsImpl: (() -> Void)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
@ -409,6 +409,9 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
let starsContext: StarsRevenueStatsContext = starsRevenueContext ?? context.engine.payments.peerStarsRevenueContext(peerId: peerId)
|
||||
let starsStats = Atomic<StarsRevenueStats?>(value: nil)
|
||||
|
||||
let revokeLinkDisposable = MetaDisposable()
|
||||
actionsDisposable.add(revokeLinkDisposable)
|
||||
|
||||
@ -487,7 +490,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
presentControllerImpl?(shareController, nil)
|
||||
}, openMainLink: { invite in
|
||||
let controller = InviteLinkViewController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: invite, invitationsContext: nil, revokedInvitationsContext: revokedInvitesContext, importersContext: nil)
|
||||
let controller = InviteLinkViewController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: invite, invitationsContext: nil, revokedInvitationsContext: revokedInvitesContext, importersContext: nil, starsState: starsStats.with { $0 })
|
||||
pushControllerImpl?(controller)
|
||||
}, copyLink: { invite in
|
||||
UIPasteboard.general.string = invite.link
|
||||
@ -604,7 +607,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
presentInGlobalOverlayImpl?(contextController)
|
||||
}, createLink: {
|
||||
let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: nil, completion: { invite in
|
||||
let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: nil, starsState: starsStats.with( { $0 }), completion: { invite in
|
||||
if let invite = invite {
|
||||
invitesContext.add(invite)
|
||||
}
|
||||
@ -613,7 +616,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
pushControllerImpl?(controller)
|
||||
}, openLink: { invite in
|
||||
if let invite = invite {
|
||||
let controller = InviteLinkViewController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: invite, invitationsContext: invitesContext, revokedInvitationsContext: revokedInvitesContext, importersContext: nil)
|
||||
let controller = InviteLinkViewController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: invite, invitationsContext: invitesContext, revokedInvitationsContext: revokedInvitesContext, importersContext: nil, starsState: starsStats.with { $0 })
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
}, linkContextAction: { invite, canEdit, node, gesture in
|
||||
@ -730,7 +733,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: invite, completion: { invite in
|
||||
let controller = inviteLinkEditController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, invite: invite, starsState: starsStats.with( { $0 }), completion: { invite in
|
||||
if let invite = invite {
|
||||
if invite.isRevoked {
|
||||
invitesContext.remove(invite)
|
||||
@ -897,12 +900,14 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
invitesContext.state,
|
||||
revokedInvitesContext.state,
|
||||
creators,
|
||||
timerPromise.get()
|
||||
timerPromise.get(),
|
||||
starsContext.state
|
||||
)
|
||||
|> map { presentationData, exportedInvitation, peer, importersContext, importers, invites, revokedInvites, creators, tick -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|> map { presentationData, exportedInvitation, peer, importersContext, importers, invites, revokedInvites, creators, tick, starsState -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let previousInvites = previousInvites.swap(invites)
|
||||
let previousRevokedInvites = previousRevokedInvites.swap(revokedInvites)
|
||||
let previousCreators = previousCreators.swap(creators)
|
||||
let _ = starsStats.swap(starsState.stats)
|
||||
|
||||
var crossfade = false
|
||||
if (previousInvites?.hasLoadedOnce ?? false) != (invites.hasLoadedOnce) {
|
||||
@ -928,7 +933,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, exportedInvitation: exportedInvitation, peer: peer, invites: invites.hasLoadedOnce ? invites.invitations : nil, revokedInvites: revokedInvites.hasLoadedOnce ? revokedInvites.invitations : nil, importers: importers, creators: creators, admin: admin, tick: tick), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, exportedInvitation: exportedInvitation, peer: peer, invites: invites.hasLoadedOnce ? invites.invitations : nil, revokedInvites: revokedInvites.hasLoadedOnce ? revokedInvites.invitations : nil, importers: importers, creators: creators, admin: admin, tick: tick, starsState: starsState.stats), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -48,14 +48,24 @@ private var subscriptionLinkIcon: UIImage? = {
|
||||
class InviteLinkViewInteraction {
|
||||
let context: AccountContext
|
||||
let openPeer: (EnginePeer.Id) -> Void
|
||||
let openSubscription: (StarsSubscriptionPricing, PeerInvitationImportersState.Importer) -> Void
|
||||
let copyLink: (ExportedInvitation) -> Void
|
||||
let shareLink: (ExportedInvitation) -> Void
|
||||
let editLink: (ExportedInvitation) -> Void
|
||||
let contextAction: (ExportedInvitation, ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(context: AccountContext, openPeer: @escaping (EnginePeer.Id) -> Void, copyLink: @escaping (ExportedInvitation) -> Void, shareLink: @escaping (ExportedInvitation) -> Void, editLink: @escaping (ExportedInvitation) -> Void, contextAction: @escaping (ExportedInvitation, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
init(
|
||||
context: AccountContext,
|
||||
openPeer: @escaping (EnginePeer.Id) -> Void,
|
||||
openSubscription: @escaping (StarsSubscriptionPricing, PeerInvitationImportersState.Importer) -> Void,
|
||||
copyLink: @escaping (ExportedInvitation) -> Void,
|
||||
shareLink: @escaping (ExportedInvitation) -> Void,
|
||||
editLink: @escaping (ExportedInvitation) -> Void,
|
||||
contextAction: @escaping (ExportedInvitation, ASDisplayNode, ContextGesture?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.openPeer = openPeer
|
||||
self.openSubscription = openSubscription
|
||||
self.copyLink = copyLink
|
||||
self.shareLink = shareLink
|
||||
self.editLink = editLink
|
||||
@ -93,7 +103,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
case requestHeader(PresentationTheme, String, String, Bool)
|
||||
case request(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool)
|
||||
case importerHeader(PresentationTheme, String, String, Bool)
|
||||
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool, Bool)
|
||||
case importer(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32, Bool, Bool, PeerInvitationImportersState.Importer?, StarsSubscriptionPricing?)
|
||||
|
||||
var stableId: InviteLinkViewEntryId {
|
||||
switch self {
|
||||
@ -113,7 +123,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
return .request(peer.id)
|
||||
case .importerHeader:
|
||||
return .importerHeader
|
||||
case let .importer(_, _, _, peer, _, _, _):
|
||||
case let .importer(_, _, _, peer, _, _, _, _, _):
|
||||
return .importer(peer.id)
|
||||
}
|
||||
}
|
||||
@ -168,8 +178,8 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsJoinedViaFolderLink, lhsLoading):
|
||||
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsJoinedViaFolderLink, rhsLoading) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsJoinedViaFolderLink == rhsJoinedViaFolderLink, lhsLoading == rhsLoading {
|
||||
case let .importer(lhsIndex, lhsTheme, lhsDateTimeFormat, lhsPeer, lhsDate, lhsJoinedViaFolderLink, lhsLoading, lhsImporter, lhsPricing):
|
||||
if case let .importer(rhsIndex, rhsTheme, rhsDateTimeFormat, rhsPeer, rhsDate, rhsJoinedViaFolderLink, rhsLoading, rhsImporter, rhsPricing) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsDateTimeFormat == rhsDateTimeFormat, lhsPeer == rhsPeer, lhsDate == rhsDate, lhsJoinedViaFolderLink == rhsJoinedViaFolderLink, lhsLoading == rhsLoading, lhsImporter == rhsImporter, lhsPricing == rhsPricing {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -237,11 +247,11 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
case .importer:
|
||||
return true
|
||||
}
|
||||
case let .importer(lhsIndex, _, _, _, _, _, _):
|
||||
case let .importer(lhsIndex, _, _, _, _, _, _, _, _):
|
||||
switch rhs {
|
||||
case .link, .subscriptionHeader, .subscriptionPricing, .creatorHeader, .creator, .importerHeader, .request, .requestHeader:
|
||||
return false
|
||||
case let .importer(rhsIndex, _, _, _, _, _, _):
|
||||
case let .importer(rhsIndex, _, _, _, _, _, _, _, _):
|
||||
return lhsIndex < rhsIndex
|
||||
}
|
||||
}
|
||||
@ -250,7 +260,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
func item(account: Account, presentationData: PresentationData, interaction: InviteLinkViewInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .link(_, invite):
|
||||
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, count: 0, peers: [], displayButton: !invite.isRevoked, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
|
||||
return ItemListPermanentInviteLinkItem(context: interaction.context, presentationData: ItemListPresentationData(presentationData), invite: invite, count: 0, peers: [], displayButton: !invite.isRevoked, separateButtons: true, displayImporters: false, buttonColor: nil, sectionId: 0, style: .plain, copyAction: {
|
||||
interaction.copyLink(invite)
|
||||
}, shareAction: {
|
||||
if invitationAvailability(invite).isZero {
|
||||
@ -290,15 +300,35 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
|
||||
additionalText = .none
|
||||
}
|
||||
return SectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title, additionalText: additionalText)
|
||||
case let .importer(_, _, dateTimeFormat, peer, date, joinedViaFolderLink, loading):
|
||||
case let .importer(_, _, dateTimeFormat, peer, date, joinedViaFolderLink, loading, importer, pricing):
|
||||
let dateString: String
|
||||
if joinedViaFolderLink {
|
||||
dateString = presentationData.strings.InviteLink_LabelJoinedViaFolder
|
||||
} else {
|
||||
dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
}
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .peerList, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
interaction.openPeer(peer.id)
|
||||
|
||||
let label: ItemListPeerItemLabel
|
||||
if let pricing {
|
||||
//TODO:localize
|
||||
let text = NSMutableAttributedString()
|
||||
text.append(NSAttributedString(string: "⭐️\(pricing.amount)\n", font: Font.semibold(17.0), textColor: presentationData.theme.list.itemPrimaryTextColor))
|
||||
text.append(NSAttributedString(string: "per month", font: Font.regular(13.0), textColor: presentationData.theme.list.itemSecondaryTextColor))
|
||||
if let range = text.string.range(of: "⭐️") {
|
||||
text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: text.string))
|
||||
text.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(range, in: text.string))
|
||||
text.addAttribute(.baselineOffset, value: 3.5, range: NSRange(range, in: text.string))
|
||||
}
|
||||
label = .attributedText(text)
|
||||
} else {
|
||||
label = .none
|
||||
}
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: interaction.context, peer: peer, height: .peerList, nameStyle: .distinctBold, presence: nil, text: .text(dateString, .secondary), label: label, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: peer.id != account.peerId, sectionId: 0, action: {
|
||||
if let importer, let pricing {
|
||||
interaction.openSubscription(pricing, importer)
|
||||
} else {
|
||||
interaction.openPeer(peer.id)
|
||||
}
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, hasTopStripe: false, noInsets: true, style: .plain, tag: nil, shimmering: loading ? ItemListPeerItemShimmering(alternationIndex: 0) : nil)
|
||||
case let .request(_, _, dateTimeFormat, peer, date, loading):
|
||||
let dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
|
||||
@ -351,18 +381,20 @@ public final class InviteLinkViewController: ViewController {
|
||||
private let invitationsContext: PeerExportedInvitationsContext?
|
||||
private let revokedInvitationsContext: PeerExportedInvitationsContext?
|
||||
private let importersContext: PeerInvitationImportersContext?
|
||||
private let starsState: StarsRevenueStats?
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
fileprivate var presentationDataPromise = Promise<PresentationData>()
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, invite: ExportedInvitation, invitationsContext: PeerExportedInvitationsContext?, revokedInvitationsContext: PeerExportedInvitationsContext?, importersContext: PeerInvitationImportersContext?) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, invite: ExportedInvitation, invitationsContext: PeerExportedInvitationsContext?, revokedInvitationsContext: PeerExportedInvitationsContext?, importersContext: PeerInvitationImportersContext?, starsState: StarsRevenueStats? = nil) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.invite = invite
|
||||
self.invitationsContext = invitationsContext
|
||||
self.revokedInvitationsContext = revokedInvitationsContext
|
||||
self.importersContext = importersContext
|
||||
self.starsState = starsState
|
||||
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -550,14 +582,25 @@ public final class InviteLinkViewController: ViewController {
|
||||
self.interaction = InviteLinkViewInteraction(context: context, openPeer: { [weak self] peerId in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer else {
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
|
||||
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), keepStack: .always))
|
||||
}
|
||||
})
|
||||
}, openSubscription: { [weak self] pricing, importer in
|
||||
guard let controller = self?.controller else {
|
||||
return
|
||||
}
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
let subscriptionController = context.sharedContext.makeStarsSubscriptionScreen(context: context, peer: peer, pricing: pricing, importer: importer, usdRate: controller.starsState?.usdRate ?? 0.0)
|
||||
self?.controller?.push(subscriptionController)
|
||||
})
|
||||
}, copyLink: { [weak self] invite in
|
||||
UIPasteboard.general.string = invite.link
|
||||
|
||||
@ -766,7 +809,7 @@ public final class InviteLinkViewController: ViewController {
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||
self?.controller?.presentInGlobalOverlay(contextController)
|
||||
})
|
||||
@ -791,6 +834,8 @@ public final class InviteLinkViewController: ViewController {
|
||||
context.account.postbox.loadedPeerWithId(adminId)
|
||||
) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, requestsState, creatorPeer in
|
||||
if let strongSelf = self {
|
||||
let usdRate = strongSelf.controller?.starsState?.usdRate
|
||||
|
||||
var entries: [InviteLinkViewEntry] = []
|
||||
|
||||
entries.append(.link(presentationData.theme, invite))
|
||||
@ -802,7 +847,12 @@ public final class InviteLinkViewController: ViewController {
|
||||
var subtitle = "No one joined yet"
|
||||
if state.count > 0 {
|
||||
title += " x \(state.count)"
|
||||
subtitle = "You get approximately $\(Float(pricing.amount * Int64(state.count)) * 0.01) monthly"
|
||||
if let usdRate {
|
||||
let usdValue = formatTonUsdValue(pricing.amount * Int64(state.count), divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
subtitle = "You get approximately \(usdValue) monthly"
|
||||
} else {
|
||||
subtitle = ""
|
||||
}
|
||||
}
|
||||
entries.append(.subscriptionPricing(presentationData.theme, title, subtitle))
|
||||
}
|
||||
@ -856,14 +906,14 @@ public final class InviteLinkViewController: ViewController {
|
||||
loading = true
|
||||
let fakeUser = TelegramUser(id: EnginePeer.Id(namespace: .max, id: EnginePeer.Id.Id._internalFromInt64Value(0)), accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, subscriberCount: nil)
|
||||
for i in 0 ..< count {
|
||||
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, false, true))
|
||||
entries.append(.importer(Int32(i), presentationData.theme, presentationData.dateTimeFormat, EnginePeer.user(fakeUser), 0, false, true, nil, nil))
|
||||
}
|
||||
} else {
|
||||
count = min(4, Int32(state.importers.count))
|
||||
loading = false
|
||||
for importer in state.importers {
|
||||
if let peer = importer.peer.peer {
|
||||
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, importer.joinedViaFolderLink, false))
|
||||
entries.append(.importer(index, presentationData.theme, presentationData.dateTimeFormat, EnginePeer(peer), importer.date, importer.joinedViaFolderLink, false, importer, invite.pricing))
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
@ -953,7 +1003,7 @@ public final class InviteLinkViewController: ViewController {
|
||||
let revokedInvitationsContext = parentController.revokedInvitationsContext
|
||||
if let navigationController = navigationController {
|
||||
let updatedPresentationData = (self.presentationData, parentController.presentationDataPromise.get())
|
||||
let controller = inviteLinkEditController(context: self.context, updatedPresentationData: updatedPresentationData, peerId: self.peerId, invite: self.invite, completion: { [weak self] invite in
|
||||
let controller = inviteLinkEditController(context: self.context, updatedPresentationData: updatedPresentationData, peerId: self.peerId, invite: self.invite, starsState: self.controller?.starsState, completion: { [weak self] invite in
|
||||
if let invite = invite {
|
||||
if invite.isRevoked {
|
||||
invitationsContext?.remove(invite)
|
||||
|
@ -198,7 +198,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
|
||||
} else {
|
||||
string = presentationData.strings.MemberRequests_UserAddedToGroup(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: peer, text: string, action: nil, duration: 3), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: peer, title: nil, text: string, action: nil, duration: 3), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ private enum ItemBackgroundColor: Equatable {
|
||||
case .blue:
|
||||
return (UIColor(rgb: 0x00b5f7), UIColor(rgb: 0x00b2f6), UIColor(rgb: 0xa7f4ff))
|
||||
case .green:
|
||||
return (UIColor(rgb: 0x4aca62), UIColor(rgb: 0x43c85c), UIColor(rgb: 0xc5ffe6))
|
||||
return (UIColor(rgb: 0x31b73b), UIColor(rgb: 0x88d93b), UIColor(rgb: 0xc5ffe6))
|
||||
case .yellow:
|
||||
return (UIColor(rgb: 0xf8a953), UIColor(rgb: 0xf7a64e), UIColor(rgb: 0xfeffd7))
|
||||
case .red:
|
||||
@ -208,7 +208,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
self.iconBackgroundNode = ASDisplayNode()
|
||||
self.iconBackgroundNode.setLayerBlock { () -> CALayer in
|
||||
return CAShapeLayer()
|
||||
return CAGradientLayer()
|
||||
}
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
@ -283,8 +283,11 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if let shapeLayer = self.iconBackgroundNode.layer as? CAShapeLayer {
|
||||
shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: 0.0, y: 0.0, width: 40.0, height: 40.0)).cgPath
|
||||
self.iconBackgroundNode.cornerRadius = 20.0
|
||||
if let iconBackgroundLayer = self.iconBackgroundNode.layer as? CAGradientLayer {
|
||||
iconBackgroundLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
|
||||
iconBackgroundLayer.endPoint = CGPoint(x: 0.0, y: 1.0)
|
||||
iconBackgroundLayer.type = .axial
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,17 +347,24 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
transitionFraction = 0.0
|
||||
}
|
||||
|
||||
let topColor = color.colors.top
|
||||
let nextTopColor = nextColor.colors.top
|
||||
let iconColor: UIColor
|
||||
let colors = color.colors
|
||||
let nextColors = nextColor.colors
|
||||
let topIconColor: UIColor
|
||||
let bottomIconColor: UIColor
|
||||
if let _ = item.invite {
|
||||
if case .blue = color {
|
||||
iconColor = item.presentationData.theme.list.itemAccentColor
|
||||
if case .green = color, item.invite?.pricing != nil {
|
||||
topIconColor = color.colors.bottom
|
||||
bottomIconColor = color.colors.top
|
||||
} else if case .blue = color {
|
||||
topIconColor = item.presentationData.theme.list.itemAccentColor
|
||||
bottomIconColor = topIconColor
|
||||
} else {
|
||||
iconColor = nextTopColor.mixedWith(topColor, alpha: transitionFraction)
|
||||
topIconColor = nextColors.top.mixedWith(colors.top, alpha: transitionFraction)
|
||||
bottomIconColor = topIconColor
|
||||
}
|
||||
} else {
|
||||
iconColor = item.presentationData.theme.list.mediaPlaceholderColor
|
||||
topIconColor = item.presentationData.theme.list.mediaPlaceholderColor
|
||||
bottomIconColor = topIconColor
|
||||
}
|
||||
|
||||
let inviteLink = item.invite?.link?.replacingOccurrences(of: "https://", with: "") ?? ""
|
||||
@ -400,7 +410,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
if let range = text.string.range(of: "⭐️") {
|
||||
text.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: text.string))
|
||||
text.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(range, in: text.string))
|
||||
text.addAttribute(.baselineOffset, value: 2.5, range: NSRange(range, in: text.string))
|
||||
text.addAttribute(.baselineOffset, value: 3.5, range: NSRange(range, in: text.string))
|
||||
}
|
||||
pricingAttributedText = text
|
||||
}
|
||||
@ -526,8 +536,11 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
strongSelf.contextSourceNode.contentRect = extractedRect
|
||||
|
||||
if let layer = strongSelf.iconBackgroundNode.layer as? CAShapeLayer {
|
||||
layer.fillColor = iconColor.cgColor
|
||||
if let iconBackgroundLayer = strongSelf.iconBackgroundNode.layer as? CAGradientLayer {
|
||||
iconBackgroundLayer.colors = [
|
||||
topIconColor.cgColor,
|
||||
bottomIconColor.cgColor
|
||||
]
|
||||
}
|
||||
|
||||
if let _ = updatedTheme {
|
||||
@ -633,7 +646,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
|
||||
strongSelf.timerNode = timerNode
|
||||
strongSelf.offsetContainerNode.addSubnode(timerNode)
|
||||
}
|
||||
timerNode.update(color: iconColor, value: timerValue)
|
||||
timerNode.update(color: topIconColor, value: timerValue)
|
||||
} else if let timerNode = strongSelf.timerNode {
|
||||
strongSelf.timerNode = nil
|
||||
timerNode.removeFromSupernode()
|
||||
|
@ -32,6 +32,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
||||
let count: Int32
|
||||
let peers: [EnginePeer]
|
||||
let displayButton: Bool
|
||||
let separateButtons: Bool
|
||||
let displayImporters: Bool
|
||||
let buttonColor: UIColor?
|
||||
public let sectionId: ItemListSectionId
|
||||
@ -49,6 +50,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
||||
count: Int32,
|
||||
peers: [EnginePeer],
|
||||
displayButton: Bool,
|
||||
separateButtons: Bool = false,
|
||||
displayImporters: Bool,
|
||||
buttonColor: UIColor?,
|
||||
sectionId: ItemListSectionId,
|
||||
@ -65,6 +67,7 @@ public class ItemListPermanentInviteLinkItem: ListViewItem, ItemListItem {
|
||||
self.count = count
|
||||
self.peers = peers
|
||||
self.displayButton = displayButton
|
||||
self.separateButtons = separateButtons
|
||||
self.displayImporters = displayImporters
|
||||
self.buttonColor = buttonColor
|
||||
self.sectionId = sectionId
|
||||
@ -126,6 +129,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
private let addressButtonNode: HighlightTrackingButtonNode
|
||||
private let addressButtonIconNode: ASImageNode
|
||||
private var addressShimmerNode: ShimmerEffectNode?
|
||||
private var copyButtonNode: SolidRoundedButtonNode?
|
||||
private var shareButtonNode: SolidRoundedButtonNode?
|
||||
|
||||
private let avatarsButtonNode: HighlightTrackingButtonNode
|
||||
@ -234,6 +238,11 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
}
|
||||
}
|
||||
}
|
||||
self.copyButtonNode?.pressed = { [weak self] in
|
||||
if let strongSelf = self, let item = strongSelf.item {
|
||||
item.copyAction?()
|
||||
}
|
||||
}
|
||||
self.shareButtonNode?.pressed = { [weak self] in
|
||||
if let strongSelf = self, let item = strongSelf.item {
|
||||
item.shareAction?()
|
||||
@ -444,7 +453,31 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
|
||||
strongSelf.addressButtonNode.isHidden = item.contextAction == nil
|
||||
strongSelf.addressButtonIconNode.isHidden = item.contextAction == nil
|
||||
|
||||
|
||||
var effectiveSeparateButtons = item.separateButtons
|
||||
if let invite = item.invite, invitationAvailability(invite).isZero {
|
||||
effectiveSeparateButtons = false
|
||||
}
|
||||
|
||||
let copyButtonNode: SolidRoundedButtonNode
|
||||
if let currentCopyButtonNode = strongSelf.copyButtonNode {
|
||||
copyButtonNode = currentCopyButtonNode
|
||||
} else {
|
||||
let buttonTheme: SolidRoundedButtonTheme
|
||||
if let buttonColor = item.buttonColor {
|
||||
buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
} else {
|
||||
buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
|
||||
}
|
||||
copyButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: 50.0, cornerRadius: 11.0)
|
||||
copyButtonNode.title = item.presentationData.strings.InviteLink_CopyShort
|
||||
copyButtonNode.pressed = { [weak self] in
|
||||
self?.item?.copyAction?()
|
||||
}
|
||||
strongSelf.addSubnode(copyButtonNode)
|
||||
strongSelf.copyButtonNode = copyButtonNode
|
||||
}
|
||||
|
||||
let shareButtonNode: SolidRoundedButtonNode
|
||||
if let currentShareButtonNode = strongSelf.shareButtonNode {
|
||||
shareButtonNode = currentShareButtonNode
|
||||
@ -459,7 +492,7 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
if let invite = item.invite, invitationAvailability(invite).isZero {
|
||||
shareButtonNode.title = item.presentationData.strings.InviteLink_ReactivateLink
|
||||
} else {
|
||||
shareButtonNode.title = item.presentationData.strings.InviteLink_Share
|
||||
shareButtonNode.title = effectiveSeparateButtons ? item.presentationData.strings.InviteLink_ShareShort : item.presentationData.strings.InviteLink_Share
|
||||
}
|
||||
shareButtonNode.pressed = { [weak self] in
|
||||
self?.item?.shareAction?()
|
||||
@ -468,9 +501,19 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
strongSelf.shareButtonNode = shareButtonNode
|
||||
}
|
||||
|
||||
let buttonWidth = contentSize.width - leftInset - rightInset
|
||||
let buttonSpacing: CGFloat = 8.0
|
||||
var buttonWidth = contentSize.width - leftInset - rightInset
|
||||
var shareButtonOriginX = leftInset
|
||||
if effectiveSeparateButtons {
|
||||
buttonWidth = (buttonWidth - buttonSpacing) / 2.0
|
||||
shareButtonOriginX = leftInset + buttonWidth + buttonSpacing
|
||||
}
|
||||
|
||||
let _ = copyButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
|
||||
copyButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
|
||||
|
||||
let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
|
||||
shareButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
|
||||
shareButtonNode.frame = CGRect(x: shareButtonOriginX, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
|
||||
|
||||
var totalWidth = invitedPeersLayout.size.width
|
||||
var leftOrigin: CGFloat = floorToScreenPixels((params.width - invitedPeersLayout.size.width) / 2.0)
|
||||
@ -498,9 +541,15 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
|
||||
strongSelf.fieldButtonNode.isUserInteractionEnabled = item.invite != nil
|
||||
strongSelf.addressButtonIconNode.alpha = item.invite != nil ? 1.0 : 0.0
|
||||
|
||||
|
||||
strongSelf.copyButtonNode?.isUserInteractionEnabled = item.invite != nil
|
||||
strongSelf.copyButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4
|
||||
strongSelf.copyButtonNode?.isHidden = !item.displayButton || !effectiveSeparateButtons
|
||||
|
||||
strongSelf.shareButtonNode?.isUserInteractionEnabled = item.invite != nil
|
||||
strongSelf.shareButtonNode?.alpha = item.invite != nil ? 1.0 : 0.4
|
||||
strongSelf.shareButtonNode?.isHidden = !item.displayButton
|
||||
|
||||
strongSelf.avatarsButtonNode.isHidden = !item.displayImporters
|
||||
strongSelf.avatarsNode.isHidden = !item.displayImporters || item.invite == nil
|
||||
strongSelf.invitedPeersNode.isHidden = !item.displayImporters || item.invite == nil
|
||||
|
@ -251,6 +251,7 @@ public enum ItemListPeerItemLabel {
|
||||
case text(String, ItemListPeerItemLabelFont)
|
||||
case disclosure(String)
|
||||
case badge(String)
|
||||
case attributedText(NSAttributedString)
|
||||
}
|
||||
|
||||
public struct ItemListPeerItemSwitch {
|
||||
@ -728,7 +729,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
private var avatarButton: HighlightTrackingButton?
|
||||
|
||||
private let titleNode: TextNode
|
||||
private let labelNode: TextNode
|
||||
private let labelNode: TextNodeWithEntities
|
||||
private let labelBadgeNode: ASImageNode
|
||||
private var labelArrowNode: ASImageNode?
|
||||
private let statusNode: TextNode
|
||||
@ -829,10 +830,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
self.statusNode.contentMode = .left
|
||||
self.statusNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.labelNode = TextNode()
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
self.labelNode.contentMode = .left
|
||||
self.labelNode.contentsScale = UIScreen.main.scale
|
||||
self.labelNode = TextNodeWithEntities()
|
||||
|
||||
self.labelBadgeNode = ASImageNode()
|
||||
self.labelBadgeNode.displayWithoutProcessing = true
|
||||
@ -850,7 +848,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
self.containerNode.addSubnode(self.avatarNode)
|
||||
self.containerNode.addSubnode(self.titleNode)
|
||||
self.containerNode.addSubnode(self.statusNode)
|
||||
self.containerNode.addSubnode(self.labelNode)
|
||||
self.containerNode.addSubnode(self.labelNode.textNode)
|
||||
|
||||
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
||||
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
|
||||
@ -885,7 +883,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
public func asyncLayout() -> (_ item: ItemListPeerItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors, _ headerAtTop: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let makeLabelLayout = TextNodeWithEntities.asyncLayout(self.labelNode)
|
||||
let editableControlLayout = ItemListEditableControlNode.asyncLayout(self.editableControlNode)
|
||||
let reorderControlLayout = ItemListEditableReorderControlNode.asyncLayout(self.reorderControlNode)
|
||||
|
||||
@ -1156,42 +1154,49 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
editingOffset = 0.0
|
||||
}
|
||||
|
||||
var labelMaximumNumberOfLines = 1
|
||||
var labelInset: CGFloat = 0.0
|
||||
var labelAlignment: NSTextAlignment = .natural
|
||||
var updatedLabelArrowNode: ASImageNode?
|
||||
switch item.label {
|
||||
case .none:
|
||||
break
|
||||
case let .text(text, font):
|
||||
let selectedFont: UIFont
|
||||
switch font {
|
||||
case .standard:
|
||||
selectedFont = labelFont
|
||||
case let .custom(value):
|
||||
selectedFont = value
|
||||
}
|
||||
labelAttributedString = NSAttributedString(string: text, font: selectedFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
labelInset += 15.0
|
||||
case let .disclosure(text):
|
||||
if let currentLabelArrowNode = currentLabelArrowNode {
|
||||
updatedLabelArrowNode = currentLabelArrowNode
|
||||
} else {
|
||||
let arrowNode = ASImageNode()
|
||||
arrowNode.isLayerBacked = true
|
||||
arrowNode.displayWithoutProcessing = true
|
||||
arrowNode.displaysAsynchronously = false
|
||||
arrowNode.image = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||
updatedLabelArrowNode = arrowNode
|
||||
}
|
||||
labelInset += 40.0
|
||||
labelAttributedString = NSAttributedString(string: text, font: labelDisclosureFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
case let .badge(text):
|
||||
labelAttributedString = NSAttributedString(string: text, font: badgeFont, textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
labelInset += 15.0
|
||||
case .none:
|
||||
break
|
||||
case let .attributedText(text):
|
||||
labelAttributedString = text
|
||||
labelInset += 15.0
|
||||
labelMaximumNumberOfLines = 2
|
||||
labelAlignment = .right
|
||||
case let .text(text, font):
|
||||
let selectedFont: UIFont
|
||||
switch font {
|
||||
case .standard:
|
||||
selectedFont = labelFont
|
||||
case let .custom(value):
|
||||
selectedFont = value
|
||||
}
|
||||
labelAttributedString = NSAttributedString(string: text, font: selectedFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
labelInset += 15.0
|
||||
case let .disclosure(text):
|
||||
if let currentLabelArrowNode = currentLabelArrowNode {
|
||||
updatedLabelArrowNode = currentLabelArrowNode
|
||||
} else {
|
||||
let arrowNode = ASImageNode()
|
||||
arrowNode.isLayerBacked = true
|
||||
arrowNode.displayWithoutProcessing = true
|
||||
arrowNode.displaysAsynchronously = false
|
||||
arrowNode.image = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||
updatedLabelArrowNode = arrowNode
|
||||
}
|
||||
labelInset += 40.0
|
||||
labelAttributedString = NSAttributedString(string: text, font: labelDisclosureFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
case let .badge(text):
|
||||
labelAttributedString = NSAttributedString(string: text, font: badgeFont, textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
labelInset += 15.0
|
||||
}
|
||||
|
||||
labelInset += reorderInset
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, backgroundColor: nil, maximumNumberOfLines: labelMaximumNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 16.0 - editingOffset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: labelAlignment, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let constrainedTitleSize = CGSize(width: params.width - leftInset - 12.0 - editingOffset - rightInset - labelLayout.size.width - labelInset - titleIconsWidth, height: CGFloat.greatestFiniteMagnitude)
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedTitleSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
@ -1351,9 +1356,10 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
|
||||
let _ = titleApply()
|
||||
let _ = statusApply()
|
||||
let _ = labelApply()
|
||||
|
||||
strongSelf.labelNode.isHidden = labelAttributedString == nil
|
||||
if case let .account(context) = item.context {
|
||||
let _ = labelApply(TextNodeWithEntities.Arguments(context: context, cache: item.context.animationCache, renderer: item.context.animationRenderer, placeholderColor: item.presentationData.theme.list.mediaPlaceholderColor, attemptSynchronous: false))
|
||||
}
|
||||
strongSelf.labelNode.textNode.isHidden = labelAttributedString == nil
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
@ -1496,15 +1502,15 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
let labelFrame: CGRect
|
||||
if case .badge = item.label {
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelLayout.size.width) / 2.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
|
||||
strongSelf.labelNode.frame = labelFrame
|
||||
strongSelf.labelNode.textNode.frame = labelFrame
|
||||
} else {
|
||||
labelFrame = CGRect(origin: CGPoint(x: revealOffset + params.width - labelLayout.size.width - rightLabelInset, y: floor((contentSize.height - labelLayout.size.height) / 2.0) + 1.0), size: labelLayout.size)
|
||||
transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
|
||||
transition.updateFrame(node: strongSelf.labelNode.textNode, frame: labelFrame)
|
||||
}
|
||||
|
||||
if let updateBadgeImage = updatedLabelBadgeImage {
|
||||
if strongSelf.labelBadgeNode.supernode == nil {
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.labelBadgeNode, belowSubnode: strongSelf.labelNode)
|
||||
strongSelf.containerNode.insertSubnode(strongSelf.labelBadgeNode, belowSubnode: strongSelf.labelNode.textNode)
|
||||
}
|
||||
strongSelf.labelBadgeNode.image = updateBadgeImage
|
||||
}
|
||||
@ -1853,16 +1859,16 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
}
|
||||
|
||||
let badgeDiameter: CGFloat = 20.0
|
||||
let labelSize = self.labelNode.frame.size
|
||||
let labelSize = self.labelNode.textNode.frame.size
|
||||
|
||||
let badgeWidth = max(badgeDiameter, labelSize.width + 10.0)
|
||||
let labelFrame: CGRect
|
||||
if case .badge = item.label {
|
||||
labelFrame = CGRect(origin: CGPoint(x: offset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelSize.width) / 2.0, y: self.labelNode.frame.minY), size: labelSize)
|
||||
labelFrame = CGRect(origin: CGPoint(x: offset + params.width - rightLabelInset - badgeWidth + (badgeWidth - labelSize.width) / 2.0, y: self.labelNode.textNode.frame.minY), size: labelSize)
|
||||
} else {
|
||||
labelFrame = CGRect(origin: CGPoint(x: offset + params.width - self.labelNode.bounds.size.width - rightLabelInset, y: self.labelNode.frame.minY), size: self.labelNode.bounds.size)
|
||||
labelFrame = CGRect(origin: CGPoint(x: offset + params.width - self.labelNode.textNode.bounds.size.width - rightLabelInset, y: self.labelNode.textNode.frame.minY), size: self.labelNode.textNode.bounds.size)
|
||||
}
|
||||
transition.updateFrame(node: self.labelNode, frame: labelFrame)
|
||||
transition.updateFrame(node: self.labelNode.textNode, frame: labelFrame)
|
||||
|
||||
transition.updateFrame(node: self.labelBadgeNode, frame: CGRect(origin: CGPoint(x: offset + params.width - rightLabelInset - badgeWidth, y: self.labelBadgeNode.frame.minY), size: CGSize(width: badgeWidth, height: badgeDiameter)))
|
||||
|
||||
|
@ -50,6 +50,7 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
let title: NSAttributedString
|
||||
let text: String
|
||||
let placeholder: String
|
||||
let label: String?
|
||||
let type: ItemListSingleLineInputItemType
|
||||
let returnKeyType: UIReturnKeyType
|
||||
let alignment: ItemListSingleLineInputAlignment
|
||||
@ -68,12 +69,13 @@ public class ItemListSingleLineInputItem: ListViewItem, ItemListItem {
|
||||
let cleared: (() -> Void)?
|
||||
public let tag: ItemListItemTag?
|
||||
|
||||
public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
|
||||
public init(context: AccountContext? = nil, presentationData: ItemListPresentationData, title: NSAttributedString, text: String, placeholder: String, label: String? = nil, type: ItemListSingleLineInputItemType = .regular(capitalization: true, autocorrection: true), returnKeyType: UIReturnKeyType = .`default`, alignment: ItemListSingleLineInputAlignment = .default, spacing: CGFloat = 0.0, clearType: ItemListSingleLineInputClearType = .none, maxLength: Int = 0, enabled: Bool = true, selectAllOnFocus: Bool = false, secondaryStyle: Bool = false, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId, textUpdated: @escaping (String) -> Void, shouldUpdateText: @escaping (String) -> Bool = { _ in return true }, processPaste: ((String) -> String)? = nil, updatedFocus: ((Bool) -> Void)? = nil, action: @escaping () -> Void, cleared: (() -> Void)? = nil) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.placeholder = placeholder
|
||||
self.label = label
|
||||
self.type = type
|
||||
self.returnKeyType = returnKeyType
|
||||
self.alignment = alignment
|
||||
|
@ -239,6 +239,7 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
itemSubtitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast)
|
||||
} else {
|
||||
if let _ = item.transaction.subscriptionPeriod {
|
||||
//TODO:localize
|
||||
itemSubtitle = "Monthly subscription fee"
|
||||
} else {
|
||||
itemSubtitle = nil
|
||||
@ -276,9 +277,15 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
itemLabel = NSAttributedString(string: labelString, font: Font.medium(fontBaseDisplaySize), textColor: labelString.hasPrefix("-") ? item.presentationData.theme.list.itemDestructiveColor : item.presentationData.theme.list.itemDisclosureActions.constructive.fillColor)
|
||||
|
||||
var itemDateColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||
itemDate = stringForMediumCompactDate(timestamp: item.transaction.date, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
|
||||
if item.transaction.flags.contains(.isRefund) {
|
||||
itemDate += " – \(item.presentationData.strings.Stars_Intro_Transaction_Refund)"
|
||||
} else if item.transaction.flags.contains(.isPending) {
|
||||
itemDate += " – \(item.presentationData.strings.Monetization_Transaction_Pending)"
|
||||
} else if item.transaction.flags.contains(.isFailed) {
|
||||
itemDate += " – \(item.presentationData.strings.Monetization_Transaction_Failed)"
|
||||
itemDateColor = item.presentationData.theme.list.itemDestructiveColor
|
||||
}
|
||||
|
||||
var titleComponents: [AnyComponentWithIdentity<Empty>] = []
|
||||
@ -309,7 +316,7 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
text: .plain(NSAttributedString(
|
||||
string: itemDate,
|
||||
font: Font.regular(floor(fontBaseDisplaySize * 14.0 / 17.0)),
|
||||
textColor: item.presentationData.theme.list.itemSecondaryTextColor
|
||||
textColor: itemDateColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)))
|
||||
|
@ -1254,7 +1254,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(participant.peer), text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(participant.peer), title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
}
|
||||
} else {
|
||||
if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) {
|
||||
@ -1362,7 +1362,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
}
|
||||
}))
|
||||
} else if case let .legacyGroup(groupPeer) = groupPeer {
|
||||
@ -1430,7 +1430,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -2262,7 +2262,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
return
|
||||
}
|
||||
let text = strongSelf.presentationData.strings.VoiceChat_PeerJoinedText(event.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: event.peer, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: event.peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
}
|
||||
}))
|
||||
|
||||
@ -2277,7 +2277,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
|
||||
} else {
|
||||
text = strongSelf.presentationData.strings.VoiceChat_DisplayAsSuccess(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
|
||||
}
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: text, action: nil, duration: 3), action: { _ in return false })
|
||||
}))
|
||||
|
||||
self.stateVersionDisposable.set((self.call.stateVersion
|
||||
|
@ -956,10 +956,6 @@ private final class StarsSubscriptionsContextImpl {
|
||||
updatedState.isLoading = false
|
||||
updatedState.canLoadMore = self.nextOffset != nil
|
||||
self.updateState(updatedState)
|
||||
|
||||
if updatedState.canLoadMore {
|
||||
self.loadMore()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
@ -967,6 +963,22 @@ private final class StarsSubscriptionsContextImpl {
|
||||
self._state = state
|
||||
self._statePromise.set(.single(state))
|
||||
}
|
||||
|
||||
func updateSubscription(id: String, cancel: Bool) {
|
||||
var updatedState = self._state
|
||||
if let index = updatedState.subscriptions.firstIndex(where: { $0.id == id }) {
|
||||
let subscription = updatedState.subscriptions[index]
|
||||
var updatedFlags = subscription.flags
|
||||
if cancel {
|
||||
updatedFlags.insert(.isCancelled)
|
||||
} else {
|
||||
updatedFlags.remove(.isCancelled)
|
||||
}
|
||||
let updatedSubscription = StarsContext.State.Subscription(flags: updatedFlags, id: subscription.id, peer: subscription.peer, untilDate: subscription.untilDate, pricing: subscription.pricing)
|
||||
updatedState.subscriptions[index] = updatedSubscription
|
||||
}
|
||||
self.updateState(updatedState)
|
||||
}
|
||||
}
|
||||
|
||||
public final class StarsSubscriptionsContext {
|
||||
@ -1007,6 +1019,12 @@ public final class StarsSubscriptionsContext {
|
||||
return StarsSubscriptionsContextImpl(account: account, starsContext: starsContext)
|
||||
})
|
||||
}
|
||||
|
||||
public func updateSubscription(id: String, cancel: Bool) {
|
||||
self.impl.with {
|
||||
$0.updateSubscription(id: id, cancel: cancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1203,7 +1221,7 @@ func _internal_updateStarsSubscription(account: Account, peerId: EnginePeer.Id,
|
||||
return .complete()
|
||||
}
|
||||
let flags: Int32 = (1 << 0)
|
||||
return account.network.request(Api.functions.payments.changeStarsSubscription(flags: flags, peer: inputPeer, subscriptionId: subscriptionId, canceled: .boolTrue))
|
||||
return account.network.request(Api.functions.payments.changeStarsSubscription(flags: flags, peer: inputPeer, subscriptionId: subscriptionId, canceled: cancel ? .boolTrue : .boolFalse))
|
||||
|> mapError { _ -> UpdateStarsSubsciptionError in
|
||||
return .generic
|
||||
}
|
||||
|
@ -510,6 +510,10 @@ public extension EnginePeer {
|
||||
var isPremium: Bool {
|
||||
return self._asPeer().isPremium
|
||||
}
|
||||
|
||||
var isSubscription: Bool {
|
||||
return self._asPeer().isSubscription
|
||||
}
|
||||
|
||||
var isService: Bool {
|
||||
if case let .user(peer) = self {
|
||||
|
@ -191,6 +191,15 @@ public extension Peer {
|
||||
}
|
||||
}
|
||||
|
||||
var isSubscription: Bool {
|
||||
switch self {
|
||||
case let channel as TelegramChannel:
|
||||
return channel.subscriptionUntilDate != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var isCloseFriend: Bool {
|
||||
switch self {
|
||||
case let user as TelegramUser:
|
||||
|
@ -905,7 +905,7 @@ private let starImage: UIImage? = {
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
|
||||
if let image = UIImage(bundleImageName: "Premium/Stars/StarLarge"), let cgImage = image.cgImage {
|
||||
context.draw(cgImage, in: CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 4.0), byTiling: false)
|
||||
context.draw(cgImage, in: CGRect(origin: .zero, size: size).insetBy(dx: 1.0, dy: 1.0), byTiling: false)
|
||||
}
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
}()
|
||||
|
@ -9232,7 +9232,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
case .fallback:
|
||||
(strongSelf.controller?.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicPhotoSuccess, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
case .custom:
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessPhotoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessPhotoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
|
||||
let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).startStandalone()
|
||||
case .suggest:
|
||||
@ -9469,7 +9469,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
case .fallback:
|
||||
(strongSelf.controller?.parentController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .image(image: image, title: nil, text: strongSelf.presentationData.strings.Privacy_ProfilePhoto_PublicVideoSuccess, round: true, undoText: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
case .custom:
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
|
||||
let _ = (strongSelf.context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, peerId: strongSelf.peerId, fetch: peerInfoProfilePhotos(context: strongSelf.context, peerId: strongSelf.peerId)) |> ignoreValues).startStandalone()
|
||||
case .suggest:
|
||||
|
@ -828,22 +828,35 @@ public final class StarsImageComponent: Component {
|
||||
|
||||
if let _ = component.icon {
|
||||
let smallIconView: UIImageView
|
||||
if let current = self.smallIconView {
|
||||
let smallIconOutlineView: UIImageView
|
||||
if let current = self.smallIconView, let currentOutline = self.smallIconOutlineView {
|
||||
smallIconView = current
|
||||
smallIconOutlineView = currentOutline
|
||||
} else {
|
||||
smallIconOutlineView = UIImageView()
|
||||
containerNode.view.addSubview(smallIconOutlineView)
|
||||
self.smallIconOutlineView = smallIconOutlineView
|
||||
|
||||
smallIconView = UIImageView()
|
||||
containerNode.view.addSubview(smallIconView)
|
||||
self.smallIconView = smallIconView
|
||||
|
||||
smallIconOutlineView.image = UIImage(bundleImageName: "Premium/Stars/TransactionStarOutline")?.withRenderingMode(.alwaysTemplate)
|
||||
smallIconView.image = UIImage(bundleImageName: "Premium/Stars/TransactionStar")
|
||||
}
|
||||
|
||||
smallIconView.image = UIImage(bundleImageName: "Premium/Stars/MockBigStar")
|
||||
smallIconOutlineView.tintColor = component.backgroundColor
|
||||
|
||||
if let icon = smallIconView.image {
|
||||
let smallIconFrame = CGRect(origin: CGPoint(x: imageFrame.maxX - icon.size.width, y: imageFrame.maxY - icon.size.height), size: icon.size)
|
||||
smallIconView.frame = smallIconFrame
|
||||
smallIconOutlineView.frame = smallIconFrame
|
||||
}
|
||||
} else if let smallIconView = self.smallIconView {
|
||||
} else if let smallIconView = self.smallIconView, let smallIconOutlineView = self.smallIconOutlineView {
|
||||
self.smallIconView = nil
|
||||
smallIconView.removeFromSuperview()
|
||||
self.smallIconOutlineView = nil
|
||||
smallIconOutlineView.removeFromSuperview()
|
||||
}
|
||||
|
||||
if let _ = component.action {
|
||||
|
@ -36,6 +36,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
|
||||
let openAppExamples: () -> Void
|
||||
let copyTransactionId: (String) -> Void
|
||||
let updateSubscription: (StarsTransactionScreen.SubscriptionAction) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
@ -45,7 +46,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void,
|
||||
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
|
||||
openAppExamples: @escaping () -> Void,
|
||||
copyTransactionId: @escaping (String) -> Void
|
||||
copyTransactionId: @escaping (String) -> Void,
|
||||
updateSubscription: @escaping (StarsTransactionScreen.SubscriptionAction) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
@ -55,6 +57,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
self.openMedia = openMedia
|
||||
self.openAppExamples = openAppExamples
|
||||
self.copyTransactionId = copyTransactionId
|
||||
self.updateSubscription = updateSubscription
|
||||
}
|
||||
|
||||
static func ==(lhs: StarsTransactionSheetContent, rhs: StarsTransactionSheetContent) -> Bool {
|
||||
@ -96,6 +99,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
peerIds.append(message.id.peerId)
|
||||
case let .subscription(subscription):
|
||||
peerIds.append(subscription.peer.id)
|
||||
case let .importer(_, _, importer, _):
|
||||
peerIds.append(importer.peer.peerId)
|
||||
}
|
||||
|
||||
self.disposable = (context.engine.data.get(
|
||||
@ -138,10 +143,11 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
let table = Child(TableComponent.self)
|
||||
let additional = Child(BalancedTextComponent.self)
|
||||
let status = Child(BalancedTextComponent.self)
|
||||
let button = Child(SolidRoundedButtonComponent.self)
|
||||
|
||||
let refundBackgound = Child(RoundedRectangle.self)
|
||||
let refundText = Child(MultilineTextComponent.self)
|
||||
let transactionStatusBackgound = Child(RoundedRectangle.self)
|
||||
let transactionStatusText = Child(MultilineTextComponent.self)
|
||||
|
||||
let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: [])
|
||||
|
||||
@ -182,8 +188,11 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
let titleText: String
|
||||
let amountText: String
|
||||
var descriptionText: String
|
||||
let additionalText: String
|
||||
let buttonText: String
|
||||
let additionalText = strings.Stars_Transaction_Terms
|
||||
var buttonText: String? = strings.Common_OK
|
||||
var buttonIsDestructive = false
|
||||
var statusText: String?
|
||||
var statusIsDestructive = false
|
||||
|
||||
let count: Int64
|
||||
var countIsGeneric = false
|
||||
@ -196,13 +205,28 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
let transactionPeer: StarsContext.State.Transaction.Peer?
|
||||
var media: [AnyMediaReference] = []
|
||||
var photo: TelegramMediaWebFile?
|
||||
var isRefund = false
|
||||
var transactionStatus: (String, UIColor)? = nil
|
||||
var isGift = false
|
||||
var isSubscription = false
|
||||
var isSubscriber = false
|
||||
var isSubscriptionFee = false
|
||||
var isCancelled = false
|
||||
|
||||
var delayedCloseOnOpenPeer = true
|
||||
switch subject {
|
||||
case let .importer(peer, pricing, importer, usdRate):
|
||||
let usdValue = formatTonUsdValue(pricing.amount, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat)
|
||||
titleText = "Subscription"
|
||||
descriptionText = "appx. \(usdValue) per month"
|
||||
count = pricing.amount
|
||||
countOnTop = true
|
||||
transactionId = nil
|
||||
date = importer.date
|
||||
via = nil
|
||||
messageId = nil
|
||||
toPeer = importer.peer.peer.flatMap(EnginePeer.init)
|
||||
transactionPeer = .peer(peer)
|
||||
isSubscriber = true
|
||||
case let .subscription(subscription):
|
||||
titleText = "Subscription"
|
||||
descriptionText = ""
|
||||
@ -214,6 +238,17 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
toPeer = subscription.peer
|
||||
transactionPeer = .peer(subscription.peer)
|
||||
isSubscription = true
|
||||
|
||||
if subscription.flags.contains(.isCancelled) {
|
||||
statusText = "You have cancelled your subscription"
|
||||
statusIsDestructive = true
|
||||
buttonText = "Renew Subscription"
|
||||
isCancelled = true
|
||||
} else {
|
||||
statusText = "If you cancel now, you can still access your subscription until \(stringForMediumDate(timestamp: subscription.untilDate, strings: strings, dateTimeFormat: dateTimeFormat, withTime: false))"
|
||||
buttonText = "Cancel Subscription"
|
||||
buttonIsDestructive = true
|
||||
}
|
||||
case let .transaction(transaction, parentPeer):
|
||||
if let _ = transaction.subscriptionPeriod {
|
||||
//TODO:localize
|
||||
@ -325,7 +360,12 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
transactionPeer = transaction.peer
|
||||
media = transaction.media.map { AnyMediaReference.starsTransaction(transaction: StarsTransactionReference(peerId: parentPeer.id, id: transaction.id, isRefund: transaction.flags.contains(.isRefund)), media: $0) }
|
||||
photo = transaction.photo
|
||||
isRefund = transaction.flags.contains(.isRefund)
|
||||
|
||||
if transaction.flags.contains(.isRefund) {
|
||||
transactionStatus = (strings.Stars_Transaction_Refund, theme.list.itemDisclosureActions.constructive.fillColor)
|
||||
} else if transaction.flags.contains(.isPending) {
|
||||
transactionStatus = (strings.Monetization_Transaction_Pending, theme.list.itemDisclosureActions.warning.fillColor)
|
||||
}
|
||||
}
|
||||
case let .receipt(receipt):
|
||||
titleText = receipt.invoiceMedia.title
|
||||
@ -386,7 +426,10 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
|
||||
let formattedAmount = presentationStringsFormattedNumber(abs(Int32(count)), dateTimeFormat.groupingSeparator)
|
||||
let countColor: UIColor
|
||||
if countIsGeneric {
|
||||
if isSubscription || isSubscriber {
|
||||
amountText = "\(formattedAmount) / month"
|
||||
countColor = theme.list.itemSecondaryTextColor
|
||||
} else if countIsGeneric {
|
||||
amountText = "\(formattedAmount)"
|
||||
countColor = theme.list.itemPrimaryTextColor
|
||||
} else if count < 0 {
|
||||
@ -396,8 +439,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
amountText = "+ \(formattedAmount)"
|
||||
countColor = theme.list.itemDisclosureActions.constructive.fillColor
|
||||
}
|
||||
additionalText = strings.Stars_Transaction_Terms
|
||||
buttonText = strings.Common_OK
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
@ -429,7 +470,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
} else {
|
||||
imageSubject = .none
|
||||
}
|
||||
if isSubscription || isSubscriptionFee {
|
||||
if isSubscription || isSubscriber || isSubscriptionFee {
|
||||
imageIcon = .star
|
||||
} else {
|
||||
imageIcon = nil
|
||||
@ -450,7 +491,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let amountAttributedText = NSMutableAttributedString(string: amountText, font: Font.semibold(17.0), textColor: countColor)
|
||||
let amountAttributedText = NSMutableAttributedString(string: amountText, font: isSubscription || isSubscriber ? Font.regular(17.0) : Font.semibold(17.0), textColor: countColor)
|
||||
let amount = amount.update(
|
||||
component: BalancedTextComponent(
|
||||
text: .plain(amountAttributedText),
|
||||
@ -500,9 +541,17 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
)
|
||||
))
|
||||
} else if let toPeer {
|
||||
let title: String
|
||||
if isSubscription {
|
||||
title = "Subscription"
|
||||
} else if isSubscriber {
|
||||
title = "Subscriber"
|
||||
} else {
|
||||
title = count < 0 || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From
|
||||
}
|
||||
tableItems.append(.init(
|
||||
id: "to",
|
||||
title: count < 0 || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From,
|
||||
title: title,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
@ -594,9 +643,25 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
))
|
||||
}
|
||||
|
||||
let dateTitle: String
|
||||
if isSubscription {
|
||||
if isCancelled {
|
||||
if date > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) {
|
||||
dateTitle = "Expires"
|
||||
} else {
|
||||
dateTitle = "Expired"
|
||||
}
|
||||
} else {
|
||||
dateTitle = "Renews"
|
||||
}
|
||||
} else if isSubscriber {
|
||||
dateTitle = "Subscribed"
|
||||
} else {
|
||||
dateTitle = strings.Stars_Transaction_Date
|
||||
}
|
||||
tableItems.append(.init(
|
||||
id: "date",
|
||||
title: strings.Stars_Transaction_Date,
|
||||
title: dateTitle,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
@ -615,6 +680,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
let boldTextFont = Font.semibold(15.0)
|
||||
let textColor = theme.actionSheet.secondaryTextColor
|
||||
let linkColor = theme.actionSheet.controlAccentColor
|
||||
let destructiveColor = theme.actionSheet.destructiveActionTextColor
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
@ -623,7 +689,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
text: .markdown(text: additionalText, attributes: markdownAttributes),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1,
|
||||
lineSpacing: 0.2,
|
||||
highlightColor: linkColor.withAlphaComponent(0.2),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
@ -643,28 +709,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let button = button.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: buttonText,
|
||||
theme: SolidRoundedButtonComponent.Theme(theme: theme),
|
||||
font: .bold,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: false,
|
||||
iconName: nil,
|
||||
animationName: nil,
|
||||
iconPosition: .left,
|
||||
isLoading: state.inProgress,
|
||||
action: {
|
||||
component.cancel(true)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
|
||||
context.add(title
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 31.0 + 125.0))
|
||||
)
|
||||
@ -684,8 +729,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
|
||||
}
|
||||
|
||||
let textFont = Font.regular(15.0)
|
||||
let textColor = countOnTop ? theme.list.itemPrimaryTextColor : textColor
|
||||
let textColor = countOnTop && !isSubscriber ? theme.list.itemPrimaryTextColor : textColor
|
||||
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
@ -728,21 +772,21 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
let amountSpacing: CGFloat = 1.0
|
||||
var totalAmountWidth: CGFloat = amount.size.width + amountSpacing + amountStar.size.width
|
||||
var amountOriginX: CGFloat = floor(context.availableSize.width - totalAmountWidth) / 2.0
|
||||
if isRefund {
|
||||
let refundText = refundText.update(
|
||||
if let (statusText, statusColor) = transactionStatus {
|
||||
let refundText = transactionStatusText.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: strings.Stars_Transaction_Refund,
|
||||
string: statusText,
|
||||
font: Font.medium(14.0),
|
||||
textColor: theme.list.itemDisclosureActions.constructive.fillColor
|
||||
textColor: statusColor
|
||||
))
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
let refundBackground = refundBackgound.update(
|
||||
let refundBackground = transactionStatusBackgound.update(
|
||||
component: RoundedRectangle(
|
||||
color: theme.list.itemDisclosureActions.constructive.fillColor.withAlphaComponent(0.1),
|
||||
color: statusColor.withAlphaComponent(0.1),
|
||||
cornerRadius: 6.0
|
||||
),
|
||||
availableSize: CGSize(width: refundText.size.width + 10.0, height: refundText.size.height + 4.0),
|
||||
@ -766,11 +810,22 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
} else {
|
||||
originY += amount.size.height + 20.0
|
||||
}
|
||||
|
||||
let amountLabelOriginX: CGFloat
|
||||
let amountStarOriginX: CGFloat
|
||||
if isSubscription || isSubscriber {
|
||||
amountStarOriginX = amountOriginX + amountStar.size.width / 2.0
|
||||
amountLabelOriginX = amountOriginX + amountStar.size.width + amountSpacing + amount.size.width / 2.0
|
||||
} else {
|
||||
amountLabelOriginX = amountOriginX + amount.size.width / 2.0
|
||||
amountStarOriginX = amountOriginX + amount.size.width + amountSpacing + amountStar.size.width / 2.0
|
||||
}
|
||||
|
||||
context.add(amount
|
||||
.position(CGPoint(x: amountOriginX + amount.size.width / 2.0, y: amountOrigin + amount.size.height / 2.0))
|
||||
.position(CGPoint(x: amountLabelOriginX, y: amountOrigin + amount.size.height / 2.0))
|
||||
)
|
||||
context.add(amountStar
|
||||
.position(CGPoint(x: amountOriginX + amount.size.width + amountSpacing + amountStar.size.width / 2.0, y: amountOrigin + amountStar.size.height / 2.0))
|
||||
.position(CGPoint(x: amountStarOriginX, y: amountOrigin + amountStar.size.height / 2.0 - UIScreenPixel))
|
||||
)
|
||||
|
||||
context.add(table
|
||||
@ -783,16 +838,66 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
)
|
||||
originY += additional.size.height + 23.0
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: button.size)
|
||||
context.add(button
|
||||
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
|
||||
)
|
||||
if let statusText {
|
||||
originY += 7.0
|
||||
let status = status.update(
|
||||
component: BalancedTextComponent(
|
||||
text: .plain(NSAttributedString(string: statusText, font: textFont, textColor: statusIsDestructive ? destructiveColor : textColor)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(status
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + status.size.height / 2.0))
|
||||
)
|
||||
originY += status.size.height + (statusIsDestructive ? 23.0 : 13.0)
|
||||
}
|
||||
|
||||
if let buttonText {
|
||||
let button = button.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: buttonText,
|
||||
theme: buttonIsDestructive ? SolidRoundedButtonComponent.Theme(backgroundColor: .clear, foregroundColor: destructiveColor) : SolidRoundedButtonComponent.Theme(theme: theme),
|
||||
font: buttonIsDestructive ? .regular : .bold,
|
||||
fontSize: 17.0,
|
||||
height: 50.0,
|
||||
cornerRadius: 10.0,
|
||||
gloss: false,
|
||||
iconName: nil,
|
||||
animationName: nil,
|
||||
iconPosition: .left,
|
||||
isLoading: state.inProgress,
|
||||
action: {
|
||||
component.cancel(true)
|
||||
|
||||
if isSubscription {
|
||||
if buttonIsDestructive {
|
||||
component.updateSubscription(.cancel)
|
||||
} else {
|
||||
component.updateSubscription(.renew)
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
transition: context.transition
|
||||
)
|
||||
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: button.size)
|
||||
context.add(button
|
||||
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
|
||||
)
|
||||
originY += button.size.height
|
||||
}
|
||||
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0))
|
||||
)
|
||||
|
||||
let contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + 5.0 + environment.safeInsets.bottom)
|
||||
let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom)
|
||||
|
||||
return contentSize
|
||||
}
|
||||
@ -809,6 +914,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
|
||||
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
|
||||
let openAppExamples: () -> Void
|
||||
let copyTransactionId: (String) -> Void
|
||||
let updateSubscription: (StarsTransactionScreen.SubscriptionAction) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
@ -817,7 +923,8 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void,
|
||||
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
|
||||
openAppExamples: @escaping () -> Void,
|
||||
copyTransactionId: @escaping (String) -> Void
|
||||
copyTransactionId: @escaping (String) -> Void,
|
||||
updateSubscription: @escaping (StarsTransactionScreen.SubscriptionAction) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
@ -826,6 +933,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
|
||||
self.openMedia = openMedia
|
||||
self.openAppExamples = openAppExamples
|
||||
self.copyTransactionId = copyTransactionId
|
||||
self.updateSubscription = updateSubscription
|
||||
}
|
||||
|
||||
static func ==(lhs: StarsTransactionSheetComponent, rhs: StarsTransactionSheetComponent) -> Bool {
|
||||
@ -869,7 +977,8 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
|
||||
openMessage: context.component.openMessage,
|
||||
openMedia: context.component.openMedia,
|
||||
openAppExamples: context.component.openAppExamples,
|
||||
copyTransactionId: context.component.copyTransactionId
|
||||
copyTransactionId: context.component.copyTransactionId,
|
||||
updateSubscription: context.component.updateSubscription
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
@ -936,11 +1045,17 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
|
||||
}
|
||||
|
||||
public class StarsTransactionScreen: ViewControllerComponentContainer {
|
||||
enum SubscriptionAction {
|
||||
case cancel
|
||||
case renew
|
||||
}
|
||||
|
||||
public enum Subject: Equatable {
|
||||
case transaction(StarsContext.State.Transaction, EnginePeer)
|
||||
case receipt(BotPaymentReceipt)
|
||||
case gift(EngineMessage)
|
||||
case subscription(StarsContext.State.Subscription)
|
||||
case importer(EnginePeer, StarsSubscriptionPricing, PeerInvitationImportersState.Importer, Double)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
@ -951,7 +1066,8 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
subject: StarsTransactionScreen.Subject,
|
||||
forceDark: Bool = false
|
||||
forceDark: Bool = false,
|
||||
updateSubscription: @escaping (Bool) -> Void = { _ in }
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
@ -960,6 +1076,8 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
||||
var openMediaImpl: (([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)?
|
||||
var openAppExamplesImpl: (() -> Void)?
|
||||
var copyTransactionIdImpl: ((String) -> Void)?
|
||||
var updateSubscriptionImpl: ((StarsTransactionScreen.SubscriptionAction) -> Void)?
|
||||
|
||||
super.init(
|
||||
context: context,
|
||||
component: StarsTransactionSheetComponent(
|
||||
@ -979,6 +1097,9 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
||||
},
|
||||
copyTransactionId: { transactionId in
|
||||
copyTransactionIdImpl?(transactionId)
|
||||
},
|
||||
updateSubscription: { action in
|
||||
updateSubscriptionImpl?(action)
|
||||
}
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
@ -1090,6 +1211,30 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
|
||||
|
||||
HapticFeedback().tap()
|
||||
}
|
||||
|
||||
updateSubscriptionImpl = { [weak self] action in
|
||||
guard let self, case let .subscription(subscription) = subject, let navigationController = self.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
updateSubscription(action == .cancel)
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
switch action {
|
||||
case .cancel:
|
||||
title = "Subscription cancelled"
|
||||
text = "You will still have access top [\(subscription.peer.compactDisplayTitle)]() until \(stringForMediumDate(timestamp: subscription.untilDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat))."
|
||||
case .renew:
|
||||
title = "Subscription renewed"
|
||||
text = "You renewed your subscription to [\(subscription.peer.compactDisplayTitle)]()."
|
||||
}
|
||||
|
||||
let controller = UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: subscription.peer, title: title, text: text, action: nil, duration: 3.0), elevatedLayout: false, position: .bottom, action: { _ in return true })
|
||||
Queue.mainQueue().after(0.6) {
|
||||
navigationController.presentOverlay(controller: controller)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -268,9 +268,15 @@ final class StarsTransactionsListPanelComponent: Component {
|
||||
}
|
||||
itemLabel = NSAttributedString(string: labelString, font: Font.medium(fontBaseDisplaySize), textColor: labelString.hasPrefix("-") ? environment.theme.list.itemDestructiveColor : environment.theme.list.itemDisclosureActions.constructive.fillColor)
|
||||
|
||||
var itemDateColor = environment.theme.list.itemSecondaryTextColor
|
||||
itemDate = stringForMediumCompactDate(timestamp: item.date, strings: environment.strings, dateTimeFormat: environment.dateTimeFormat)
|
||||
if item.flags.contains(.isRefund) {
|
||||
itemDate += " – \(environment.strings.Stars_Intro_Transaction_Refund)"
|
||||
} else if item.flags.contains(.isPending) {
|
||||
itemDate += " – \(environment.strings.Monetization_Transaction_Pending)"
|
||||
} else if item.flags.contains(.isFailed) {
|
||||
itemDate += " – \(environment.strings.Monetization_Transaction_Failed)"
|
||||
itemDateColor = environment.theme.list.itemDestructiveColor
|
||||
}
|
||||
|
||||
var titleComponents: [AnyComponentWithIdentity<Empty>] = []
|
||||
@ -301,7 +307,7 @@ final class StarsTransactionsListPanelComponent: Component {
|
||||
text: .plain(NSAttributedString(
|
||||
string: itemDate,
|
||||
font: Font.regular(floor(fontBaseDisplaySize * 14.0 / 17.0)),
|
||||
textColor: environment.theme.list.itemSecondaryTextColor
|
||||
textColor: itemDateColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)))
|
||||
|
@ -27,6 +27,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
|
||||
let context: AccountContext
|
||||
let starsContext: StarsContext
|
||||
let subscriptionsContext: StarsSubscriptionsContext
|
||||
let openTransaction: (StarsContext.State.Transaction) -> Void
|
||||
let openSubscription: (StarsContext.State.Subscription) -> Void
|
||||
let buy: () -> Void
|
||||
@ -35,6 +36,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
init(
|
||||
context: AccountContext,
|
||||
starsContext: StarsContext,
|
||||
subscriptionsContext: StarsSubscriptionsContext,
|
||||
openTransaction: @escaping (StarsContext.State.Transaction) -> Void,
|
||||
openSubscription: @escaping (StarsContext.State.Subscription) -> Void,
|
||||
buy: @escaping () -> Void,
|
||||
@ -42,6 +44,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
) {
|
||||
self.context = context
|
||||
self.starsContext = starsContext
|
||||
self.subscriptionsContext = subscriptionsContext
|
||||
self.openTransaction = openTransaction
|
||||
self.openSubscription = openSubscription
|
||||
self.buy = buy
|
||||
@ -122,7 +125,6 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
|
||||
private var previousBalance: Int64?
|
||||
|
||||
private var subscriptionsContext: StarsSubscriptionsContext?
|
||||
private var subscriptionsStateDisposable: Disposable?
|
||||
private var subscriptionsState: StarsSubscriptionsContext.State?
|
||||
|
||||
@ -312,9 +314,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
}
|
||||
})
|
||||
|
||||
let subscriptionsContext = component.context.engine.payments.peerStarsSubscriptionsContext(starsContext: component.starsContext)
|
||||
self.subscriptionsContext = subscriptionsContext
|
||||
self.subscriptionsStateDisposable = (subscriptionsContext.state
|
||||
self.subscriptionsStateDisposable = (component.subscriptionsContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let self else {
|
||||
return
|
||||
@ -614,10 +614,21 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
)))
|
||||
)
|
||||
//TODO:localize
|
||||
let dateText: String
|
||||
let dateValue = stringForDateWithoutYear(date: Date(timeIntervalSince1970: Double(subscription.untilDate)), strings: environment.strings)
|
||||
if subscription.flags.contains(.isCancelled) {
|
||||
if subscription.untilDate > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) {
|
||||
dateText = "expires on \(dateValue)"
|
||||
} else {
|
||||
dateText = "expired on \(dateValue)"
|
||||
}
|
||||
} else {
|
||||
dateText = "renews on \(dateValue)"
|
||||
}
|
||||
titleComponents.append(
|
||||
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "renews on \(stringForDateWithoutYear(date: Date(timeIntervalSince1970: Double(subscription.untilDate)), strings: environment.strings))",
|
||||
string: dateText,
|
||||
font: Font.regular(floor(fontBaseDisplaySize * 15.0 / 17.0)),
|
||||
textColor: environment.theme.list.itemSecondaryTextColor
|
||||
)),
|
||||
@ -626,15 +637,15 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
)
|
||||
|
||||
let labelComponent: AnyComponentWithIdentity<Empty>
|
||||
if "".isEmpty {
|
||||
if subscription.flags.contains(.isCancelled) {
|
||||
labelComponent = AnyComponentWithIdentity(id: "cancelledLabel", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "cancelled", font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)), textColor: environment.theme.list.itemDestructiveColor)))
|
||||
))
|
||||
} else {
|
||||
let itemLabel = NSAttributedString(string: "\(subscription.pricing.amount)", font: Font.medium(fontBaseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor)
|
||||
let itemSublabel = NSAttributedString(string: "per month", font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)), textColor: environment.theme.list.itemSecondaryTextColor)
|
||||
|
||||
labelComponent = AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel, subtext: itemSublabel)))
|
||||
} else {
|
||||
labelComponent = AnyComponentWithIdentity(id: "cancelledLabel", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "cancelled", font: Font.regular(floor(fontBaseDisplaySize * 13.0 / 17.0)), textColor: environment.theme.list.itemDestructiveColor)))
|
||||
))
|
||||
}
|
||||
|
||||
subscriptionsItems.append(AnyComponentWithIdentity(
|
||||
@ -657,6 +668,38 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
)
|
||||
))
|
||||
}
|
||||
if subscriptionsState.canLoadMore {
|
||||
subscriptionsItems.append(AnyComponentWithIdentity(
|
||||
id: "showMore",
|
||||
component: AnyComponent(
|
||||
ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
title: AnyComponent(Text(
|
||||
text: "Show More",
|
||||
font: Font.regular(17.0),
|
||||
color: environment.theme.list.itemAccentColor
|
||||
)),
|
||||
leftIcon: .custom(
|
||||
AnyComponentWithIdentity(
|
||||
id: "icon",
|
||||
component: AnyComponent(Image(
|
||||
image: PresentationResourcesItemList.downArrowImage(environment.theme),
|
||||
size: CGSize(width: 30.0, height: 30.0)
|
||||
))
|
||||
),
|
||||
false
|
||||
),
|
||||
accessory: nil,
|
||||
action: { _ in
|
||||
|
||||
},
|
||||
highlighting: .default,
|
||||
updateIsHighlighted: { view, _ in
|
||||
|
||||
})
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if !subscriptionsItems.isEmpty {
|
||||
@ -837,6 +880,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private let starsContext: StarsContext
|
||||
private let subscriptionsContext: StarsSubscriptionsContext
|
||||
|
||||
private let options = Promise<[StarsTopUpOption]>()
|
||||
|
||||
@ -844,6 +888,8 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
||||
self.context = context
|
||||
self.starsContext = starsContext
|
||||
|
||||
self.subscriptionsContext = context.engine.payments.peerStarsSubscriptionsContext(starsContext: starsContext)
|
||||
|
||||
var buyImpl: (() -> Void)?
|
||||
var giftImpl: (() -> Void)?
|
||||
var openTransactionImpl: ((StarsContext.State.Transaction) -> Void)?
|
||||
@ -851,6 +897,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
||||
super.init(context: context, component: StarsTransactionsScreenComponent(
|
||||
context: context,
|
||||
starsContext: starsContext,
|
||||
subscriptionsContext: self.subscriptionsContext,
|
||||
openTransaction: { transaction in
|
||||
openTransactionImpl?(transaction)
|
||||
},
|
||||
@ -887,7 +934,12 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makeStarsSubscriptionScreen(context: context, subscription: subscription)
|
||||
let controller = context.sharedContext.makeStarsSubscriptionScreen(context: context, subscription: subscription, update: { [weak self] cancel in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.subscriptionsContext.updateSubscription(id: subscription.id, cancel: cancel)
|
||||
})
|
||||
self.push(controller)
|
||||
}
|
||||
|
||||
|
@ -262,7 +262,7 @@ private final class SheetContent: CombinedComponent {
|
||||
var contentSize = CGSize(width: context.availableSize.width, height: 18.0)
|
||||
|
||||
let background = background.update(
|
||||
component: RoundedRectangle(color: theme.list.blocksBackgroundColor, cornerRadius: 8.0),
|
||||
component: RoundedRectangle(color: theme.actionSheet.opaqueItemBackgroundColor, cornerRadius: 8.0),
|
||||
availableSize: CGSize(width: context.availableSize.width, height: 1000.0),
|
||||
transition: .immediate
|
||||
)
|
||||
@ -270,7 +270,6 @@ private final class SheetContent: CombinedComponent {
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: background.size.height / 2.0))
|
||||
)
|
||||
|
||||
var isSubscription = false
|
||||
let subject: StarsImageComponent.Subject
|
||||
if !component.extendedMedia.isEmpty {
|
||||
subject = .extendedMedia(component.extendedMedia)
|
||||
@ -283,13 +282,19 @@ private final class SheetContent: CombinedComponent {
|
||||
} else {
|
||||
subject = .none
|
||||
}
|
||||
|
||||
var isSubscription = false
|
||||
if case .starsChatSubscription = context.component.source {
|
||||
isSubscription = true
|
||||
}
|
||||
let star = star.update(
|
||||
component: StarsImageComponent(
|
||||
context: component.context,
|
||||
subject: subject,
|
||||
theme: theme,
|
||||
diameter: 90.0,
|
||||
backgroundColor: theme.list.blocksBackgroundColor
|
||||
backgroundColor: theme.actionSheet.opaqueItemBackgroundColor,
|
||||
icon: isSubscription ? .star : nil
|
||||
),
|
||||
availableSize: CGSize(width: min(414.0, context.availableSize.width), height: 220.0),
|
||||
transition: context.transition
|
||||
@ -324,10 +329,9 @@ private final class SheetContent: CombinedComponent {
|
||||
contentSize.height += 126.0
|
||||
|
||||
let titleString: String
|
||||
if case .starsChatSubscription = context.component.source {
|
||||
if isSubscription {
|
||||
//TODO:localize
|
||||
titleString = "Subscribe to the Channel"
|
||||
isSubscription = true
|
||||
} else {
|
||||
titleString = strings.Stars_Transfer_Title
|
||||
}
|
||||
@ -588,12 +592,26 @@ private final class SheetContent: CombinedComponent {
|
||||
let info = info.update(
|
||||
component: BalancedTextComponent(
|
||||
text: .markdown(
|
||||
text: "By subscribing you agree to the [Terms of Service]()",
|
||||
text: strings.Stars_Subscription_Terms,
|
||||
attributes: termsMarkdownAttributes
|
||||
),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
lineSpacing: 0.2,
|
||||
highlightColor: linkColor.withAlphaComponent(0.2),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak controller] attributes, _ in
|
||||
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_Subscription_Terms_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMediumOutline.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMediumOutline.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "StarOutline.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMediumOutline.imageset/StarOutline.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/StarMediumOutline.imageset/StarOutline.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Stars/TransactionStar.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Stars/TransactionStar.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "StarTransaction.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/TransactionStar.imageset/StarTransaction.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/TransactionStar.imageset/StarTransaction.pdf
vendored
Normal file
Binary file not shown.
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "StarTransactionOutline.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
@ -3989,7 +3989,7 @@ extension ChatControllerImpl {
|
||||
if let strongSelf = self {
|
||||
HapticFeedback().impact()
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.Conversation_SendMesageAsPremiumInfo, action: strongSelf.presentationData.strings.EmojiInput_PremiumEmojiToast_Action, duration: 3), elevatedLayout: false, action: { [weak self] action in
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, title: nil, text: strongSelf.presentationData.strings.Conversation_SendMesageAsPremiumInfo, action: strongSelf.presentationData.strings.EmojiInput_PremiumEmojiToast_Action, duration: 3), elevatedLayout: false, action: { [weak self] action in
|
||||
guard let strongSelf = self else {
|
||||
return true
|
||||
}
|
||||
|
@ -87,34 +87,35 @@ func chatHistoryEntriesForView(
|
||||
if (associatedData.subject?.isService ?? false) {
|
||||
|
||||
} else {
|
||||
if case let .peer(peerId) = location, case let cachedData = cachedData as? CachedChannelData, let invitedOn = cachedData?.invitedOn {
|
||||
joinMessage = Message(
|
||||
stableId: UInt32.max - 1000,
|
||||
stableVersion: 0,
|
||||
id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 0),
|
||||
globallyUniqueId: nil,
|
||||
groupingKey: nil,
|
||||
groupInfo: nil,
|
||||
threadId: nil,
|
||||
timestamp: invitedOn,
|
||||
flags: [.Incoming],
|
||||
tags: [],
|
||||
globalTags: [],
|
||||
localTags: [],
|
||||
customTags: [],
|
||||
forwardInfo: nil,
|
||||
author: channelPeer,
|
||||
text: "",
|
||||
attributes: [],
|
||||
media: [TelegramMediaAction(action: .joinedByRequest)],
|
||||
peers: SimpleDictionary<PeerId, Peer>(),
|
||||
associatedMessages: SimpleDictionary<MessageId, Message>(),
|
||||
associatedMessageIds: [],
|
||||
associatedMedia: [:],
|
||||
associatedThreadInfo: nil,
|
||||
associatedStories: [:]
|
||||
)
|
||||
} else if let peer = channelPeer as? TelegramChannel, case .broadcast = peer.info, case .member = peer.participationStatus, !peer.flags.contains(.isCreator) {
|
||||
// if case let .peer(peerId) = location, case let cachedData = cachedData as? CachedChannelData, let invitedOn = cachedData?.invitedOn {
|
||||
// joinMessage = Message(
|
||||
// stableId: UInt32.max - 1000,
|
||||
// stableVersion: 0,
|
||||
// id: MessageId(peerId: peerId, namespace: Namespaces.Message.Local, id: 0),
|
||||
// globallyUniqueId: nil,
|
||||
// groupingKey: nil,
|
||||
// groupInfo: nil,
|
||||
// threadId: nil,
|
||||
// timestamp: invitedOn,
|
||||
// flags: [.Incoming],
|
||||
// tags: [],
|
||||
// globalTags: [],
|
||||
// localTags: [],
|
||||
// customTags: [],
|
||||
// forwardInfo: nil,
|
||||
// author: channelPeer,
|
||||
// text: "",
|
||||
// attributes: [],
|
||||
// media: [TelegramMediaAction(action: .joinedByRequest)],
|
||||
// peers: SimpleDictionary<PeerId, Peer>(),
|
||||
// associatedMessages: SimpleDictionary<MessageId, Message>(),
|
||||
// associatedMessageIds: [],
|
||||
// associatedMedia: [:],
|
||||
// associatedThreadInfo: nil,
|
||||
// associatedStories: [:]
|
||||
// )
|
||||
// } else
|
||||
if let peer = channelPeer as? TelegramChannel, case .broadcast = peer.info, case .member = peer.participationStatus, !peer.flags.contains(.isCreator) {
|
||||
joinMessage = Message(
|
||||
stableId: UInt32.max - 1000,
|
||||
stableVersion: 0,
|
||||
|
@ -2750,8 +2750,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return StarsTransactionScreen(context: context, subject: .receipt(receipt))
|
||||
}
|
||||
|
||||
public func makeStarsSubscriptionScreen(context: AccountContext, subscription: StarsContext.State.Subscription) -> ViewController {
|
||||
return StarsTransactionScreen(context: context, subject: .subscription(subscription))
|
||||
public func makeStarsSubscriptionScreen(context: AccountContext, subscription: StarsContext.State.Subscription, update: @escaping (Bool) -> Void) -> ViewController {
|
||||
return StarsTransactionScreen(context: context, subject: .subscription(subscription), updateSubscription: update)
|
||||
}
|
||||
|
||||
public func makeStarsSubscriptionScreen(context: AccountContext, peer: EnginePeer, pricing: StarsSubscriptionPricing, importer: PeerInvitationImportersState.Importer, usdRate: Double) -> ViewController {
|
||||
return StarsTransactionScreen(context: context, subject: .importer(peer, pricing, importer, usdRate))
|
||||
}
|
||||
|
||||
public func makeStarsStatisticsScreen(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) -> ViewController {
|
||||
|
@ -22,7 +22,7 @@ public enum UndoOverlayContent {
|
||||
case chatRemovedFromFolder(chatTitle: String, folderTitle: String)
|
||||
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
|
||||
case setProximityAlert(title: String, text: String, cancelled: Bool)
|
||||
case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, text: String, action: String?, duration: Double)
|
||||
case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, title: String?, text: String, action: String?, duration: Double)
|
||||
case linkCopied(text: String)
|
||||
case banned(text: String)
|
||||
case importedMessage(text: String)
|
||||
|
@ -652,19 +652,21 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 3
|
||||
case let .invitedToVoiceChat(context, peer, text, action, duration):
|
||||
case let .invitedToVoiceChat(context, peer, title, text, action, duration):
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = nil
|
||||
self.animatedStickerNode = nil
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: title ?? "", font: Font.semibold(14.0), textColor: .white)
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
|
||||
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
|
||||
self.textNode.attributedText = attributedText
|
||||
self.avatarNode?.setPeer(context: context, theme: presentationData.theme, peer: peer, overrideImage: nil, emptyColor: presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: true)
|
||||
|
||||
if let action = action {
|
||||
|
Loading…
x
Reference in New Issue
Block a user