Stars subscriptions

This commit is contained in:
Ilya Laktyushin 2024-08-02 12:34:08 +02:00
parent 5145337d87
commit cf28e1b651
6 changed files with 87 additions and 23 deletions

View File

@ -12674,3 +12674,5 @@ Sorry for the inconvenience.";
"MediaPicker.CreateSticker" = "Create a sticker from a photo"; "MediaPicker.CreateSticker" = "Create a sticker from a photo";
"Stickers.CreateSticker" = "Create\nSticker"; "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.";

View File

@ -351,7 +351,7 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData,
} }
} }
if admin == nil { 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 { if let revokedInvites = revokedInvites {

View File

@ -798,7 +798,13 @@ public final class InviteLinkViewController: ViewController {
if let pricing = invite.pricing { if let pricing = invite.pricing {
//TODO:localize //TODO:localize
entries.append(.subscriptionHeader(presentationData.theme, "SUBSCRIPTION FEE")) 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())) entries.append(.creatorHeader(presentationData.theme, presentationData.strings.InviteLink_CreatedBy.uppercased()))

View File

@ -238,9 +238,13 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
itemTitle = title itemTitle = title
itemSubtitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast) itemSubtitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast)
} else { } else {
itemTitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast) if let _ = item.transaction.subscriptionPeriod {
itemSubtitle = "Monthly subscription fee"
} else {
itemSubtitle = nil itemSubtitle = nil
} }
itemTitle = peer.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast)
}
case .appStore: case .appStore:
itemTitle = item.presentationData.strings.Stars_Intro_Transaction_AppleTopUp_Title itemTitle = item.presentationData.strings.Stars_Intro_Transaction_AppleTopUp_Title
itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_AppleTopUp_Subtitle itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_AppleTopUp_Subtitle

View File

@ -297,11 +297,16 @@ public final class StarsImageComponent: Component {
} }
} }
public enum Icon {
case star
}
public let context: AccountContext public let context: AccountContext
public let subject: Subject public let subject: Subject
public let theme: PresentationTheme public let theme: PresentationTheme
public let diameter: CGFloat public let diameter: CGFloat
public let backgroundColor: UIColor public let backgroundColor: UIColor
public let icon: Icon?
public let action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? public let action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)?
public init( public init(
@ -310,6 +315,7 @@ public final class StarsImageComponent: Component {
theme: PresentationTheme, theme: PresentationTheme,
diameter: CGFloat, diameter: CGFloat,
backgroundColor: UIColor, backgroundColor: UIColor,
icon: Icon? = nil,
action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? = nil action: ((@escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)? = nil
) { ) {
self.context = context self.context = context
@ -317,6 +323,7 @@ public final class StarsImageComponent: Component {
self.theme = theme self.theme = theme
self.diameter = diameter self.diameter = diameter
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.icon = icon
self.action = action self.action = action
} }
@ -336,6 +343,9 @@ public final class StarsImageComponent: Component {
if lhs.backgroundColor != rhs.backgroundColor { if lhs.backgroundColor != rhs.backgroundColor {
return false return false
} }
if lhs.icon != rhs.icon {
return false
}
return true return true
} }
@ -353,6 +363,8 @@ public final class StarsImageComponent: Component {
private var avatarNode: ImageNode? private var avatarNode: ImageNode?
private var iconBackgroundView: UIImageView? private var iconBackgroundView: UIImageView?
private var iconView: UIImageView? private var iconView: UIImageView?
private var smallIconOutlineView: UIImageView?
private var smallIconView: UIImageView?
private var dustNode: MediaDustNode? private var dustNode: MediaDustNode?
private var button: UIControl? private var button: UIControl?
@ -814,6 +826,36 @@ public final class StarsImageComponent: Component {
animationNode.updateLayout(size: animationFrame.size) 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 let _ = component.action {
if self.button == nil { if self.button == nil {
let button = UIControl(frame: imageFrame) let button = UIControl(frame: imageFrame)

View File

@ -97,6 +97,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
peerIds.append(receipt.botPaymentId) peerIds.append(receipt.botPaymentId)
case let .gift(message): case let .gift(message):
peerIds.append(message.id.peerId) peerIds.append(message.id.peerId)
case let .subscription(subscription):
peerIds.append(subscription.peer.id)
} }
self.disposable = (context.engine.data.get( self.disposable = (context.engine.data.get(
@ -195,17 +197,30 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let messageId: EngineMessage.Id? let messageId: EngineMessage.Id?
let toPeer: EnginePeer? let toPeer: EnginePeer?
let transactionPeer: StarsContext.State.Transaction.Peer? let transactionPeer: StarsContext.State.Transaction.Peer?
let media: [AnyMediaReference] var media: [AnyMediaReference] = []
let photo: TelegramMediaWebFile? var photo: TelegramMediaWebFile?
let isRefund: Bool var isRefund = false
let isGift: Bool var isGift = false
var isSubscription = false
var isSubscriptionFee = false
var delayedCloseOnOpenPeer = true var delayedCloseOnOpenPeer = true
switch subject { 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): case let .transaction(transaction, parentPeer):
if let _ = transaction.subscriptionPeriod { if let _ = transaction.subscriptionPeriod {
//TODO:localize //TODO:localize
titleText = "Monthly Subscription Fee" titleText = "Monthly subscription fee"
descriptionText = "" descriptionText = ""
count = transaction.count count = transaction.count
countOnTop = false countOnTop = false
@ -219,10 +234,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
toPeer = nil toPeer = nil
} }
transactionPeer = transaction.peer transactionPeer = transaction.peer
media = [] isSubscriptionFee = true
photo = nil
isRefund = false
isGift = false
} else if transaction.flags.contains(.isGift) { } else if transaction.flags.contains(.isGift) {
titleText = strings.Stars_Gift_Received_Title titleText = strings.Stars_Gift_Received_Title
descriptionText = strings.Stars_Gift_Received_Text descriptionText = strings.Stars_Gift_Received_Text
@ -238,9 +250,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
toPeer = nil toPeer = nil
} }
transactionPeer = transaction.peer transactionPeer = transaction.peer
media = []
photo = nil
isRefund = false
isGift = true isGift = true
} else { } else {
switch transaction.peer { switch transaction.peer {
@ -319,7 +328,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
transactionPeer = transaction.peer transactionPeer = transaction.peer
media = transaction.media.map { AnyMediaReference.starsTransaction(transaction: StarsTransactionReference(peerId: parentPeer.id, id: transaction.id, isRefund: transaction.flags.contains(.isRefund)), media: $0) } media = transaction.media.map { AnyMediaReference.starsTransaction(transaction: StarsTransactionReference(peerId: parentPeer.id, id: transaction.id, isRefund: transaction.flags.contains(.isRefund)), media: $0) }
photo = transaction.photo photo = transaction.photo
isGift = false
isRefund = transaction.flags.contains(.isRefund) isRefund = transaction.flags.contains(.isRefund)
} }
case let .receipt(receipt): case let .receipt(receipt):
@ -336,10 +344,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
toPeer = nil toPeer = nil
} }
transactionPeer = nil transactionPeer = nil
media = []
photo = receipt.invoiceMedia.photo photo = receipt.invoiceMedia.photo
isRefund = false
isGift = false
delayedCloseOnOpenPeer = false delayedCloseOnOpenPeer = false
case let .gift(message): case let .gift(message):
let incoming = message.flags.contains(.Incoming) let incoming = message.flags.contains(.Incoming)
@ -365,9 +370,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
toPeer = state.peerMap[message.id.peerId] toPeer = state.peerMap[message.id.peerId]
} }
transactionPeer = nil transactionPeer = nil
media = []
photo = nil
isRefund = false
isGift = true isGift = true
delayedCloseOnOpenPeer = false delayedCloseOnOpenPeer = false
} }
@ -416,6 +418,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
) )
let imageSubject: StarsImageComponent.Subject let imageSubject: StarsImageComponent.Subject
let imageIcon: StarsImageComponent.Icon?
if isGift { if isGift {
imageSubject = .gift(count) imageSubject = .gift(count)
} else if !media.isEmpty { } else if !media.isEmpty {
@ -429,6 +432,11 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else { } else {
imageSubject = .none imageSubject = .none
} }
if isSubscription || isSubscriptionFee {
imageIcon = .star
} else {
imageIcon = nil
}
let star = star.update( let star = star.update(
component: StarsImageComponent( component: StarsImageComponent(
context: component.context, context: component.context,
@ -436,6 +444,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
theme: theme, theme: theme,
diameter: 90.0, diameter: 90.0,
backgroundColor: theme.actionSheet.opaqueItemBackgroundColor, backgroundColor: theme.actionSheet.opaqueItemBackgroundColor,
icon: imageIcon,
action: !media.isEmpty ? { transitionNode, addToTransitionSurface in action: !media.isEmpty ? { transitionNode, addToTransitionSurface in
component.openMedia(media.map { $0.media }, transitionNode, addToTransitionSurface) component.openMedia(media.map { $0.media }, transitionNode, addToTransitionSurface)
} : nil } : nil
@ -939,6 +948,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
case transaction(StarsContext.State.Transaction, EnginePeer) case transaction(StarsContext.State.Transaction, EnginePeer)
case receipt(BotPaymentReceipt) case receipt(BotPaymentReceipt)
case gift(EngineMessage) case gift(EngineMessage)
case subscription(StarsContext.State.Subscription)
} }
private let context: AccountContext private let context: AccountContext