From cf28e1b651f0a19dc25b6c4d1591b4e658cd1b73 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 2 Aug 2024 12:34:08 +0200 Subject: [PATCH] Stars subscriptions --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 + .../Sources/InviteLinkListController.swift | 2 +- .../Sources/InviteLinkViewController.swift | 8 +++- .../Sources/StarsTransactionItem.swift | 8 +++- .../Sources/StarsImageComponent.swift | 42 ++++++++++++++++ .../Sources/StarsTransactionScreen.swift | 48 +++++++++++-------- 6 files changed, 87 insertions(+), 23 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 04c4abfc6c..2fdf3ec924 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12674,3 +12674,5 @@ Sorry for the inconvenience."; "MediaPicker.CreateSticker" = "Create a sticker from a photo"; "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."; diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index 1088f93cf0..84f0daa5ef 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -351,7 +351,7 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData, } } if admin == nil { - entries.append(.linksInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) + entries.append(.linksInfo(presentationData.theme, presentationData.strings.InviteLink_CreateNewInfo)) } if let revokedInvites = revokedInvites { diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index 803cb4793b..a5f19c7534 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -798,7 +798,13 @@ public final class InviteLinkViewController: ViewController { if let pricing = invite.pricing { //TODO:localize entries.append(.subscriptionHeader(presentationData.theme, "SUBSCRIPTION FEE")) - entries.append(.subscriptionPricing(presentationData.theme, "⭐️\(pricing.amount) / month x \(state.count)", "You get approximately $\(Float(pricing.amount * Int64(state.count)) * 0.01) monthly")) + var title = "⭐️\(pricing.amount) / month" + 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" + } + entries.append(.subscriptionPricing(presentationData.theme, title, subtitle)) } entries.append(.creatorHeader(presentationData.theme, presentationData.strings.InviteLink_CreatedBy.uppercased())) diff --git a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift index 6a225c18dc..43c3daf13a 100644 --- a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift +++ b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift @@ -231,15 +231,19 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode { var itemDate: String switch item.transaction.peer { case let .peer(peer): - if !item.transaction.media.isEmpty { + if !item.transaction.media.isEmpty { itemTitle = item.presentationData.strings.Stars_Intro_Transaction_MediaPurchase itemSubtitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast) } else if let title = item.transaction.title { itemTitle = title itemSubtitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast) } else { + if let _ = item.transaction.subscriptionPeriod { + itemSubtitle = "Monthly subscription fee" + } else { + itemSubtitle = nil + } itemTitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast) - itemSubtitle = nil } case .appStore: itemTitle = item.presentationData.strings.Stars_Intro_Transaction_AppleTopUp_Title diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift index 6c1e5dc948..aa48532c32 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -297,11 +297,16 @@ public final class StarsImageComponent: Component { } } + public enum Icon { + case star + } + public let context: AccountContext public let subject: Subject public let theme: PresentationTheme public let diameter: CGFloat public let backgroundColor: UIColor + public let icon: Icon? public let action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? public init( @@ -310,6 +315,7 @@ public final class StarsImageComponent: Component { theme: PresentationTheme, diameter: CGFloat, backgroundColor: UIColor, + icon: Icon? = nil, action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? = nil ) { self.context = context @@ -317,6 +323,7 @@ public final class StarsImageComponent: Component { self.theme = theme self.diameter = diameter self.backgroundColor = backgroundColor + self.icon = icon self.action = action } @@ -336,6 +343,9 @@ public final class StarsImageComponent: Component { if lhs.backgroundColor != rhs.backgroundColor { return false } + if lhs.icon != rhs.icon { + return false + } return true } @@ -353,6 +363,8 @@ public final class StarsImageComponent: Component { private var avatarNode: ImageNode? private var iconBackgroundView: UIImageView? private var iconView: UIImageView? + private var smallIconOutlineView: UIImageView? + private var smallIconView: UIImageView? private var dustNode: MediaDustNode? private var button: UIControl? @@ -814,6 +826,36 @@ public final class StarsImageComponent: Component { animationNode.updateLayout(size: animationFrame.size) } + if let _ = component.icon { + let smallIconView: UIImageView + let smallIconOutlineView: UIImageView + if let current = self.smallIconView, let currentOutline = self.smallIconOutlineView { + smallIconView = current + smallIconOutlineView = currentOutline + } else { + smallIconOutlineView = UIImageView() + containerNode.view.addSubview(smallIconOutlineView) + + smallIconView = UIImageView() + containerNode.view.addSubview(smallIconView) + } + + smallIconView.image = UIImage(bundleImageName: "Premium/Stars/BalanceStar") + if smallIconOutlineView.image == nil { + smallIconOutlineView.image = generateTintedImage(image: smallIconView.image, color: .white)?.withRenderingMode(.alwaysTemplate) + } + smallIconOutlineView.tintColor = component.backgroundColor + + if let icon = smallIconView.image { + let smallIconFrame = CGRect(origin: CGPoint(x: imageFrame.maxX - icon.size.width - 5.0, y: imageFrame.maxY - icon.size.height - 5.0), size: icon.size) + smallIconView.frame = smallIconFrame + smallIconOutlineView.frame = smallIconFrame.insetBy(dx: -3.0 + UIScreenPixel, dy: -3.0 + UIScreenPixel) + } + } else if let smallIconView = self.smallIconView { + self.smallIconView = nil + smallIconView.removeFromSuperview() + } + if let _ = component.action { if self.button == nil { let button = UIControl(frame: imageFrame) diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index 9702ce0582..1119a23904 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -97,6 +97,8 @@ private final class StarsTransactionSheetContent: CombinedComponent { peerIds.append(receipt.botPaymentId) case let .gift(message): peerIds.append(message.id.peerId) + case let .subscription(subscription): + peerIds.append(subscription.peer.id) } self.disposable = (context.engine.data.get( @@ -195,17 +197,30 @@ private final class StarsTransactionSheetContent: CombinedComponent { let messageId: EngineMessage.Id? let toPeer: EnginePeer? let transactionPeer: StarsContext.State.Transaction.Peer? - let media: [AnyMediaReference] - let photo: TelegramMediaWebFile? - let isRefund: Bool - let isGift: Bool + var media: [AnyMediaReference] = [] + var photo: TelegramMediaWebFile? + var isRefund = false + var isGift = false + var isSubscription = false + var isSubscriptionFee = false var delayedCloseOnOpenPeer = true switch subject { + case let .subscription(subscription): + titleText = "Subscription" + descriptionText = "" + count = subscription.pricing.amount + transactionId = nil + date = subscription.untilDate + via = nil + messageId = nil + toPeer = subscription.peer + transactionPeer = .peer(subscription.peer) + isSubscription = true case let .transaction(transaction, parentPeer): if let _ = transaction.subscriptionPeriod { //TODO:localize - titleText = "Monthly Subscription Fee" + titleText = "Monthly subscription fee" descriptionText = "" count = transaction.count countOnTop = false @@ -219,10 +234,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { toPeer = nil } transactionPeer = transaction.peer - media = [] - photo = nil - isRefund = false - isGift = false + isSubscriptionFee = true } else if transaction.flags.contains(.isGift) { titleText = strings.Stars_Gift_Received_Title descriptionText = strings.Stars_Gift_Received_Text @@ -238,9 +250,6 @@ private final class StarsTransactionSheetContent: CombinedComponent { toPeer = nil } transactionPeer = transaction.peer - media = [] - photo = nil - isRefund = false isGift = true } else { switch transaction.peer { @@ -319,7 +328,6 @@ 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 - isGift = false isRefund = transaction.flags.contains(.isRefund) } case let .receipt(receipt): @@ -336,10 +344,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { toPeer = nil } transactionPeer = nil - media = [] photo = receipt.invoiceMedia.photo - isRefund = false - isGift = false delayedCloseOnOpenPeer = false case let .gift(message): let incoming = message.flags.contains(.Incoming) @@ -365,9 +370,6 @@ private final class StarsTransactionSheetContent: CombinedComponent { toPeer = state.peerMap[message.id.peerId] } transactionPeer = nil - media = [] - photo = nil - isRefund = false isGift = true delayedCloseOnOpenPeer = false } @@ -416,6 +418,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { ) let imageSubject: StarsImageComponent.Subject + let imageIcon: StarsImageComponent.Icon? if isGift { imageSubject = .gift(count) } else if !media.isEmpty { @@ -429,6 +432,11 @@ private final class StarsTransactionSheetContent: CombinedComponent { } else { imageSubject = .none } + if isSubscription || isSubscriptionFee { + imageIcon = .star + } else { + imageIcon = nil + } let star = star.update( component: StarsImageComponent( context: component.context, @@ -436,6 +444,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { theme: theme, diameter: 90.0, backgroundColor: theme.actionSheet.opaqueItemBackgroundColor, + icon: imageIcon, action: !media.isEmpty ? { transitionNode, addToTransitionSurface in component.openMedia(media.map { $0.media }, transitionNode, addToTransitionSurface) } : nil @@ -939,6 +948,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { case transaction(StarsContext.State.Transaction, EnginePeer) case receipt(BotPaymentReceipt) case gift(EngineMessage) + case subscription(StarsContext.State.Subscription) } private let context: AccountContext