Various improvements

This commit is contained in:
Ilya Laktyushin 2024-08-09 17:43:47 +02:00
parent d17391ad9b
commit 2bf11a6279
31 changed files with 340 additions and 126 deletions

View File

@ -12344,6 +12344,7 @@ Sorry for the inconvenience.";
"Stars.Transfer.Purchased.Stars_any" = "%@ Stars"; "Stars.Transfer.Purchased.Stars_any" = "%@ Stars";
"Stars.Transfer.UnlockedText" = "You unlocked media for **%1$@**."; "Stars.Transfer.UnlockedText" = "You unlocked media for **%1$@**.";
"Stars.Transfer.UnlockInfo" = "Do you want to unlock %1$@ in **%2$@** for **%3$@**?"; "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"; "Stars.Transfer.Balance" = "Balance";
@ -12695,6 +12696,9 @@ Sorry for the inconvenience.";
"Stars.Transaction.Subscription.Cancelled" = "You have cancelled your subscription."; "Stars.Transaction.Subscription.Cancelled" = "You have cancelled your subscription.";
"Stars.Transaction.Subscription.Renew" = "Renew Subscription"; "Stars.Transaction.Subscription.Renew" = "Renew Subscription";
"Stars.Transaction.Subscription.Cancel" = "Cancel 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.PerMonth" = "%@ / month";
"Stars.Transaction.Subscription.PerMonthUsd" = "appx. %@ per month"; "Stars.Transaction.Subscription.PerMonthUsd" = "appx. %@ per month";
"Stars.Transaction.Subscription.Subscription" = "Subscription"; "Stars.Transaction.Subscription.Subscription" = "Subscription";
@ -12721,3 +12725,14 @@ Sorry for the inconvenience.";
"Stars.Intro.Transaction.SubscriptionFee.Title" = "Monthly Subscription Fee"; "Stars.Intro.Transaction.SubscriptionFee.Title" = "Monthly Subscription Fee";
"Stars.Intro.Transaction.Reaction.Title" = "Star Reaction"; "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";

View File

@ -305,7 +305,7 @@ public enum ResolvedUrl {
case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?) case startAttach(peerId: PeerId, payload: String?, choose: ResolvedBotChoosePeerTypes?)
case invoice(slug: String, invoice: TelegramMediaInvoice?) case invoice(slug: String, invoice: TelegramMediaInvoice?)
case premiumOffer(reference: String?) case premiumOffer(reference: String?)
case starsTopup(amount: Int64?) case starsTopup(amount: Int64, purpose: String?)
case chatFolder(slug: String) case chatFolder(slug: String)
case story(peerId: PeerId, id: Int32) case story(peerId: PeerId, id: Int32)
case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?) case boost(peerId: PeerId?, status: ChannelBoostStatus?, myBoostStatus: MyBoostStatus?)
@ -917,7 +917,7 @@ public protocol SharedAccountContext: AnyObject {
func makeProxySettingsController(context: AccountContext) -> ViewController func makeProxySettingsController(context: AccountContext) -> ViewController
func makeLocalizationListController(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 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 makePrivacyAndSecurityController(context: AccountContext) -> ViewController
func makeBioPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, present: @escaping (ViewController) -> Void) func makeBioPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, present: @escaping (ViewController) -> Void)
func makeBirthdayPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) func makeBirthdayPrivacyController(context: AccountContext, settings: Promise<AccountPrivacySettings?>, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void)

View File

@ -123,7 +123,8 @@ public enum BoostSubject: Equatable {
} }
public enum StarsPurchasePurpose: 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 transfer(peerId: EnginePeer.Id, requiredStars: Int64)
case subscription(peerId: EnginePeer.Id, requiredStars: Int64, renew: Bool) case subscription(peerId: EnginePeer.Id, requiredStars: Int64, renew: Bool)
case gift(peerId: EnginePeer.Id) case gift(peerId: EnginePeer.Id)

View File

@ -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) let gallery = SecretMediaPreviewController(context: context, messageId: message.id)
return .secretGallery(gallery) return .secretGallery(gallery)
} else { } else {

View File

@ -770,10 +770,18 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
} }
} }
func setup(origin: GalleryItemOriginData?, caption: NSAttributedString) { func setup(origin: GalleryItemOriginData?, caption: NSAttributedString, isAd: Bool = false) {
let titleText = origin?.title var titleText = origin?.title
let dateText = origin?.timestamp.flatMap { humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: $0).string } 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 { if self.currentMessageText != caption || self.currentAuthorNameText != titleText || self.currentDateText != dateText {
self.currentMessageText = caption self.currentMessageText = caption
self.currentAuthorNameText = titleText self.currentAuthorNameText = titleText
@ -820,9 +828,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
if Namespaces.Message.allNonRegular.contains(message.id.namespace) || message.timestamp == 0 { if Namespaces.Message.allNonRegular.contains(message.id.namespace) || message.timestamp == 0 {
displayInfo = false displayInfo = false
} }
if let _ = message.adAttribute {
displayInfo = false
}
var canDelete: Bool 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 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 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 var hasCaption = false
for media in message.media { for media in message.media {
if media is TelegramMediaPaidContent { if media is TelegramMediaPaidContent {
@ -991,7 +997,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
codeHighlightState.disposable.dispose() 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 { 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 {

View File

@ -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)) node._title.set(.single(self.presentationData.strings.Items_NOfM("\(location.index + 1)", "\(location.count)").string))
} }

View File

@ -1528,15 +1528,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.playbackRatePromise.set(self.playbackRate ?? 1.0) self.playbackRatePromise.set(self.playbackRate ?? 1.0)
var isAd = false
if let contentInfo = item.contentInfo { if let contentInfo = item.contentInfo {
switch contentInfo { switch contentInfo {
case let .message(message, _): case let .message(message, _):
isAd = message.adAttribute != nil
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected) self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected)
case let .webPage(webPage, media, _): case let .webPage(webPage, media, _):
self.footerContentNode.setWebPage(webPage, media: 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) { override func controlsVisibilityUpdated(isVisible: Bool) {

View File

@ -857,18 +857,20 @@ public func inviteLinkEditController(context: AccountContext, updatedPresentatio
private struct StarsSubscriptionConfiguration { private struct StarsSubscriptionConfiguration {
static var defaultValue: StarsSubscriptionConfiguration { static var defaultValue: StarsSubscriptionConfiguration {
return StarsSubscriptionConfiguration(maxFee: 2500) return StarsSubscriptionConfiguration(maxFee: 2500, usdSellRate: 2000)
} }
let maxFee: Int64? let maxFee: Int64?
let usdSellRate: Int64?
fileprivate init(maxFee: Int64?) { fileprivate init(maxFee: Int64?, usdSellRate: Int64?) {
self.maxFee = maxFee self.maxFee = maxFee
self.usdSellRate = usdSellRate
} }
public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration { public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration {
if let data = appConfiguration.data, let value = data["stars_subscription_amount_max"] as? Double { 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)) return StarsSubscriptionConfiguration(maxFee: Int64(value), usdSellRate: Int64(usdRate))
} else { } else {
return .defaultValue return .defaultValue
} }

View File

@ -182,6 +182,7 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
private var absoluteLocation: (CGRect, CGSize)? private var absoluteLocation: (CGRect, CGSize)?
private var currentColor: ItemBackgroundColor? private var currentColor: ItemBackgroundColor?
private var currentIsPaid: Bool?
private var layoutParams: (ItemListInviteLinkItem, ListViewItemLayoutParams, ItemListNeighbors, Bool, Bool)? private var layoutParams: (ItemListInviteLinkItem, ListViewItemLayoutParams, ItemListNeighbors, Bool, Bool)?
public var tag: ItemListItemTag? public var tag: ItemListItemTag?
@ -548,8 +549,12 @@ public class ItemListInviteLinkItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor 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) strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/SubscriptionLink"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)
} else { } else {
strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/InviteLink"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Item List/InviteLink"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor)

View File

@ -1358,6 +1358,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
let _ = statusApply() let _ = statusApply()
if case let .account(context) = item.context { 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)) 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 strongSelf.labelNode.textNode.isHidden = labelAttributedString == nil

View File

@ -709,7 +709,7 @@ public func channelAdminsController(context: AccountContext, updatedPresentation
} }
if case .legacyGroup = peer { if case .legacyGroup = peer {
} else { } 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))
} }
}) })
}) })

View File

@ -890,7 +890,7 @@ public func groupStatsController(context: AccountContext, updatedPresentationDat
let _ = (context.account.postbox.loadedPeerWithId(peerId) let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { peer in |> 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) navigationController.pushViewController(controller)
}) })
} }

View File

@ -881,7 +881,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) } dict[-651419003] = { return Api.SendMessageAction.parse_speakingInGroupCallAction($0) }
dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) } dict[-1239335713] = { return Api.ShippingOption.parse_shippingOption($0) }
dict[-425595208] = { return Api.SmsJob.parse_smsJob($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[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
dict[1577421297] = { return Api.StarsGiftOption.parse_starsGiftOption($0) } dict[1577421297] = { return Api.StarsGiftOption.parse_starsGiftOption($0) }
dict[2033461574] = { return Api.StarsRevenueStatus.parse_starsRevenueStatus($0) } dict[2033461574] = { return Api.StarsRevenueStatus.parse_starsRevenueStatus($0) }

View File

@ -441,14 +441,14 @@ public extension Api {
} }
} }
public extension Api { public extension Api {
enum SponsoredMessage: TypeConstructorDescription { indirect 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?) 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { 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 { if boxed {
buffer.appendInt32(-1108478618) buffer.appendInt32(1301522832)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeBytes(randomId, buffer: buffer, boxed: false) serializeBytes(randomId, buffer: buffer, boxed: false)
@ -461,6 +461,7 @@ public extension Api {
item.serialize(buffer, true) item.serialize(buffer, true)
}} }}
if Int(flags) & Int(1 << 6) != 0 {photo!.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)} if Int(flags) & Int(1 << 13) != 0 {color!.serialize(buffer, true)}
serializeString(buttonText, buffer: buffer, boxed: false) serializeString(buttonText, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 7) != 0 {serializeString(sponsorInfo!, 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)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { 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):
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)]) 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() { if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.Photo _7 = Api.parse(reader, signature: signature) as? Api.Photo
} } } }
var _8: Api.PeerColor? var _8: Api.MessageMedia?
if Int(_1!) & Int(1 << 13) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.PeerColor _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? var _10: String?
if Int(_1!) & Int(1 << 7) != 0 {_10 = parseString(reader) } _10 = parseString(reader)
var _11: String? 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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -512,12 +517,13 @@ public extension Api {
let _c5 = _5 != nil let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil let _c7 = (Int(_1!) & Int(1 << 6) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 13) == 0) || _8 != nil let _c8 = (Int(_1!) & Int(1 << 14) == 0) || _8 != nil
let _c9 = _9 != nil let _c9 = (Int(_1!) & Int(1 << 13) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 7) == 0) || _10 != nil let _c10 = _10 != nil
let _c11 = (Int(_1!) & Int(1 << 8) == 0) || _11 != nil let _c11 = (Int(_1!) & Int(1 << 7) == 0) || _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 { let _c12 = (Int(_1!) & Int(1 << 8) == 0) || _12 != nil
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) 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 { else {
return nil return nil

View File

@ -14,8 +14,9 @@ public final class AdMessageAttribute: MessageAttribute {
public let sponsorInfo: String? public let sponsorInfo: String?
public let additionalInfo: String? public let additionalInfo: String?
public let canReport: Bool 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.opaqueId = opaqueId
self.messageType = messageType self.messageType = messageType
self.url = url self.url = url
@ -23,6 +24,7 @@ public final class AdMessageAttribute: MessageAttribute {
self.sponsorInfo = sponsorInfo self.sponsorInfo = sponsorInfo
self.additionalInfo = additionalInfo self.additionalInfo = additionalInfo
self.canReport = canReport self.canReport = canReport
self.hasContentMedia = hasContentMedia
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {

View File

@ -12,6 +12,7 @@ private class AdMessagesHistoryContextImpl {
case text case text
case textEntities case textEntities
case media case media
case contentMedia
case color case color
case backgroundEmojiId case backgroundEmojiId
case url case url
@ -32,6 +33,7 @@ private class AdMessagesHistoryContextImpl {
public let text: String public let text: String
public let textEntities: [MessageTextEntity] public let textEntities: [MessageTextEntity]
public let media: [Media] public let media: [Media]
public let contentMedia: [Media]
public let color: PeerNameColor? public let color: PeerNameColor?
public let backgroundEmojiId: Int64? public let backgroundEmojiId: Int64?
public let url: String public let url: String
@ -47,6 +49,7 @@ private class AdMessagesHistoryContextImpl {
text: String, text: String,
textEntities: [MessageTextEntity], textEntities: [MessageTextEntity],
media: [Media], media: [Media],
contentMedia: [Media],
color: PeerNameColor?, color: PeerNameColor?,
backgroundEmojiId: Int64?, backgroundEmojiId: Int64?,
url: String, url: String,
@ -61,6 +64,7 @@ private class AdMessagesHistoryContextImpl {
self.text = text self.text = text
self.textEntities = textEntities self.textEntities = textEntities
self.media = media self.media = media
self.contentMedia = contentMedia
self.color = color self.color = color
self.backgroundEmojiId = backgroundEmojiId self.backgroundEmojiId = backgroundEmojiId
self.url = url self.url = url
@ -89,6 +93,12 @@ private class AdMessagesHistoryContextImpl {
self.media = mediaData.compactMap { data -> Media? in self.media = mediaData.compactMap { data -> Media? in
return PostboxDecoder(buffer: MemoryBuffer(data: data)).decodeRootObject() as? Media 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.color = try container.decodeIfPresent(Int32.self, forKey: .color).flatMap { PeerNameColor(rawValue: $0) }
self.backgroundEmojiId = try container.decodeIfPresent(Int64.self, forKey: .backgroundEmojiId) self.backgroundEmojiId = try container.decodeIfPresent(Int64.self, forKey: .backgroundEmojiId)
@ -117,6 +127,13 @@ private class AdMessagesHistoryContextImpl {
} }
try container.encode(mediaData, forKey: .media) 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.color?.rawValue, forKey: .color)
try container.encodeIfPresent(self.backgroundEmojiId, forKey: .backgroundEmojiId) try container.encodeIfPresent(self.backgroundEmojiId, forKey: .backgroundEmojiId)
@ -153,6 +170,14 @@ private class AdMessagesHistoryContextImpl {
return false 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 { if lhs.url != rhs.url {
return false return false
} }
@ -181,7 +206,7 @@ private class AdMessagesHistoryContextImpl {
case .recommended: case .recommended:
mappedMessageType = .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 { if !self.textEntities.isEmpty {
let attribute = TextEntitiesMessageAttribute(entities: self.textEntities) let attribute = TextEntitiesMessageAttribute(entities: self.textEntities)
attributes.append(attribute) attributes.append(attribute)
@ -241,7 +266,7 @@ private class AdMessagesHistoryContextImpl {
author: author, author: author,
text: self.text, text: self.text,
attributes: attributes, attributes: attributes,
media: self.media, media: !self.contentMedia.isEmpty ? self.contentMedia : self.media,
peers: messagePeers, peers: messagePeers,
associatedMessages: SimpleDictionary<MessageId, Message>(), associatedMessages: SimpleDictionary<MessageId, Message>(),
associatedMessageIds: [], associatedMessageIds: [],
@ -422,7 +447,7 @@ private class AdMessagesHistoryContextImpl {
for message in messages { for message in messages {
switch message { 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] = [] var parsedEntities: [MessageTextEntity] = []
if let entities = entities { if let entities = entities {
parsedEntities = messageTextEntitiesFromApiEntities(entities) parsedEntities = messageTextEntitiesFromApiEntities(entities)
@ -442,6 +467,8 @@ private class AdMessagesHistoryContextImpl {
} }
let photo = photo.flatMap { telegramMediaImageFromApiPhoto($0) } let photo = photo.flatMap { telegramMediaImageFromApiPhoto($0) }
let (contentMedia, _, _, _, _) = textMediaAndExpirationTimerFromApiMedia(media, peerId)
parsedMessages.append(CachedMessage( parsedMessages.append(CachedMessage(
opaqueId: randomId.makeData(), opaqueId: randomId.makeData(),
messageType: isRecommended ? .recommended : .sponsored, messageType: isRecommended ? .recommended : .sponsored,
@ -449,6 +476,7 @@ private class AdMessagesHistoryContextImpl {
text: message, text: message,
textEntities: parsedEntities, textEntities: parsedEntities,
media: photo.flatMap { [$0] } ?? [], media: photo.flatMap { [$0] } ?? [],
contentMedia: contentMedia.flatMap { [$0] } ?? [],
color: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) }, color: nameColorIndex.flatMap { PeerNameColor(rawValue: $0) },
backgroundEmojiId: backgroundEmojiId, backgroundEmojiId: backgroundEmojiId,
url: url, url: url,

View File

@ -686,7 +686,7 @@ public final class StarsContext {
return peerId! return peerId!
} }
var currentState: StarsContext.State? { public var currentState: StarsContext.State? {
var state: StarsContext.State? var state: StarsContext.State?
self.impl.syncWith { impl in self.impl.syncWith { impl in
state = impl._state state = impl._state

View File

@ -637,6 +637,14 @@ public struct PeerInvitationImportersState: Equatable {
public var about: String? public var about: String?
public var approvedBy: PeerId? public var approvedBy: PeerId?
public var joinedViaFolderLink: Bool 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 importers: [Importer]
public var isLoadingMore: Bool public var isLoadingMore: Bool

View File

@ -318,6 +318,12 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
flags.remove(.preferMediaInline) flags.remove(.preferMediaInline)
mediaAndFlags = (mediaAndFlagsValue.0, flags) 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 var contentMediaAspectFilled = false

View File

@ -82,7 +82,7 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
} }
} }
} }
let openChatMessageMode: ChatControllerInteractionOpenMessageMode var openChatMessageMode: ChatControllerInteractionOpenMessageMode
switch mode { switch mode {
case .default: case .default:
openChatMessageMode = .default openChatMessageMode = .default
@ -91,6 +91,9 @@ public final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContent
case .automaticPlayback: case .automaticPlayback:
openChatMessageMode = .automaticPlayback openChatMessageMode = .automaticPlayback
} }
if let adAttribute = item.message.adAttribute, adAttribute.hasContentMedia {
openChatMessageMode = .automaticPlayback
}
if !item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode)) { if !item.controllerInteraction.openMessage(item.message, OpenMessageParams(mode: openChatMessageMode)) {
if let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content { if let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
var isConcealed = true var isConcealed = true

View File

@ -22,6 +22,8 @@ public final class ChatRecentActionsController: TelegramBaseController {
private let context: AccountContext private let context: AccountContext
private let peer: Peer private let peer: Peer
private let initialAdminPeerId: PeerId? private let initialAdminPeerId: PeerId?
let starsState: StarsRevenueStats?
private var presentationData: PresentationData private var presentationData: PresentationData
private var presentationDataPromise = Promise<PresentationData>() private var presentationDataPromise = Promise<PresentationData>()
override public var updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) { override public var updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) {
@ -37,10 +39,11 @@ public final class ChatRecentActionsController: TelegramBaseController {
private var adminsDisposable: Disposable? 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.context = context
self.peer = peer self.peer = peer
self.initialAdminPeerId = adminPeerId self.initialAdminPeerId = adminPeerId
self.starsState = starsState
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }

View File

@ -210,10 +210,16 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
])]) ])])
strongSelf.presentController(actionSheet, .window(.root), nil) strongSelf.presentController(actionSheet, .window(.root), nil)
} else { } else {
let controller = inviteLinkEditController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peerId: peer.id, invite: invite, completion: { [weak self] _ in let controller = InviteLinkViewController(
self?.eventLogContext.reload() context: strongSelf.context,
}) updatedPresentationData: strongSelf.controller?.updatedPresentationData,
controller.navigationPresentation = .modal peerId: peer.id,
invite: invite,
invitationsContext: nil,
revokedInvitationsContext: nil,
importersContext: nil,
starsState: strongSelf.controller?.starsState
)
strongSelf.pushController(controller) strongSelf.pushController(controller)
} }
return true return true

View File

@ -8584,7 +8584,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
guard let peer = self.data?.peer else { guard let peer = self.data?.peer else {
return 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) self.controller?.push(controller)
} }

View File

@ -208,9 +208,24 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
let textString: String let textString: String
switch context.component.purpose { switch context.component.purpose {
case let .generic(requiredStars): case .generic:
let _ = requiredStars
textString = strings.Stars_Purchase_GetStarsInfo 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: case .gift:
textString = strings.Stars_Purchase_GiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string textString = strings.Stars_Purchase_GiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
case .transfer: case .transfer:
@ -816,12 +831,10 @@ private final class StarsPurchaseScreenComponent: CombinedComponent {
let titleText: String let titleText: String
switch context.component.purpose { switch context.component.purpose {
case let .generic(requiredStars): case .generic:
if let requiredStars {
titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars))
} else {
titleText = strings.Stars_Purchase_GetStars titleText = strings.Stars_Purchase_GetStars
} case let .topUp(requiredStars, _):
titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars))
case .gift: case .gift:
titleText = strings.Stars_Purchase_GiftStars titleText = strings.Stars_Purchase_GiftStars
case let .transfer(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars): case let .transfer(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars):
@ -1226,7 +1239,7 @@ private extension StarsPurchasePurpose {
var requiredStars: Int64? { var requiredStars: Int64? {
switch self { switch self {
case let .generic(requiredStars): case let .topUp(requiredStars, _):
return requiredStars return requiredStars
case let .transfer(_, requiredStars): case let .transfer(_, requiredStars):
return requiredStars return requiredStars

View File

@ -36,7 +36,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
let openAppExamples: () -> Void let openAppExamples: () -> Void
let copyTransactionId: (String) -> Void let copyTransactionId: (String) -> Void
let updateSubscription: (StarsTransactionScreen.SubscriptionAction) -> Void let updateSubscription: () -> Void
init( init(
context: AccountContext, context: AccountContext,
@ -47,7 +47,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void, openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
openAppExamples: @escaping () -> Void, openAppExamples: @escaping () -> Void,
copyTransactionId: @escaping (String) -> Void, copyTransactionId: @escaping (String) -> Void,
updateSubscription: @escaping (StarsTransactionScreen.SubscriptionAction) -> Void updateSubscription: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -199,6 +199,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
var countOnTop = false var countOnTop = false
var transactionId: String? var transactionId: String?
let date: Int32 let date: Int32
var additionalDate: Int32?
var via: String? var via: String?
var messageId: EngineMessage.Id? var messageId: EngineMessage.Id?
var toPeer: EnginePeer? var toPeer: EnginePeer?
@ -230,14 +231,41 @@ private final class StarsTransactionSheetContent: CombinedComponent {
descriptionText = "" descriptionText = ""
count = subscription.pricing.amount count = subscription.pricing.amount
date = subscription.untilDate date = subscription.untilDate
if let creationDate = (subscription.peer._asPeer() as? TelegramChannel)?.creationDate, creationDate > 0 {
additionalDate = creationDate
} else {
additionalDate = nil
}
toPeer = subscription.peer toPeer = subscription.peer
transactionPeer = .peer(subscription.peer) transactionPeer = .peer(subscription.peer)
isSubscription = true isSubscription = true
var hasLeft = false
if let toPeer, case let .channel(channel) = toPeer, channel.participationStatus == .left {
hasLeft = true
}
if hasLeft {
if subscription.flags.contains(.isCancelled) { if subscription.flags.contains(.isCancelled) {
statusText = strings.Stars_Transaction_Subscription_Cancelled statusText = strings.Stars_Transaction_Subscription_Cancelled
statusIsDestructive = true statusIsDestructive = true
if date > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) {
buttonText = strings.Stars_Transaction_Subscription_Renew 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 isCancelled = true
} else { } else {
statusText = strings.Stars_Transaction_Subscription_Active(stringForMediumDate(timestamp: subscription.untilDate, strings: strings, dateTimeFormat: dateTimeFormat, withTime: false)).string 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 let dateTitle: String
if isSubscription { if isSubscription {
if isCancelled {
if date > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) { if date > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) {
if isCancelled {
dateTitle = strings.Stars_Transaction_Subscription_Status_Expires dateTitle = strings.Stars_Transaction_Subscription_Status_Expires
} else { } else {
dateTitle = strings.Stars_Transaction_Subscription_Status_Expired dateTitle = strings.Stars_Transaction_Subscription_Status_Renews
} }
} else { } else {
dateTitle = strings.Stars_Transaction_Subscription_Status_Renews dateTitle = strings.Stars_Transaction_Subscription_Status_Expired
} }
} else if isSubscriber { } else if isSubscriber {
dateTitle = strings.Stars_Transaction_Subscription_Status_Subscribed 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( let table = table.update(
component: TableComponent( component: TableComponent(
theme: environment.theme, theme: environment.theme,
@ -857,13 +905,8 @@ private final class StarsTransactionSheetContent: CombinedComponent {
isLoading: state.inProgress, isLoading: state.inProgress,
action: { action: {
component.cancel(true) component.cancel(true)
if isSubscription { if isSubscription {
if buttonIsDestructive { component.updateSubscription()
component.updateSubscription(.cancel)
} else {
component.updateSubscription(.renew)
}
} }
} }
), ),
@ -899,7 +942,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void let openMedia: ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void
let openAppExamples: () -> Void let openAppExamples: () -> Void
let copyTransactionId: (String) -> Void let copyTransactionId: (String) -> Void
let updateSubscription: (StarsTransactionScreen.SubscriptionAction) -> Void let updateSubscription: () -> Void
init( init(
context: AccountContext, context: AccountContext,
@ -909,7 +952,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void, openMedia: @escaping ([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void,
openAppExamples: @escaping () -> Void, openAppExamples: @escaping () -> Void,
copyTransactionId: @escaping (String) -> Void, copyTransactionId: @escaping (String) -> Void,
updateSubscription: @escaping (StarsTransactionScreen.SubscriptionAction) -> Void updateSubscription: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.subject = subject 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 openMediaImpl: (([Media], @escaping (Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?, @escaping (UIView) -> Void) -> Void)?
var openAppExamplesImpl: (() -> Void)? var openAppExamplesImpl: (() -> Void)?
var copyTransactionIdImpl: ((String) -> Void)? var copyTransactionIdImpl: ((String) -> Void)?
var updateSubscriptionImpl: ((StarsTransactionScreen.SubscriptionAction) -> Void)? var updateSubscriptionImpl: (() -> Void)?
super.init( super.init(
context: context, context: context,
@ -1083,8 +1126,8 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
copyTransactionId: { transactionId in copyTransactionId: { transactionId in
copyTransactionIdImpl?(transactionId) copyTransactionIdImpl?(transactionId)
}, },
updateSubscription: { action in updateSubscription: {
updateSubscriptionImpl?(action) updateSubscriptionImpl?()
} }
), ),
navigationBarAppearance: .none, navigationBarAppearance: .none,
@ -1197,30 +1240,40 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
HapticFeedback().tap() 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 { guard let self, case let .subscription(subscription) = subject, let navigationController = self.navigationController as? NavigationController else {
return return
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
updateSubscription(action == .cancel) var titleAndText: (String, String)?
if subscription.flags.contains(.isCancelled) {
let title: String updateSubscription(false)
let text: String if subscription.untilDate > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) {
switch action { titleAndText = (
case .cancel: presentationData.strings.Stars_Transaction_Subscription_Renewed_Title,
title = presentationData.strings.Stars_Transaction_Subscription_Cancelled_Title presentationData.strings.Stars_Transaction_Subscription_Renewed_Text(subscription.peer.compactDisplayTitle).string
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 } else {
text = presentationData.strings.Stars_Transaction_Subscription_Renewed_Text(subscription.peer.compactDisplayTitle).string 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
)
}
} }
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 }) 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) { Queue.mainQueue().after(0.6) {
navigationController.presentOverlay(controller: controller) navigationController.presentOverlay(controller: controller)
} }
} }
} }
}
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")

View File

@ -972,7 +972,7 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer {
guard let self else { guard let self else {
return 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 { guard let self else {
return return
} }

View File

@ -348,8 +348,7 @@ private final class SheetContent: CombinedComponent {
let titleString: String let titleString: String
if isSubscription { if isSubscription {
//TODO:localize titleString = strings.Stars_Transfer_Subscribe_Channel_Title
titleString = "Subscribe to the Channel"
} else { } else {
titleString = strings.Stars_Transfer_Title titleString = strings.Stars_Transfer_Title
} }
@ -376,7 +375,7 @@ private final class SheetContent: CombinedComponent {
let amount = component.invoice.totalAmount let amount = component.invoice.totalAmount
let infoText: String let infoText: String
if case .starsChatSubscription = context.component.source { 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 { } else if !component.extendedMedia.isEmpty {
var description: String = "" var description: String = ""
var photoCount: Int32 = 0 var photoCount: Int32 = 0
@ -403,11 +402,20 @@ private final class SheetContent: CombinedComponent {
description += "**\(strings.Stars_Transfer_SingleVideo)**" description += "**\(strings.Stars_Transfer_SingleVideo)**"
} }
} }
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( infoText = strings.Stars_Transfer_UnlockInfo(
description, description,
state.chatPeer?.compactDisplayTitle ?? "", state.chatPeer?.compactDisplayTitle ?? "",
strings.Stars_Transfer_Info_Stars(Int32(amount)) strings.Stars_Transfer_Info_Stars(Int32(amount))
).string ).string
}
} else { } else {
infoText = strings.Stars_Transfer_Info( infoText = strings.Stars_Transfer_Info(
component.invoice.title, component.invoice.title,
@ -483,7 +491,7 @@ private final class SheetContent: CombinedComponent {
let amountString = presentationStringsFormattedNumber(Int32(amount), presentationData.dateTimeFormat.groupingSeparator) let amountString = presentationStringsFormattedNumber(Int32(amount), presentationData.dateTimeFormat.groupingSeparator)
let buttonAttributedString: NSMutableAttributedString let buttonAttributedString: NSMutableAttributedString
if case .starsChatSubscription = component.source { 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 { } else {
buttonAttributedString = NSMutableAttributedString(string: "\(strings.Stars_Transfer_Pay) # \(amountString)", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) 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 { } else if let peerId = state?.botPeer?.id {
purpose = .transfer(peerId: peerId, requiredStars: invoice.totalAmount) purpose = .transfer(peerId: peerId, requiredStars: invoice.totalAmount)
} else { } else {
purpose = .generic(requiredStars: nil) purpose = .generic
} }
let purchaseController = accountContext.sharedContext.makeStarsPurchaseScreen( let purchaseController = accountContext.sharedContext.makeStarsPurchaseScreen(
context: accountContext, context: accountContext,
@ -549,9 +557,8 @@ private final class SheetContent: CombinedComponent {
var title = presentationData.strings.Stars_Transfer_PurchasedTitle var title = presentationData.strings.Stars_Transfer_PurchasedTitle
let text: String let text: String
if isSubscription { if isSubscription {
//TODO:localize title = presentationData.strings.Stars_Transfer_Subscribe_Successful_Title
title = "Subscription successful!" text = presentationData.strings.Stars_Transfer_Subscribe_Successful_Text(presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount)), botTitle).string
text = "\(presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))) transferred to \(botTitle)."
} else if let _ = component.invoice.extendedMedia { } else if let _ = component.invoice.extendedMedia {
text = presentationData.strings.Stars_Transfer_UnlockedText( presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string text = presentationData.strings.Stars_Transfer_UnlockedText( presentationData.strings.Stars_Transfer_Purchased_Stars(Int32(invoice.totalAmount))).string
} else { } else {

View File

@ -706,14 +706,40 @@ func openResolvedUrlImpl(
if let navigationController = navigationController { if let navigationController = navigationController {
navigationController.pushViewController(controller, animated: true) navigationController.pushViewController(controller, animated: true)
} }
case let .starsTopup(amount): case let .starsTopup(amount, purpose):
dismissInput() dismissInput()
if let starsContext = context.starsContext { if let starsContext = context.starsContext {
let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: [], purpose: .generic(requiredStars: amount), completion: { _ in }) let proceed = {
let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: [], purpose: .topUp(requiredStars: amount, purpose: purpose), completion: { _ in })
if let navigationController = navigationController { if let navigationController = navigationController {
navigationController.pushViewController(controller, animated: true) 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): case let .joinVoiceChat(peerId, invite):
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in |> deliverOnMainQueue).start(next: { peer in

View File

@ -920,18 +920,23 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
handleResolvedUrl(.premiumMultiGift(reference: reference)) handleResolvedUrl(.premiumMultiGift(reference: reference))
} else if parsedUrl.host == "stars_topup" { } else if parsedUrl.host == "stars_topup" {
var amount: Int64? var amount: Int64?
var purpose: String?
if let components = URLComponents(string: "/?" + query) { if let components = URLComponents(string: "/?" + query) {
if let queryItems = components.queryItems { if let queryItems = components.queryItems {
for queryItem in queryItems { for queryItem in queryItems {
if let value = queryItem.value { if let value = queryItem.value {
if queryItem.name == "amount" { if queryItem.name == "balance" {
amount = Int64(value) 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" { } else if parsedUrl.host == "addlist" {
if let components = URLComponents(string: "/?" + query) { if let components = URLComponents(string: "/?" + query) {
var slug: String? var slug: String?

View File

@ -1650,8 +1650,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return nil return nil
} }
public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController { public func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?, starsState: StarsRevenueStats?) -> ViewController {
return ChatRecentActionsController(context: context, peer: peer, adminPeerId: adminPeerId) return ChatRecentActionsController(context: context, peer: peer, adminPeerId: adminPeerId, starsState: starsState)
} }
public func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) { public func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) {

View File

@ -1,5 +1,5 @@
{ {
"app": "10.15", "app": "11.0",
"xcode": "15.2", "xcode": "15.2",
"bazel": "7.1.1", "bazel": "7.1.1",
"macos": "13.0" "macos": "13.0"