diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8608db5544..6b1c878a99 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12344,6 +12344,7 @@ Sorry for the inconvenience."; "Stars.Transfer.Purchased.Stars_any" = "%@ Stars"; "Stars.Transfer.UnlockedText" = "You unlocked media for **%1$@**."; "Stars.Transfer.UnlockInfo" = "Do you want to unlock %1$@ in **%2$@** for **%3$@**?"; +"Stars.Transfer.UnlockBotInfo" = "Do you want to unlock %1$@ from **%2$@** for **%3$@**?"; "Stars.Transfer.Balance" = "Balance"; @@ -12695,6 +12696,9 @@ Sorry for the inconvenience."; "Stars.Transaction.Subscription.Cancelled" = "You have cancelled your subscription."; "Stars.Transaction.Subscription.Renew" = "Renew Subscription"; "Stars.Transaction.Subscription.Cancel" = "Cancel Subscription"; +"Stars.Transaction.Subscription.JoinChannel" = "Join Channel"; +"Stars.Transaction.Subscription.JoinAgainChannel" = "Join Channel"; +"Stars.Transaction.Subscription.LeftChannel" = "You left channel but you can still get back until %@"; "Stars.Transaction.Subscription.PerMonth" = "%@ / month"; "Stars.Transaction.Subscription.PerMonthUsd" = "appx. %@ per month"; "Stars.Transaction.Subscription.Subscription" = "Subscription"; @@ -12721,3 +12725,14 @@ Sorry for the inconvenience."; "Stars.Intro.Transaction.SubscriptionFee.Title" = "Monthly Subscription Fee"; "Stars.Intro.Transaction.Reaction.Title" = "Star Reaction"; + +"Stars.Purchase.GenericPurchasePurpose" = "Buy Stars to unlock content and services on Telegram."; +"Stars.Purchase.PurchasePurpose.subs" = "Buy Stars to keep all your subscriptions."; + +"Stars.Transfer.Subscribe.Channel.Title" = "Subscribe"; +"Stars.Transfer.SubscribeInfo" = "Do you want to subscribe to **%1$@** for **%2$@** per month?"; +"Stars.Transfer.Subscribe" = "Subscribe"; +"Stars.Transfer.Subscribe.Successful.Title" = "Subscription successful!"; +"Stars.Transfer.Subscribe.Successful.Text" = "%1$@ transferred to %2$@."; + +"Gallery.Ad" = "Ad"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 2f4efe6f2f..a3afd814b0 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -305,7 +305,7 @@ public enum ResolvedUrl { case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?) case invoice(slug: String, invoice: TelegramMediaInvoice?) case premiumOffer(reference: String?) - case starsTopup(amount: Int64?) + case starsTopup(amount: Int64, purpose: String?) case chatFolder(slug: String) case story(peerId: PeerId, id: Int32) case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?) @@ -917,7 +917,7 @@ public protocol SharedAccountContext: AnyObject { func makeProxySettingsController(context: AccountContext) -> ViewController func makeLocalizationListController(context: AccountContext) -> ViewController func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController - func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController + func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController func makePrivacyAndSecurityController(context: AccountContext) -> ViewController func makeBioPrivacyController(context: AccountContext, settings: Promise, present: @escaping (ViewController) -> Void) func makeBirthdayPrivacyController(context: AccountContext, settings: Promise, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index 183c5aeea7..b395017057 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -123,7 +123,8 @@ public enum BoostSubject: Equatable { } public enum StarsPurchasePurpose: Equatable { - case generic(requiredStars: Int64?) + case generic + case topUp(requiredStars: Int64, purpose: String?) case transfer(peerId: EnginePeer.Id, requiredStars: Int64) case subscription(peerId: EnginePeer.Id, requiredStars: Int64, renew: Bool) case gift(peerId: EnginePeer.Id) diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 5e9bfedecc..236db5f6a3 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -256,7 +256,13 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati } } - if message.containsSecretMedia { + if let adAttribute = message.adAttribute, adAttribute.hasContentMedia { + let gallery = GalleryController(context: context, source: .standaloneMessage(message, mediaIndex), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: nil, playbackRate: 1.0, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in + navigationController?.replaceTopController(controller, animated: false, ready: ready) + }, baseNavigationController: navigationController, actionInteraction: actionInteraction) + gallery.temporaryDoNotWaitForReady = autoplayingVideo + return .gallery(.single(gallery)) + } else if message.containsSecretMedia { let gallery = SecretMediaPreviewController(context: context, messageId: message.id) return .secretGallery(gallery) } else { diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index c4bbe07918..8a980e6a89 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -770,10 +770,18 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } } - func setup(origin: GalleryItemOriginData?, caption: NSAttributedString) { - let titleText = origin?.title + func setup(origin: GalleryItemOriginData?, caption: NSAttributedString, isAd: Bool = false) { + var titleText = origin?.title let dateText = origin?.timestamp.flatMap { humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: $0).string } + let caption = caption.mutableCopy() as! NSMutableAttributedString + if isAd { + if let titleText, !titleText.isEmpty { + caption.insert(NSAttributedString(string: titleText + "\n", font: Font.semibold(17.0), textColor: .white), at: 0) + } + titleText = nil + } + if self.currentMessageText != caption || self.currentAuthorNameText != titleText || self.currentDateText != dateText { self.currentMessageText = caption self.currentAuthorNameText = titleText @@ -820,9 +828,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll if Namespaces.Message.allNonRegular.contains(message.id.namespace) || message.timestamp == 0 { displayInfo = false } + if let _ = message.adAttribute { + displayInfo = false + } var canDelete: Bool - var canShare = !message.containsSecretMedia && !Namespaces.Message.allNonRegular.contains(message.id.namespace) + var canShare = !message.containsSecretMedia && !Namespaces.Message.allNonRegular.contains(message.id.namespace) && message.adAttribute == nil var canFullscreen = false @@ -922,13 +933,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } var dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: message.timestamp).string - if !displayInfo { - authorNameText = "" - dateText = "" - canEdit = false - } - - var messageText = NSAttributedString(string: "") + + var messageText = NSMutableAttributedString(string: "") var hasCaption = false for media in message.media { if media is TelegramMediaPaidContent { @@ -991,7 +997,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll codeHighlightState.disposable.dispose() } - messageText = galleryCaptionStringWithAppliedEntities(context: self.context, text: text, entities: entities, message: message, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight) + messageText = galleryCaptionStringWithAppliedEntities(context: self.context, text: text, entities: entities, message: message, cachedMessageSyntaxHighlight: cachedMessageSyntaxHighlight).mutableCopy() as! NSMutableAttributedString + messageText.insert(NSAttributedString(string: (authorNameText ?? "") + "\n", font: Font.semibold(17.0), textColor: .white), at: 0) + } + + if !displayInfo { + authorNameText = "" + dateText = "" + canEdit = false } if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canFullscreen != !self.fullscreenButton.isHidden || canShare != !self.actionButton.isHidden || canEdit != !self.editButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText { diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index edfca6bc77..b1bdf5aba1 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -168,7 +168,9 @@ class ChatImageGalleryItem: GalleryItem { } } - if let location = self.location { + if let _ = message.adAttribute { + node._title.set(.single(self.presentationData.strings.Gallery_Ad)) + } else if let location = self.location { node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").string)) } diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 7c82a528e0..f9136ac677 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1528,15 +1528,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { self.playbackRatePromise.set(self.playbackRate ?? 1.0) + var isAd = false if let contentInfo = item.contentInfo { switch contentInfo { case let .message(message, _): + isAd = message.adAttribute != nil self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected) case let .webPage(webPage, media, _): self.footerContentNode.setWebPage(webPage, media: media) } } - self.footerContentNode.setup(origin: item.originData, caption: item.caption) + self.footerContentNode.setup(origin: item.originData, caption: item.caption, isAd: isAd) } override func controlsVisibilityUpdated(isVisible: Bool) { diff --git a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift index f1b96e8970..9a06e280ef 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkEditController.swift @@ -857,18 +857,20 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio private struct StarsSubscriptionConfiguration { static var defaultValue: StarsSubscriptionConfiguration { - return StarsSubscriptionConfiguration(maxFee: 2500) + return StarsSubscriptionConfiguration(maxFee: 2500, usdSellRate: 2000) } let maxFee: Int64? + let usdSellRate: Int64? - fileprivate init(maxFee: Int64?) { + fileprivate init(maxFee: Int64?, usdSellRate: Int64?) { self.maxFee = maxFee + self.usdSellRate = usdSellRate } public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration { - if let data = appConfiguration.data, let value = data["stars_subscription_amount_max"] as? Double { - return StarsSubscriptionConfiguration(maxFee: Int64(value)) + if let data = appConfiguration.data, let value = data["stars_subscription_amount_max"] as? Double, let usdRate = data["stars_usd_sell_rate_x1000"] as? Double { + return StarsSubscriptionConfiguration(maxFee: Int64(value), usdSellRate: Int64(usdRate)) } else { return .defaultValue } diff --git a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift index e305a37971..3cb8afafa5 100644 --- a/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/ItemListInviteLinkItem.swift @@ -182,6 +182,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { private var absoluteLocation: (CGRect, CGSize)? private var currentColor: ItemBackgroundColor? + private var currentIsPaid: Bool? private var layoutParams: (ItemListInviteLinkItem, ListViewItemLayoutParams, ItemListNeighbors, Bool, Bool)? public var tag: ItemListItemTag? @@ -548,8 +549,12 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode { strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor strongSelf.backgroundNode.backgroundColor = itemBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor - - if let _ = item.invite?.pricing { + } + + let isPaid = item.invite?.pricing != nil + if updatedTheme != nil || strongSelf.currentIsPaid != isPaid { + strongSelf.currentIsPaid = isPaid + if isPaid { strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/SubscriptionLink"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) } else { strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/InviteLink"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index d4e18d0bbe..51b612227f 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -1358,6 +1358,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo let _ = statusApply() 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)) + } else { + let _ = labelApply(nil) } strongSelf.labelNode.textNode.isHidden = labelAttributedString == nil diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index c30f940220..63a08160d5 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -709,7 +709,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation } if case .legacyGroup = peer { } else { - pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: nil)) + pushControllerImpl?(context.sharedContext.makeChatRecentActionsController(context: context, peer: peer._asPeer(), adminPeerId: nil, starsState: nil)) } }) }) diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index 0bff3fe232..324a0a5eaa 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -890,7 +890,7 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat let _ = (context.account.postbox.loadedPeerWithId(peerId) |> take(1) |> deliverOnMainQueue).start(next: { peer in - let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: participantPeerId) + let controller = context.sharedContext.makeChatRecentActionsController(context: context, peer: peer, adminPeerId: participantPeerId, starsState: nil) navigationController.pushViewController(controller) }) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index e3a99a4bd6..b1a774dc68 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -881,7 +881,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) } dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) } dict[-425595208] = { return Api.SmsJob.parse_smsJob($0) } - dict[-1108478618] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } + dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[1577421297] = { return Api.StarsGiftOption.parse_starsGiftOption($0) } dict[2033461574] = { return Api.StarsRevenueStatus.parse_starsRevenueStatus($0) } diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 054dfffbbb..83c393bc91 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -441,14 +441,14 @@ public extension Api { } } public extension Api { - enum SponsoredMessage: TypeConstructorDescription { - case sponsoredMessage(flags: Int32, randomId: Buffer, url: String, title: String, message: String, entities: [Api.MessageEntity]?, photo: Api.Photo?, color: Api.PeerColor?, buttonText: String, sponsorInfo: String?, additionalInfo: String?) + indirect enum SponsoredMessage: TypeConstructorDescription { + case sponsoredMessage(flags: Int32, randomId: Buffer, url: String, title: String, message: String, entities: [Api.MessageEntity]?, photo: Api.Photo?, media: Api.MessageMedia?, color: Api.PeerColor?, buttonText: String, sponsorInfo: String?, additionalInfo: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let color, let buttonText, let sponsorInfo, let additionalInfo): + case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let media, let color, let buttonText, let sponsorInfo, let additionalInfo): if boxed { - buffer.appendInt32(-1108478618) + buffer.appendInt32(1301522832) } serializeInt32(flags, buffer: buffer, boxed: false) serializeBytes(randomId, buffer: buffer, boxed: false) @@ -461,6 +461,7 @@ public extension Api { item.serialize(buffer, true) }} if Int(flags) & Int(1 << 6) != 0 {photo!.serialize(buffer, true)} + if Int(flags) & Int(1 << 14) != 0 {media!.serialize(buffer, true)} if Int(flags) & Int(1 << 13) != 0 {color!.serialize(buffer, true)} serializeString(buttonText, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 7) != 0 {serializeString(sponsorInfo!, buffer: buffer, boxed: false)} @@ -471,8 +472,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let color, let buttonText, let sponsorInfo, let additionalInfo): - return ("sponsoredMessage", [("flags", flags as Any), ("randomId", randomId as Any), ("url", url as Any), ("title", title as Any), ("message", message as Any), ("entities", entities as Any), ("photo", photo as Any), ("color", color as Any), ("buttonText", buttonText as Any), ("sponsorInfo", sponsorInfo as Any), ("additionalInfo", additionalInfo as Any)]) + case .sponsoredMessage(let flags, let randomId, let url, let title, let message, let entities, let photo, let media, let color, let buttonText, let sponsorInfo, let additionalInfo): + return ("sponsoredMessage", [("flags", flags as Any), ("randomId", randomId as Any), ("url", url as Any), ("title", title as Any), ("message", message as Any), ("entities", entities as Any), ("photo", photo as Any), ("media", media as Any), ("color", color as Any), ("buttonText", buttonText as Any), ("sponsorInfo", sponsorInfo as Any), ("additionalInfo", additionalInfo as Any)]) } } @@ -495,16 +496,20 @@ public extension Api { if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { _7 = Api.parse(reader, signature: signature) as? Api.Photo } } - var _8: Api.PeerColor? - if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.PeerColor + var _8: Api.MessageMedia? + if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.MessageMedia + } } + var _9: Api.PeerColor? + if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PeerColor } } - var _9: String? - _9 = parseString(reader) var _10: String? - if Int(_1!) & Int(1 << 7) != 0 {_10 = parseString(reader) } + _10 = parseString(reader) var _11: String? - if Int(_1!) & Int(1 << 8) != 0 {_11 = parseString(reader) } + if Int(_1!) & Int(1 << 7) != 0 {_11 = parseString(reader) } + var _12: String? + if Int(_1!) & Int(1 << 8) != 0 {_12 = parseString(reader) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -512,12 +517,13 @@ public extension Api { let _c5 = _5 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil - let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil - let _c9 = _9 != nil - let _c10 = (Int(_1!) & Int(1 << 7) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 8) == 0) || _11 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { - return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, color: _8, buttonText: _9!, sponsorInfo: _10, additionalInfo: _11) + let _c8 = (Int(_1!) & Int(1 << 14) == 0) || _8 != nil + let _c9 = (Int(_1!) & Int(1 << 13) == 0) || _9 != nil + let _c10 = _10 != nil + let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil + let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 { + return Api.SponsoredMessage.sponsoredMessage(flags: _1!, randomId: _2!, url: _3!, title: _4!, message: _5!, entities: _6, photo: _7, media: _8, color: _9, buttonText: _10!, sponsorInfo: _11, additionalInfo: _12) } else { return nil diff --git a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift index 3f57b53351..ad5dae0a83 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/AdMessageAttribute.swift @@ -14,8 +14,9 @@ public final class AdMessageAttribute: MessageAttribute { public let sponsorInfo: String? public let additionalInfo: String? public let canReport: Bool + public let hasContentMedia: Bool - public init(opaqueId: Data, messageType: MessageType, url: String, buttonText: String, sponsorInfo: String?, additionalInfo: String?, canReport: Bool) { + public init(opaqueId: Data, messageType: MessageType, url: String, buttonText: String, sponsorInfo: String?, additionalInfo: String?, canReport: Bool, hasContentMedia: Bool) { self.opaqueId = opaqueId self.messageType = messageType self.url = url @@ -23,6 +24,7 @@ public final class AdMessageAttribute: MessageAttribute { self.sponsorInfo = sponsorInfo self.additionalInfo = additionalInfo self.canReport = canReport + self.hasContentMedia = hasContentMedia } public init(decoder: PostboxDecoder) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index f42b4dd4fb..c558333abc 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -12,6 +12,7 @@ private class AdMessagesHistoryContextImpl { case text case textEntities case media + case contentMedia case color case backgroundEmojiId case url @@ -32,6 +33,7 @@ private class AdMessagesHistoryContextImpl { public let text: String public let textEntities: [MessageTextEntity] public let media: [Media] + public let contentMedia: [Media] public let color: PeerNameColor? public let backgroundEmojiId: Int64? public let url: String @@ -47,6 +49,7 @@ private class AdMessagesHistoryContextImpl { text: String, textEntities: [MessageTextEntity], media: [Media], + contentMedia: [Media], color: PeerNameColor?, backgroundEmojiId: Int64?, url: String, @@ -61,6 +64,7 @@ private class AdMessagesHistoryContextImpl { self.text = text self.textEntities = textEntities self.media = media + self.contentMedia = contentMedia self.color = color self.backgroundEmojiId = backgroundEmojiId self.url = url @@ -89,6 +93,12 @@ private class AdMessagesHistoryContextImpl { self.media = mediaData.compactMap { data -> Media? in return PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? Media } + + let contentMediaData = try container.decode([Data].self, forKey: .contentMedia) + self.contentMedia = contentMediaData.compactMap { data -> Media? in + return PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? Media + } + self.color = try container.decodeIfPresent(Int32.self, forKey: .color).flatMap { PeerNameColor(rawValue: $0) } self.backgroundEmojiId = try container.decodeIfPresent(Int64.self, forKey: .backgroundEmojiId) @@ -116,6 +126,13 @@ private class AdMessagesHistoryContextImpl { return encoder.makeData() } try container.encode(mediaData, forKey: .media) + + let contentMediaData = self.contentMedia.map { media -> Data in + let encoder = PostboxEncoder() + encoder.encodeRootObject(media) + return encoder.makeData() + } + try container.encode(contentMediaData, forKey: .contentMedia) try container.encodeIfPresent(self.color?.rawValue, forKey: .color) try container.encodeIfPresent(self.backgroundEmojiId, forKey: .backgroundEmojiId) @@ -153,6 +170,14 @@ private class AdMessagesHistoryContextImpl { return false } } + if lhs.contentMedia.count != rhs.contentMedia.count { + return false + } + for i in 0 ..< lhs.contentMedia.count { + if !lhs.contentMedia[i].isEqual(to: rhs.contentMedia[i]) { + return false + } + } if lhs.url != rhs.url { return false } @@ -181,7 +206,7 @@ private class AdMessagesHistoryContextImpl { case .recommended: mappedMessageType = .recommended } - attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, messageType: mappedMessageType, url: self.url, buttonText: self.buttonText, sponsorInfo: self.sponsorInfo, additionalInfo: self.additionalInfo, canReport: self.canReport)) + attributes.append(AdMessageAttribute(opaqueId: self.opaqueId, messageType: mappedMessageType, url: self.url, buttonText: self.buttonText, sponsorInfo: self.sponsorInfo, additionalInfo: self.additionalInfo, canReport: self.canReport, hasContentMedia: !self.contentMedia.isEmpty)) if !self.textEntities.isEmpty { let attribute = TextEntitiesMessageAttribute(entities: self.textEntities) attributes.append(attribute) @@ -241,7 +266,7 @@ private class AdMessagesHistoryContextImpl { author: author, text: self.text, attributes: attributes, - media: self.media, + media: !self.contentMedia.isEmpty ? self.contentMedia : self.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], @@ -422,7 +447,7 @@ private class AdMessagesHistoryContextImpl { for message in messages { switch message { - case let .sponsoredMessage(flags, randomId, url, title, message, entities, photo, color, buttonText, sponsorInfo, additionalInfo): + case let .sponsoredMessage(flags, randomId, url, title, message, entities, photo, media, color, buttonText, sponsorInfo, additionalInfo): var parsedEntities: [MessageTextEntity] = [] if let entities = entities { parsedEntities = messageTextEntitiesFromApiEntities(entities) @@ -442,6 +467,8 @@ private class AdMessagesHistoryContextImpl { } let photo = photo.flatMap { telegramMediaImageFromApiPhoto($0) } + let (contentMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId) + parsedMessages.append(CachedMessage( opaqueId: randomId.makeData(), messageType: isRecommended ? .recommended : .sponsored, @@ -449,6 +476,7 @@ private class AdMessagesHistoryContextImpl { text: message, textEntities: parsedEntities, media: photo.flatMap { [$0] } ?? [], + contentMedia: contentMedia.flatMap { [$0] } ?? [], color: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, backgroundEmojiId: backgroundEmojiId, url: url, diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 0e62df17a4..5df0687e91 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -686,7 +686,7 @@ public final class StarsContext { return peerId! } - var currentState: StarsContext.State? { + public var currentState: StarsContext.State? { var state: StarsContext.State? self.impl.syncWith { impl in state = impl._state diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift index 62a7d6ebdd..5f4e7797d9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift @@ -637,6 +637,14 @@ public struct PeerInvitationImportersState: Equatable { public var about: String? public var approvedBy: PeerId? public var joinedViaFolderLink: Bool + + public init(peer: RenderedPeer, date: Int32, about: String? = nil, approvedBy: PeerId? = nil, joinedViaFolderLink: Bool) { + self.peer = peer + self.date = date + self.about = about + self.approvedBy = approvedBy + self.joinedViaFolderLink = joinedViaFolderLink + } } public var importers: [Importer] public var isLoadingMore: Bool diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index 4ee70dcddd..6ef71e157f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -318,6 +318,12 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { flags.remove(.preferMediaInline) mediaAndFlags = (mediaAndFlagsValue.0, flags) } + if let adAttribute = message.adAttribute, adAttribute.hasContentMedia { + var flags = mediaAndFlagsValue.1 + flags.remove(.preferMediaInline) + flags.insert(.preferMediaBeforeText) + mediaAndFlags = (mediaAndFlagsValue.0, flags) + } } var contentMediaAspectFilled = false diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift index a869fe1543..27ce00d8f0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageWebpageBubbleContentNode/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -82,7 +82,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent } } } - let openChatMessageMode: ChatControllerInteractionOpenMessageMode + var openChatMessageMode: ChatControllerInteractionOpenMessageMode switch mode { case .default: openChatMessageMode = .default @@ -91,6 +91,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent case .automaticPlayback: openChatMessageMode = .automaticPlayback } + if let adAttribute = item.message.adAttribute, adAttribute.hasContentMedia { + openChatMessageMode = .automaticPlayback + } if !item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode)) { if let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content { var isConcealed = true diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 968ad4dbac..7c897a228a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -22,6 +22,8 @@ public final class ChatRecentActionsController: TelegramBaseController { private let context: AccountContext private let peer: Peer private let initialAdminPeerId: PeerId? + let starsState: StarsRevenueStats? + private var presentationData: PresentationData private var presentationDataPromise = Promise() override public var updatedPresentationData: (PresentationData, Signal) { @@ -37,10 +39,11 @@ public final class ChatRecentActionsController: TelegramBaseController { private var adminsDisposable: Disposable? - public init(context: AccountContext, peer: Peer, adminPeerId: PeerId?) { + public init(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) { self.context = context self.peer = peer self.initialAdminPeerId = adminPeerId + self.starsState = starsState self.presentationData = context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index bbe912994c..167d7f080e 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -210,10 +210,16 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { ])]) strongSelf.presentController(actionSheet, .window(.root), nil) } else { - let controller = inviteLinkEditController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peerId: peer.id, invite: invite, completion: { [weak self] _ in - self?.eventLogContext.reload() - }) - controller.navigationPresentation = .modal + let controller = InviteLinkViewController( + context: strongSelf.context, + updatedPresentationData: strongSelf.controller?.updatedPresentationData, + peerId: peer.id, + invite: invite, + invitationsContext: nil, + revokedInvitationsContext: nil, + importersContext: nil, + starsState: strongSelf.controller?.starsState + ) strongSelf.pushController(controller) } return true diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 115495afae..65c7f1afdb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -8584,7 +8584,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro guard let peer = self.data?.peer else { return } - let controller = self.context.sharedContext.makeChatRecentActionsController(context: self.context, peer: peer, adminPeerId: nil) + let controller = self.context.sharedContext.makeChatRecentActionsController(context: self.context, peer: peer, adminPeerId: nil, starsState: self.data?.starsRevenueStatsState) self.controller?.push(controller) } diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index a5257985bd..f1765f36f4 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -208,9 +208,24 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { let textString: String switch context.component.purpose { - case let .generic(requiredStars): - let _ = requiredStars + case .generic: textString = strings.Stars_Purchase_GetStarsInfo + case let .topUp(_, purpose): + var text = strings.Stars_Purchase_GenericPurchasePurpose + if let purpose, !purpose.isEmpty { + switch purpose { + case "subs": + text = strings.Stars_Purchase_PurchasePurpose_subs + default: + let key = "Stars.Purchase.PurchasePurpose.\(purpose)" + if let string = strings.primaryComponent.dict[key] { + text = string + } else if let string = strings.secondaryComponent?.dict[key] { + text = string + } + } + } + textString = text case .gift: textString = strings.Stars_Purchase_GiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string case .transfer: @@ -816,12 +831,10 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { let titleText: String switch context.component.purpose { - case let .generic(requiredStars): - if let requiredStars { - titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars)) - } else { - titleText = strings.Stars_Purchase_GetStars - } + case .generic: + titleText = strings.Stars_Purchase_GetStars + case let .topUp(requiredStars, _): + titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars)) case .gift: titleText = strings.Stars_Purchase_GiftStars case let .transfer(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars): @@ -1226,7 +1239,7 @@ private extension StarsPurchasePurpose { var requiredStars: Int64? { switch self { - case let .generic(requiredStars): + case let .topUp(requiredStars, _): return requiredStars case let .transfer(_, requiredStars): return requiredStars diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index 178a74a226..a52f158bc7 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -36,7 +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 + let updateSubscription: () -> Void init( context: AccountContext, @@ -47,7 +47,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void, openAppExamples: @escaping () -> Void, copyTransactionId: @escaping (String) -> Void, - updateSubscription: @escaping (StarsTransactionScreen.SubscriptionAction) -> Void + updateSubscription: @escaping () -> Void ) { self.context = context self.subject = subject @@ -199,6 +199,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { var countOnTop = false var transactionId: String? let date: Int32 + var additionalDate: Int32? var via: String? var messageId: EngineMessage.Id? var toPeer: EnginePeer? @@ -230,14 +231,41 @@ private final class StarsTransactionSheetContent: CombinedComponent { descriptionText = "" count = subscription.pricing.amount date = subscription.untilDate + if let creationDate = (subscription.peer._asPeer() as? TelegramChannel)?.creationDate, creationDate > 0 { + additionalDate = creationDate + } else { + additionalDate = nil + } toPeer = subscription.peer transactionPeer = .peer(subscription.peer) isSubscription = true - if subscription.flags.contains(.isCancelled) { - statusText = strings.Stars_Transaction_Subscription_Cancelled - statusIsDestructive = true - buttonText = strings.Stars_Transaction_Subscription_Renew + var hasLeft = false + if let toPeer, case let .channel(channel) = toPeer, channel.participationStatus == .left { + hasLeft = true + } + + if hasLeft { + if subscription.flags.contains(.isCancelled) { + statusText = strings.Stars_Transaction_Subscription_Cancelled + statusIsDestructive = true + if date > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) { + buttonText = strings.Stars_Transaction_Subscription_Renew + } else { + if let _ = subscription.inviteHash { + buttonText = strings.Stars_Transaction_Subscription_JoinAgainChannel + } else { + buttonText = strings.Common_OK + } + } + } else { + if date < Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) { + buttonText = strings.Stars_Transaction_Subscription_Renew + } else { + statusText = strings.Stars_Transaction_Subscription_LeftChannel(stringForMediumDate(timestamp: subscription.untilDate, strings: strings, dateTimeFormat: dateTimeFormat, withTime: false)).string + buttonText = strings.Stars_Transaction_Subscription_JoinChannel + } + } isCancelled = true } else { statusText = strings.Stars_Transaction_Subscription_Active(stringForMediumDate(timestamp: subscription.untilDate, strings: strings, dateTimeFormat: dateTimeFormat, withTime: false)).string @@ -628,16 +656,26 @@ private final class StarsTransactionSheetContent: CombinedComponent { )) } + if isSubscription, let additionalDate { + tableItems.append(.init( + id: "additionalDate", + title: strings.Stars_Transaction_Subscription_Status_Subscribed, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: additionalDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor))) + ) + )) + } + let dateTitle: String if isSubscription { - if isCancelled { - if date > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) { + if date > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) { + if isCancelled { dateTitle = strings.Stars_Transaction_Subscription_Status_Expires } else { - dateTitle = strings.Stars_Transaction_Subscription_Status_Expired + dateTitle = strings.Stars_Transaction_Subscription_Status_Renews } } else { - dateTitle = strings.Stars_Transaction_Subscription_Status_Renews + dateTitle = strings.Stars_Transaction_Subscription_Status_Expired } } else if isSubscriber { dateTitle = strings.Stars_Transaction_Subscription_Status_Subscribed @@ -652,6 +690,16 @@ private final class StarsTransactionSheetContent: CombinedComponent { ) )) + if isSubscriber, let additionalDate { + tableItems.append(.init( + id: "additionalDate", + title: strings.Stars_Transaction_Subscription_Status_Renews, + component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: additionalDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor))) + ) + )) + } + let table = table.update( component: TableComponent( theme: environment.theme, @@ -857,13 +905,8 @@ private final class StarsTransactionSheetContent: CombinedComponent { isLoading: state.inProgress, action: { component.cancel(true) - if isSubscription { - if buttonIsDestructive { - component.updateSubscription(.cancel) - } else { - component.updateSubscription(.renew) - } + component.updateSubscription() } } ), @@ -899,7 +942,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 + let updateSubscription: () -> Void init( context: AccountContext, @@ -909,7 +952,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent { openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void, openAppExamples: @escaping () -> Void, copyTransactionId: @escaping (String) -> Void, - updateSubscription: @escaping (StarsTransactionScreen.SubscriptionAction) -> Void + updateSubscription: @escaping () -> Void ) { self.context = context self.subject = subject @@ -1061,7 +1104,7 @@ 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)? + var updateSubscriptionImpl: (() -> Void)? super.init( context: context, @@ -1083,8 +1126,8 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { copyTransactionId: { transactionId in copyTransactionIdImpl?(transactionId) }, - updateSubscription: { action in - updateSubscriptionImpl?(action) + updateSubscription: { + updateSubscriptionImpl?() } ), navigationBarAppearance: .none, @@ -1197,27 +1240,37 @@ public class StarsTransactionScreen: ViewControllerComponentContainer { HapticFeedback().tap() } - updateSubscriptionImpl = { [weak self] action in + updateSubscriptionImpl = { [weak self] 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 = presentationData.strings.Stars_Transaction_Subscription_Cancelled_Title - text = presentationData.strings.Stars_Transaction_Subscription_Cancelled_Text(subscription.peer.compactDisplayTitle, stringForMediumDate(timestamp: subscription.untilDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)).string - case .renew: - title = presentationData.strings.Stars_Transaction_Subscription_Renewed_Title - text = presentationData.strings.Stars_Transaction_Subscription_Renewed_Text(subscription.peer.compactDisplayTitle).string + var titleAndText: (String, String)? + if subscription.flags.contains(.isCancelled) { + updateSubscription(false) + if subscription.untilDate > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) { + titleAndText = ( + presentationData.strings.Stars_Transaction_Subscription_Renewed_Title, + presentationData.strings.Stars_Transaction_Subscription_Renewed_Text(subscription.peer.compactDisplayTitle).string + ) + } + } else { + if subscription.untilDate < Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) { + updateSubscription(false) + } else { + updateSubscription(true) + titleAndText = ( + presentationData.strings.Stars_Transaction_Subscription_Cancelled_Title, + presentationData.strings.Stars_Transaction_Subscription_Cancelled_Text(subscription.peer.compactDisplayTitle, stringForMediumDate(timestamp: subscription.untilDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)).string + ) + } } - - 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) + + if let (title, text) = titleAndText { + 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) + } } } } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index a939524fcc..39ff8d8111 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -972,7 +972,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { guard let self else { return } - let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic(requiredStars: nil), completion: { [weak self] stars in + let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: options, purpose: .generic, completion: { [weak self] stars in guard let self else { return } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift index b0dc0a709e..30d333d5ca 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransferScreen/Sources/StarsTransferScreen.swift @@ -348,8 +348,7 @@ private final class SheetContent: CombinedComponent { let titleString: String if isSubscription { - //TODO:localize - titleString = "Subscribe to the Channel" + titleString = strings.Stars_Transfer_Subscribe_Channel_Title } else { titleString = strings.Stars_Transfer_Title } @@ -376,7 +375,7 @@ private final class SheetContent: CombinedComponent { let amount = component.invoice.totalAmount let infoText: String if case .starsChatSubscription = context.component.source { - infoText = "Do you want to subscribe to **\(state.botPeer?.compactDisplayTitle ?? "")** for **\(strings.Stars_Transfer_Info_Stars(Int32(amount)))** per month?" + infoText = strings.Stars_Transfer_SubscribeInfo(state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_Info_Stars(Int32(amount))).string } else if !component.extendedMedia.isEmpty { var description: String = "" var photoCount: Int32 = 0 @@ -403,11 +402,20 @@ private final class SheetContent: CombinedComponent { description += "**\(strings.Stars_Transfer_SingleVideo)**" } } - infoText = strings.Stars_Transfer_UnlockInfo( - description, - state.chatPeer?.compactDisplayTitle ?? "", - strings.Stars_Transfer_Info_Stars(Int32(amount)) - ).string + + if let botPeerName = state.botPeer?.compactDisplayTitle { + infoText = strings.Stars_Transfer_UnlockBotInfo( + description, + botPeerName, + strings.Stars_Transfer_Info_Stars(Int32(amount)) + ).string + } else { + infoText = strings.Stars_Transfer_UnlockInfo( + description, + state.chatPeer?.compactDisplayTitle ?? "", + strings.Stars_Transfer_Info_Stars(Int32(amount)) + ).string + } } else { infoText = strings.Stars_Transfer_Info( component.invoice.title, @@ -483,7 +491,7 @@ private final class SheetContent: CombinedComponent { let amountString = presentationStringsFormattedNumber(Int32(amount), presentationData.dateTimeFormat.groupingSeparator) let buttonAttributedString: NSMutableAttributedString if case .starsChatSubscription = component.source { - buttonAttributedString = NSMutableAttributedString(string: "Subscribe", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) + buttonAttributedString = NSMutableAttributedString(string: strings.Stars_Transfer_Subscribe, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) } else { buttonAttributedString = NSMutableAttributedString(string: "\(strings.Stars_Transfer_Pay) # \(amountString)", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) } @@ -524,7 +532,7 @@ private final class SheetContent: CombinedComponent { } else if let peerId = state?.botPeer?.id { purpose = .transfer(peerId: peerId, requiredStars: invoice.totalAmount) } else { - purpose = .generic(requiredStars: nil) + purpose = .generic } let purchaseController = accountContext.sharedContext.makeStarsPurchaseScreen( context: accountContext, @@ -549,9 +557,8 @@ private final class SheetContent: CombinedComponent { var title = presentationData.strings.Stars_Transfer_PurchasedTitle let text: String if isSubscription { - //TODO:localize - title = "Subscription successful!" - text = "\(presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))) transferred to \(botTitle)." + title = presentationData.strings.Stars_Transfer_Subscribe_Successful_Title + text = presentationData.strings.Stars_Transfer_Subscribe_Successful_Text(presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount)), botTitle).string } else if let _ = component.invoice.extendedMedia { text = presentationData.strings.Stars_Transfer_UnlockedText( presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string } else { diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 7cd2153142..82e296a34b 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -706,12 +706,38 @@ func openResolvedUrlImpl( if let navigationController = navigationController { navigationController.pushViewController(controller, animated: true) } - case let .starsTopup(amount): + case let .starsTopup(amount, purpose): dismissInput() if let starsContext = context.starsContext { - let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: [], purpose: .generic(requiredStars: amount), completion: { _ in }) - if let navigationController = navigationController { - navigationController.pushViewController(controller, animated: true) + let proceed = { + let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: [], purpose: .topUp(requiredStars: amount, purpose: purpose), completion: { _ in }) + if let navigationController = navigationController { + navigationController.pushViewController(controller, animated: true) + } + } + if let currentState = starsContext.currentState, currentState.balance >= amount { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = UndoOverlayController( + presentationData: presentationData, + content: .universal( + animation: "StarsBuy", + scale: 0.066, + colors: [:], + title: nil, + text: "You have enough stars at the moment.", + customUndoText: "Buy Anyway", + timeout: nil + ), + elevatedLayout: true, + action: { action in + if case .undo = action { + proceed() + } + return true + }) + present(controller, nil) + } else { + proceed() } } case let .joinVoiceChat(peerId, invite): diff --git a/submodules/TelegramUI/Sources/OpenUrl.swift b/submodules/TelegramUI/Sources/OpenUrl.swift index a30266f109..1ee74427c1 100644 --- a/submodules/TelegramUI/Sources/OpenUrl.swift +++ b/submodules/TelegramUI/Sources/OpenUrl.swift @@ -920,18 +920,23 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur handleResolvedUrl(.premiumMultiGift(reference: reference)) } else if parsedUrl.host == "stars_topup" { var amount: Int64? + var purpose: String? if let components = URLComponents(string: "/?" + query) { if let queryItems = components.queryItems { for queryItem in queryItems { if let value = queryItem.value { - if queryItem.name == "amount" { + if queryItem.name == "balance" { amount = Int64(value) + } else if queryItem.name == "purpose" { + purpose = value } } } } } - handleResolvedUrl(.starsTopup(amount: amount)) + if let amount { + handleResolvedUrl(.starsTopup(amount: amount, purpose: purpose)) + } } else if parsedUrl.host == "addlist" { if let components = URLComponents(string: "/?" + query) { var slug: String? diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 5ce56c2bc8..7f4a28979a 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1650,8 +1650,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return nil } - public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController { - return ChatRecentActionsController(context: context, peer: peer, adminPeerId: adminPeerId) + public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController { + return ChatRecentActionsController(context: context, peer: peer, adminPeerId: adminPeerId, starsState: starsState) } public func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) { diff --git a/versions.json b/versions.json index 394e4986a7..0687306f8e 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "10.15", + "app": "11.0", "xcode": "15.2", "bazel": "7.1.1", "macos": "13.0"