Various improvements

This commit is contained in:
Ilya Laktyushin 2024-12-27 23:04:34 +04:00
parent e3ee1dde7e
commit 5d4213c4fc
41 changed files with 1193 additions and 545 deletions

View File

@ -980,7 +980,7 @@ private final class NotificationServiceHandler {
enum Action { enum Action {
case logout case logout
case poll(peerId: PeerId, content: NotificationContent, messageId: MessageId?) case poll(peerId: PeerId, content: NotificationContent, messageId: MessageId?, reportDelivery: Bool)
case pollStories(peerId: PeerId, content: NotificationContent, storyId: Int32, isReaction: Bool) case pollStories(peerId: PeerId, content: NotificationContent, storyId: Int32, isReaction: Bool)
case deleteMessage([MessageId]) case deleteMessage([MessageId])
case readReactions([MessageId]) case readReactions([MessageId])
@ -999,7 +999,7 @@ private final class NotificationServiceHandler {
action = .logout action = .logout
case "MESSAGE_MUTED": case "MESSAGE_MUTED":
if let peerId = peerId { if let peerId = peerId {
action = .poll(peerId: peerId, content: NotificationContent(isLockedMessage: nil), messageId: nil) action = .poll(peerId: peerId, content: NotificationContent(isLockedMessage: nil), messageId: nil, reportDelivery: false)
} }
case "MESSAGE_DELETED": case "MESSAGE_DELETED":
if let peerId = peerId { if let peerId = peerId {
@ -1183,7 +1183,14 @@ private final class NotificationServiceHandler {
action = .pollStories(peerId: peerId, content: content, storyId: storyId, isReaction: isReaction) action = .pollStories(peerId: peerId, content: content, storyId: storyId, isReaction: isReaction)
} else { } else {
action = .poll(peerId: peerId, content: content, messageId: messageIdValue) var reportDelivery = false
if let reportDeliveryUntilDate = aps["report_delivery_until_date"] as? Int32, let messageId = messageIdValue {
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if reportDeliveryUntilDate > currentTime {
reportDelivery = true
}
}
action = .poll(peerId: peerId, content: content, messageId: messageIdValue, reportDelivery: reportDelivery)
} }
updateCurrentContent(content) updateCurrentContent(content)
@ -1245,7 +1252,7 @@ private final class NotificationServiceHandler {
let content = NotificationContent(isLockedMessage: nil) let content = NotificationContent(isLockedMessage: nil)
updateCurrentContent(content) updateCurrentContent(content)
completed() completed()
case let .poll(peerId, initialContent, messageId): case let .poll(peerId, initialContent, messageId, reportDelivery):
Logger.shared.log("NotificationService \(episode)", "Will poll") Logger.shared.log("NotificationService \(episode)", "Will poll")
if let stateManager = strongSelf.stateManager { if let stateManager = strongSelf.stateManager {
let shouldKeepConnection = stateManager.network.shouldKeepConnection let shouldKeepConnection = stateManager.network.shouldKeepConnection
@ -1684,11 +1691,22 @@ private final class NotificationServiceHandler {
|> map { _ -> (NotificationContent, Media?) in } |> map { _ -> (NotificationContent, Media?) in }
} }
let reportDeliverySignal: Signal<Bool, NoError>
if reportDelivery, let messageId {
reportDeliverySignal = _internal_reportMessageDelivery(postbox: stateManager.postbox, network: stateManager.network, messageIds: [messageId], fromPushNotification: true)
|> mapToSignal { _ -> Signal<Bool, NoError> in
return .single(true)
}
|> then(.single(true))
} else {
reportDeliverySignal = .single(true)
}
var updatedContent = initialContent var updatedContent = initialContent
var updatedMedia: Media? var updatedMedia: Media?
strongSelf.pollDisposable.set(pollWithUpdatedContent.start(next: { content, media in strongSelf.pollDisposable.set(combineLatest(pollWithUpdatedContent, reportDeliverySignal).start(next: { contentAndMedia, _ in
updatedContent = content updatedContent = contentAndMedia.0
updatedMedia = media updatedMedia = contentAndMedia.1
}, completed: { }, completed: {
pollCompletion(updatedContent, updatedMedia) pollCompletion(updatedContent, updatedMedia)
})) }))
@ -1952,6 +1970,8 @@ private final class NotificationServiceHandler {
pollWithUpdatedContent = .complete() pollWithUpdatedContent = .complete()
} }
var updatedContent = initialContent var updatedContent = initialContent
strongSelf.pollDisposable.set(pollWithUpdatedContent.start(next: { content in strongSelf.pollDisposable.set(pollWithUpdatedContent.start(next: { content in
updatedContent = content updatedContent = content

Binary file not shown.

View File

@ -12299,6 +12299,7 @@ Sorry for the inconvenience.";
"Stars.Intro.Transaction.ConvertedGift" = "Converted Gift"; "Stars.Intro.Transaction.ConvertedGift" = "Converted Gift";
"Stars.Intro.Transaction.Unsupported.Title" = "Unsupported"; "Stars.Intro.Transaction.Unsupported.Title" = "Unsupported";
"Stars.Intro.Transaction.Refund" = "Refund"; "Stars.Intro.Transaction.Refund" = "Refund";
"Stars.Intro.Transaction.GiftUpgrade" = "Gift Upgrade";
"Stars.Intro.PurchasedTitle" = "Stars Acquired"; "Stars.Intro.PurchasedTitle" = "Stars Acquired";
"Stars.Intro.PurchasedText" = "**%@** added to your balance."; "Stars.Intro.PurchasedText" = "**%@** added to your balance.";
@ -13503,9 +13504,9 @@ Sorry for the inconvenience.";
"Gift.Unique.Availability" = "Availability"; "Gift.Unique.Availability" = "Availability";
"Gift.Unique.Issued" = "%@ issued"; "Gift.Unique.Issued" = "%@ issued";
"Gift.Unique.OriginalInfo" = "Gifted to %1$@ on %2$@."; "Gift.Unique.OriginalInfo" = "Gifted to %1$@ on %2$@.";
"Gift.Unique.OriginalInfoWithText" = "Gifted to %1$@ on %2$@ with comment \"%3$@\""; "Gift.Unique.OriginalInfoWithText" = "Gifted to %1$@ on %2$@ with comment \"%3$@\".";
"Gift.Unique.OriginalInfoSender" = "Gifted by %1$@ to %2$@ on %3$@."; "Gift.Unique.OriginalInfoSender" = "Gifted by %1$@ to %2$@ on %3$@.";
"Gift.Unique.OriginalInfoSenderWithText" = "Gifted by %1$@ to %2$@ on %3$@ with comment \"%4$@\""; "Gift.Unique.OriginalInfoSenderWithText" = "Gifted by %1$@ to %2$@ on %3$@ with comment \"%4$@\".";
"Gift.Unique.AttributeDescription" = "Only %@ of such collectibles have this attribute."; "Gift.Unique.AttributeDescription" = "Only %@ of such collectibles have this attribute.";
@ -13533,6 +13534,7 @@ Sorry for the inconvenience.";
"Gift.View.UpgradeForFree" = "Upgrade for Free"; "Gift.View.UpgradeForFree" = "Upgrade for Free";
"Gift.View.KeepUpgradeOrConvertDescription" = "You can keep this gift, upgrade it, or sell it for %@. [More About Stars >]()"; "Gift.View.KeepUpgradeOrConvertDescription" = "You can keep this gift, upgrade it, or sell it for %@. [More About Stars >]()";
"Gift.View.KeepOrUpgradeDescription" = "You can keep this gift or upgrade it.";
"PeerInfo.VerificationInfo.Bot" = "This bot is verified as official by the representatives of Telegram."; "PeerInfo.VerificationInfo.Bot" = "This bot is verified as official by the representatives of Telegram.";
"PeerInfo.VerificationInfo.Channel" = "This channel is verified as official by the representatives of Telegram."; "PeerInfo.VerificationInfo.Channel" = "This channel is verified as official by the representatives of Telegram.";
@ -13561,4 +13563,7 @@ Sorry for the inconvenience.";
"Notification.StarGift.Subtitle.Refunded" = "This gift cannot be converted to Stars because the payment related to it was refunded."; "Notification.StarGift.Subtitle.Refunded" = "This gift cannot be converted to Stars because the payment related to it was refunded.";
"Notification.StarGift.Subtitle.Downgraded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned."; "Notification.StarGift.Subtitle.Downgraded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned.";
"Gift.View.KeepOrUpgradeDescription" = "You can keep this gift or upgrade it."; "Notification.StarGift.Subtitle.Other" = "%1$@ can keep this gift or upgrade it.";
"Stars.Transaction.GiftFrom" = "Gift From";
"Stars.Transaction.GiftUpgrade" = "Gift Upgrade";

View File

@ -218,7 +218,7 @@ public final class DefaultAnimatedStickerNodeImpl: ASDisplayNode, AnimatedSticke
public var isPlaying: Bool = false public var isPlaying: Bool = false
private var currentLoopCount: Int = 0 private var currentLoopCount: Int = 0
private var canDisplayFirstFrame: Bool = false private var canDisplayFirstFrame: Bool = false
private var playbackMode: AnimatedStickerPlaybackMode = .loop public var playbackMode: AnimatedStickerPlaybackMode = .loop
public var stopAtNearestLoop: Bool = false public var stopAtNearestLoop: Bool = false

View File

@ -1527,7 +1527,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
} }
switch result { switch result {
case let .done(receiptMessageId, _): case let .done(receiptMessageId, _, _):
proceedWithCompletion(true, receiptMessageId) proceedWithCompletion(true, receiptMessageId)
case let .externalVerificationRequired(url): case let .externalVerificationRequired(url):
strongSelf.updateActionButton() strongSelf.updateActionButton()

View File

@ -1352,6 +1352,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
let _ = titleApply() let _ = titleApply()
var titleLeftOffset: CGFloat = 0.0 var titleLeftOffset: CGFloat = 0.0
var nextIconX: CGFloat = titleFrame.maxX
if let verifiedIcon { if let verifiedIcon {
let animationCache = item.context.animationCache let animationCache = item.context.animationCache
let animationRenderer = item.context.animationRenderer let animationRenderer = item.context.animationRenderer
@ -1376,16 +1377,29 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
) )
strongSelf.verifiedIconComponent = verifiedIconComponent strongSelf.verifiedIconComponent = verifiedIconComponent
let iconOrigin: CGFloat
if case .animation = verifiedIcon {
iconOrigin = titleFrame.minX
} else {
nextIconX += 4.0
iconOrigin = nextIconX
}
let containerSize = CGSize(width: 16.0, height: 16.0)
let iconSize = verifiedIconView.update( let iconSize = verifiedIconView.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(verifiedIconComponent), component: AnyComponent(verifiedIconComponent),
environment: {}, environment: {},
containerSize: CGSize(width: 16.0, height: 16.0) containerSize: containerSize
) )
transition.updateFrame(view: verifiedIconView, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) transition.updateFrame(view: verifiedIconView, frame: CGRect(origin: CGPoint(x: iconOrigin, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize))
titleLeftOffset += iconSize.width + 4.0 if case .animation = verifiedIcon {
titleLeftOffset += iconSize.width + 4.0
} else {
nextIconX += iconSize.width
}
} else if let verifiedIconView = strongSelf.verifiedIconView { } else if let verifiedIconView = strongSelf.verifiedIconView {
strongSelf.verifiedIconView = nil strongSelf.verifiedIconView = nil
verifiedIconView.removeFromSuperview() verifiedIconView.removeFromSuperview()
@ -1424,7 +1438,6 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
} }
} }
var nextIconX: CGFloat = titleFrame.maxX
if let credibilityIcon { if let credibilityIcon {
let animationCache = item.context.animationCache let animationCache = item.context.animationCache
let animationRenderer = item.context.animationRenderer let animationRenderer = item.context.animationRenderer

View File

@ -126,7 +126,7 @@ public final class TextAlertContentActionNode: HighlightableButtonNode {
} }
let attributedString = NSMutableAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center) let attributedString = NSMutableAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center)
if let range = attributedString.string.range(of: "⭐️") { if let range = attributedString.string.range(of: "$") {
attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string)) attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.foregroundColor, value: color, range: NSRange(range, in: attributedString.string)) attributedString.addAttribute(.foregroundColor, value: color, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string)) attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))

View File

@ -1436,6 +1436,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
var titleFrame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size) var titleFrame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size)
var titleLeftOffset: CGFloat = 0.0 var titleLeftOffset: CGFloat = 0.0
var nextIconX: CGFloat = titleFrame.maxX
if let verifiedIcon = verifiedIcon { if let verifiedIcon = verifiedIcon {
let animationCache = item.context.animationCache let animationCache = item.context.animationCache
let animationRenderer = item.context.animationRenderer let animationRenderer = item.context.animationRenderer
@ -1461,6 +1462,15 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
emojiFileUpdated: nil emojiFileUpdated: nil
) )
strongSelf.verifiedIconComponent = verifiedIconComponent strongSelf.verifiedIconComponent = verifiedIconComponent
let iconOrigin: CGFloat
if case .animation = verifiedIcon {
iconOrigin = titleFrame.minX
} else {
nextIconX += 4.0
iconOrigin = nextIconX
}
let iconSize = verifiedIconView.update( let iconSize = verifiedIconView.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(verifiedIconComponent), component: AnyComponent(verifiedIconComponent),
@ -1468,9 +1478,13 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
containerSize: CGSize(width: 20.0, height: 20.0) containerSize: CGSize(width: 20.0, height: 20.0)
) )
transition.updateFrame(view: verifiedIconView, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) transition.updateFrame(view: verifiedIconView, frame: CGRect(origin: CGPoint(x: iconOrigin, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize))
titleLeftOffset += iconSize.width + 4.0 if case .animation = verifiedIcon {
titleLeftOffset += iconSize.width + 4.0
} else {
nextIconX += iconSize.width
}
} else if let verifiedIconView = strongSelf.verifiedIconView { } else if let verifiedIconView = strongSelf.verifiedIconView {
strongSelf.verifiedIconView = nil strongSelf.verifiedIconView = nil
verifiedIconView.removeFromSuperview() verifiedIconView.removeFromSuperview()
@ -1512,7 +1526,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
containerSize: CGSize(width: 20.0, height: 20.0) containerSize: CGSize(width: 20.0, height: 20.0)
) )
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize)) nextIconX += 4.0
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextIconX, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0)), size: iconSize))
} else if let credibilityIconView = strongSelf.credibilityIconView { } else if let credibilityIconView = strongSelf.credibilityIconView {
strongSelf.credibilityIconView = nil strongSelf.credibilityIconView = nil
credibilityIconView.removeFromSuperview() credibilityIconView.removeFromSuperview()

View File

@ -336,7 +336,7 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
theme: item.presentationData.theme, theme: item.presentationData.theme,
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)),
contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0), contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: item.context, theme: item.presentationData.theme, peer: item.transaction.peer, photo: nil, media: [], backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor))), false), leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: item.context, theme: item.presentationData.theme, peer: item.transaction.peer, photo: nil, media: [], uniqueGift: nil, backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor))), false),
icon: nil, icon: nil,
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
action: { [weak self] _ in action: { [weak self] _ in

View File

@ -132,6 +132,7 @@ enum AccountStateMutationOperation {
case UpdateStarsBalance(peerId: PeerId, balance: Api.StarsAmount) case UpdateStarsBalance(peerId: PeerId, balance: Api.StarsAmount)
case UpdateStarsRevenueStatus(peerId: PeerId, status: StarsRevenueStats.Balances) case UpdateStarsRevenueStatus(peerId: PeerId, status: StarsRevenueStats.Balances)
case UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: Bool) case UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: Bool)
case ReportMessageDelivery([MessageId])
} }
struct HoleFromPreviousState { struct HoleFromPreviousState {
@ -702,9 +703,13 @@ struct AccountMutableState {
self.addOperation(.UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: isAnonymous)) self.addOperation(.UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: isAnonymous))
} }
mutating func addReportMessageDelivery(messageIds: [MessageId]) {
self.addOperation(.ReportMessageDelivery(messageIds))
}
mutating func addOperation(_ operation: AccountStateMutationOperation) { mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsAreAnonymousByDefault: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsAreAnonymousByDefault, .ReportMessageDelivery:
break break
case let .AddMessages(messages, location): case let .AddMessages(messages, location):
for message in messages { for message in messages {
@ -852,6 +857,7 @@ struct AccountReplayedFinalState {
let updatedStarsBalance: [PeerId: StarsAmount] let updatedStarsBalance: [PeerId: StarsAmount]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let sentScheduledMessageIds: Set<MessageId> let sentScheduledMessageIds: Set<MessageId>
let reportMessageDelivery: Set<MessageId>
} }
struct AccountFinalStateEvents { struct AccountFinalStateEvents {
@ -882,12 +888,13 @@ struct AccountFinalStateEvents {
let updatedRevenueBalances: [PeerId: RevenueStats.Balances] let updatedRevenueBalances: [PeerId: RevenueStats.Balances]
let updatedStarsBalance: [PeerId: StarsAmount] let updatedStarsBalance: [PeerId: StarsAmount]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let reportMessageDelivery: Set<MessageId>
var isEmpty: Bool { var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty
} }
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set()) { init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set()) {
self.addedIncomingMessageIds = addedIncomingMessageIds self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds
@ -915,6 +922,7 @@ struct AccountFinalStateEvents {
self.updatedStarsBalance = updatedStarsBalance self.updatedStarsBalance = updatedStarsBalance
self.updatedStarsRevenueStatus = updatedStarsRevenueStatus self.updatedStarsRevenueStatus = updatedStarsRevenueStatus
self.sentScheduledMessageIds = sentScheduledMessageIds self.sentScheduledMessageIds = sentScheduledMessageIds
self.reportMessageDelivery = reportMessageDelivery
} }
init(state: AccountReplayedFinalState) { init(state: AccountReplayedFinalState) {
@ -945,6 +953,7 @@ struct AccountFinalStateEvents {
self.updatedStarsBalance = state.updatedStarsBalance self.updatedStarsBalance = state.updatedStarsBalance
self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus
self.sentScheduledMessageIds = state.sentScheduledMessageIds self.sentScheduledMessageIds = state.sentScheduledMessageIds
self.reportMessageDelivery = state.reportMessageDelivery
} }
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
@ -977,6 +986,9 @@ struct AccountFinalStateEvents {
var sentScheduledMessageIds = self.sentScheduledMessageIds var sentScheduledMessageIds = self.sentScheduledMessageIds
sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds) sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds)
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }), sentScheduledMessageIds: sentScheduledMessageIds) var reportMessageDelivery = self.reportMessageDelivery
reportMessageDelivery.formUnion(other.reportMessageDelivery)
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }), sentScheduledMessageIds: sentScheduledMessageIds, reportMessageDelivery: reportMessageDelivery)
} }
} }

View File

@ -171,7 +171,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId)) return TelegramMediaAction(action: .paymentRefunded(peerId: peer.peerId, currency: currency, totalAmount: totalAmount, payload: payload?.makeData(), transactionId: transactionId))
case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId): case let .messageActionPrizeStars(flags, stars, transactionId, boostPeer, giveawayMsgId):
return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId))) return TelegramMediaAction(action: .prizeStars(amount: stars, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer.peerId, transactionId: transactionId, giveawayMessageId: MessageId(peerId: boostPeer.peerId, namespace: Namespaces.Message.Cloud, id: giveawayMsgId)))
case let .messageActionStarGift(flags, apiGift, message, convertStars, _, upgradeStars): case let .messageActionStarGift(flags, apiGift, message, convertStars, upgradeMessageId, upgradeStars):
let text: String? let text: String?
let entities: [MessageTextEntity]? let entities: [MessageTextEntity]?
switch message { switch message {
@ -185,7 +185,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
guard let gift = StarGift(apiStarGift: apiGift) else { guard let gift = StarGift(apiStarGift: apiGift) else {
return nil return nil
} }
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0)) return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, upgradeMessageId: upgradeMessageId))
case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars): case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars):
guard let gift = StarGift(apiStarGift: apiGift) else { guard let gift = StarGift(apiStarGift: apiGift) else {
return nil return nil

View File

@ -721,6 +721,7 @@ func finalStateWithDifference(accountPeerId: PeerId, postbox: Postbox, network:
updatedState.mergeChats(chats) updatedState.mergeChats(chats)
updatedState.mergeUsers(users) updatedState.mergeUsers(users)
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
for message in messages { for message in messages {
if let preCachedResources = message.preCachedResources { if let preCachedResources = message.preCachedResources {
for (resource, data) in preCachedResources { for (resource, data) in preCachedResources {
@ -738,6 +739,10 @@ func finalStateWithDifference(accountPeerId: PeerId, postbox: Postbox, network:
} }
if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) { if let message = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) {
updatedState.addMessages([message], location: .UpperHistoryBlock) updatedState.addMessages([message], location: .UpperHistoryBlock)
if let reportDeliveryAttribute = message.attributes.first(where: { $0 is ReportDeliveryMessageAttribute }) as? ReportDeliveryMessageAttribute, case let .Id(id) = message.id, reportDeliveryAttribute.untilDate > currentTime {
updatedState.addReportMessageDelivery(messageIds: [id])
}
} }
} }
@ -901,6 +906,8 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
} }
} }
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
var missingUpdatesFromChannels = Set<PeerId>() var missingUpdatesFromChannels = Set<PeerId>()
for update in sortedUpdates(updates) { for update in sortedUpdates(updates) {
@ -1110,6 +1117,10 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
} }
} }
updatedState.addMessages([message], location: .UpperHistoryBlock) updatedState.addMessages([message], location: .UpperHistoryBlock)
if let reportDeliveryAttribute = message.attributes.first(where: { $0 is ReportDeliveryMessageAttribute }) as? ReportDeliveryMessageAttribute, case let .Id(id) = message.id, reportDeliveryAttribute.untilDate > currentTime {
updatedState.addReportMessageDelivery(messageIds: [id])
}
} }
case let .updateServiceNotification(flags, date, type, text, media, entities): case let .updateServiceNotification(flags, date, type, text, media, entities):
let popup = (flags & (1 << 0)) != 0 let popup = (flags & (1 << 0)) != 0
@ -3282,7 +3293,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddQuickReplyMessages: OptimizeAddMessagesState? var currentAddQuickReplyMessages: OptimizeAddMessagesState?
for operation in operations { for operation in operations {
switch operation { switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsAreAnonymousByDefault: case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateRevenueBalances, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsAreAnonymousByDefault, .ReportMessageDelivery:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
} }
@ -3421,6 +3432,7 @@ func replayFinalState(
var updatedStarsBalance: [PeerId: StarsAmount] = [:] var updatedStarsBalance: [PeerId: StarsAmount] = [:]
var updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:] var updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:]
var updatedStarsReactionsAreAnonymousByDefault: Bool? var updatedStarsReactionsAreAnonymousByDefault: Bool?
var reportMessageDelivery = Set<MessageId>()
var holesFromPreviousStateMessageIds: [MessageId] = [] var holesFromPreviousStateMessageIds: [MessageId] = []
var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:]
@ -4855,6 +4867,8 @@ func replayFinalState(
updatedStarsRevenueStatus[peerId] = status updatedStarsRevenueStatus[peerId] = status
case let .UpdateStarsReactionsAreAnonymousByDefault(value): case let .UpdateStarsReactionsAreAnonymousByDefault(value):
updatedStarsReactionsAreAnonymousByDefault = value updatedStarsReactionsAreAnonymousByDefault = value
case let .ReportMessageDelivery(messageIds):
reportMessageDelivery = Set(messageIds)
} }
} }
@ -5376,6 +5390,7 @@ func replayFinalState(
updatedRevenueBalances: updatedRevenueBalances, updatedRevenueBalances: updatedRevenueBalances,
updatedStarsBalance: updatedStarsBalance, updatedStarsBalance: updatedStarsBalance,
updatedStarsRevenueStatus: updatedStarsRevenueStatus, updatedStarsRevenueStatus: updatedStarsRevenueStatus,
sentScheduledMessageIds: finalState.state.sentScheduledMessageIds sentScheduledMessageIds: finalState.state.sentScheduledMessageIds,
reportMessageDelivery: reportMessageDelivery
) )
} }

View File

@ -352,6 +352,7 @@ public final class AccountStateManager {
private let appliedMaxMessageIdDisposable = MetaDisposable() private let appliedMaxMessageIdDisposable = MetaDisposable()
private let appliedQtsPromise = Promise<Int32?>(nil) private let appliedQtsPromise = Promise<Int32?>(nil)
private let appliedQtsDisposable = MetaDisposable() private let appliedQtsDisposable = MetaDisposable()
private let reportMessageDeliveryDisposable = DisposableSet()
let updateConfigRequested: (() -> Void)? let updateConfigRequested: (() -> Void)?
let isPremiumUpdated: (() -> Void)? let isPremiumUpdated: (() -> Void)?
@ -391,6 +392,7 @@ public final class AccountStateManager {
self.operationDisposable.dispose() self.operationDisposable.dispose()
self.appliedMaxMessageIdDisposable.dispose() self.appliedMaxMessageIdDisposable.dispose()
self.appliedQtsDisposable.dispose() self.appliedQtsDisposable.dispose()
self.reportMessageDeliveryDisposable.dispose()
} }
public func reset() { public func reset() {
@ -1130,6 +1132,9 @@ public final class AccountStateManager {
if !events.sentScheduledMessageIds.isEmpty { if !events.sentScheduledMessageIds.isEmpty {
strongSelf.sentScheduledMessageIdsPipe.putNext(events.sentScheduledMessageIds) strongSelf.sentScheduledMessageIdsPipe.putNext(events.sentScheduledMessageIds)
} }
if !events.reportMessageDelivery.isEmpty {
strongSelf.reportMessageDeliveryDisposable.add(_internal_reportMessageDelivery(postbox: strongSelf.postbox, network: strongSelf.network, messageIds: Array(events.reportMessageDelivery), fromPushNotification: false).start())
}
if !events.isContactUpdates.isEmpty { if !events.isContactUpdates.isEmpty {
strongSelf.addIsContactUpdates(events.isContactUpdates) strongSelf.addIsContactUpdates(events.isContactUpdates)
} }

View File

@ -4,24 +4,20 @@ import TelegramApi
public final class ReportDeliveryMessageAttribute: Equatable, MessageAttribute { public final class ReportDeliveryMessageAttribute: Equatable, MessageAttribute {
public let untilDate: Int32 public let untilDate: Int32
public let isReported: Bool
public init(untilDate: Int32, isReported: Bool) { public init(untilDate: Int32, isReported: Bool) {
self.untilDate = untilDate self.untilDate = untilDate
self.isReported = isReported
} }
required public init(decoder: PostboxDecoder) { required public init(decoder: PostboxDecoder) {
self.untilDate = decoder.decodeInt32ForKey("d", orElse: 0) self.untilDate = decoder.decodeInt32ForKey("d", orElse: 0)
self.isReported = decoder.decodeBoolForKey("r", orElse: false)
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.untilDate, forKey: "d") encoder.encodeInt32(self.untilDate, forKey: "d")
encoder.encodeBool(self.isReported, forKey: "r")
} }
public static func ==(lhs: ReportDeliveryMessageAttribute, rhs: ReportDeliveryMessageAttribute) -> Bool { public static func ==(lhs: ReportDeliveryMessageAttribute, rhs: ReportDeliveryMessageAttribute) -> Bool {
return lhs.untilDate == rhs.untilDate && lhs.isReported == rhs.isReported return lhs.untilDate == rhs.untilDate
} }
} }

View File

@ -130,7 +130,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String) case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String)
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?) case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool) case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, upgradeMessageId: Int32?)
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool) case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool)
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -253,7 +253,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} }
self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId) self = .prizeStars(amount: decoder.decodeInt64ForKey("amount", orElse: 0), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: boostPeerId, transactionId: decoder.decodeOptionalStringForKey("transactionId"), giveawayMessageId: giveawayMessageId)
case 44: case 44:
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false)) self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"))
case 45: case 45:
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false)) self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false))
default: default:
@ -548,7 +548,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "giveawayMsgId") encoder.encodeNil(forKey: "giveawayMsgId")
} }
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded): case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, upgradeMessageId):
encoder.encodeInt32(44, forKey: "_rawValue") encoder.encodeInt32(44, forKey: "_rawValue")
encoder.encodeObject(gift, forKey: "gift") encoder.encodeObject(gift, forKey: "gift")
if let convertStars { if let convertStars {
@ -574,6 +574,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
encoder.encodeNil(forKey: "upgradeStars") encoder.encodeNil(forKey: "upgradeStars")
} }
encoder.encodeBool(isRefunded, forKey: "isRefunded") encoder.encodeBool(isRefunded, forKey: "isRefunded")
if let upgradeMessageId {
encoder.encodeInt32(upgradeMessageId, forKey: "upgradeMessageId")
} else {
encoder.encodeNil(forKey: "upgradeMessageId")
}
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded): case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded):
encoder.encodeInt32(45, forKey: "_rawValue") encoder.encodeInt32(45, forKey: "_rawValue")
encoder.encodeObject(gift, forKey: "gift") encoder.encodeObject(gift, forKey: "gift")

View File

@ -0,0 +1,33 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
public func _internal_reportMessageDelivery(postbox: Postbox, network: Network, messageIds: [EngineMessage.Id], fromPushNotification: Bool) -> Signal<Never, NoError> {
var signals: [Signal<Void, NoError>] = []
for (peerId, messageIds) in messagesIdsGroupedByPeerId(messageIds) {
signals.append(_internal_reportMessageDeliveryByPeerId(postbox: postbox, network: network, peerId: peerId, messageIds: messageIds, fromPushNotification: fromPushNotification))
}
return combineLatest(signals)
|> ignoreValues
}
private func _internal_reportMessageDeliveryByPeerId(postbox: Postbox, network: Network, peerId: EnginePeer.Id, messageIds: [EngineMessage.Id], fromPushNotification: Bool) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Signal<Void, NoError> in
guard let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) else {
return .complete()
}
var flags: Int32 = 0
if fromPushNotification {
flags |= (1 << 0)
}
return network.request(Api.functions.messages.reportMessagesDelivery(flags: flags, peer: inputPeer, id: messageIds.map { $0.id }))
|> `catch` { error -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> mapToSignal { _ in
return .complete()
}
}
|> switchToLatest
}

View File

@ -175,6 +175,7 @@ extension BotPaymentMethod {
public enum BotPaymentFormRequestError { public enum BotPaymentFormRequestError {
case generic case generic
case alreadyActive case alreadyActive
case noPaymentNeeded
} }
extension BotPaymentInvoice { extension BotPaymentInvoice {
@ -457,7 +458,10 @@ func _internal_fetchBotPaymentForm(accountPeerId: PeerId, postbox: Postbox, netw
} }
return network.request(Api.functions.payments.getPaymentForm(flags: flags, invoice: invoice, themeParams: serializedThemeParams)) return network.request(Api.functions.payments.getPaymentForm(flags: flags, invoice: invoice, themeParams: serializedThemeParams))
|> `catch` { _ -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in |> `catch` { error -> Signal<Api.payments.PaymentForm, BotPaymentFormRequestError> in
if error.errorDescription == "NO_PAYMENT_NEEDED" {
return .fail(.noPaymentNeeded)
}
return .fail(.generic) return .fail(.generic)
} }
|> mapToSignal { result -> Signal<BotPaymentForm, BotPaymentFormRequestError> in |> mapToSignal { result -> Signal<BotPaymentForm, BotPaymentFormRequestError> in
@ -622,7 +626,7 @@ public enum SendBotPaymentFormError {
} }
public enum SendBotPaymentResult { public enum SendBotPaymentResult {
case done(receiptMessageId: MessageId?, subscriptionPeerId: PeerId?) case done(receiptMessageId: MessageId?, subscriptionPeerId: PeerId?, uniqueStarGift: ProfileGiftsContext.State.StarGift?)
case externalVerificationRequired(url: String) case externalVerificationRequired(url: String)
} }
@ -671,7 +675,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa
case .starsChatSubscription: case .starsChatSubscription:
let chats = updates.chats.compactMap { parseTelegramGroupOrChannel(chat: $0) } let chats = updates.chats.compactMap { parseTelegramGroupOrChannel(chat: $0) }
if let first = chats.first { if let first = chats.first {
return .done(receiptMessageId: nil, subscriptionPeerId: first.id) return .done(receiptMessageId: nil, subscriptionPeerId: first.id, uniqueStarGift: nil)
} }
default: default:
break break
@ -721,7 +725,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa
} }
} }
} }
return .done(receiptMessageId: receiptMessageId, subscriptionPeerId: nil) return .done(receiptMessageId: receiptMessageId, subscriptionPeerId: nil, uniqueStarGift: nil)
case let .paymentVerificationNeeded(url): case let .paymentVerificationNeeded(url):
return .externalVerificationRequired(url: url) return .externalVerificationRequired(url: url)
} }

View File

@ -689,15 +689,23 @@ func _internal_transferStarGift(account: Account, prepaid: Bool, messageId: Engi
} else { } else {
let source: BotPaymentInvoiceSource = .starGiftTransfer(messageId: messageId, toPeerId: peerId) let source: BotPaymentInvoiceSource = .starGiftTransfer(messageId: messageId, toPeerId: peerId)
return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil) return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil)
|> mapError { _ -> TransferStarGiftError in |> map(Optional.init)
return .generic |> `catch` { error -> Signal<BotPaymentForm?, TransferStarGiftError> in
if case .noPaymentNeeded = error {
return .single(nil)
}
return .fail(.generic)
} }
|> mapToSignal { paymentForm in |> mapToSignal { paymentForm in
return _internal_sendStarsPaymentForm(account: account, formId: paymentForm.id, source: source) if let paymentForm {
|> mapError { _ -> TransferStarGiftError in return _internal_sendStarsPaymentForm(account: account, formId: paymentForm.id, source: source)
return .generic |> mapError { _ -> TransferStarGiftError in
return .generic
}
|> ignoreValues
} else {
return _internal_transferStarGift(account: account, prepaid: true, messageId: messageId, peerId: peerId)
} }
|> ignoreValues
} }
} }
} }
@ -714,8 +722,12 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, messageId: Engi
|> mapError { _ -> UpgradeStarGiftError in |> mapError { _ -> UpgradeStarGiftError in
return .generic return .generic
} }
|> mapToSignal { _ in |> mapToSignal { result in
return .complete() if case let .done(_, _, gift) = result, let gift {
return .single(gift)
} else {
return .complete()
}
} }
} else { } else {
var flags: Int32 = 0 var flags: Int32 = 0
@ -970,16 +982,30 @@ private final class ProfileGiftsContextImpl {
self.pushState() self.pushState()
} }
func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) { func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {
self.actionDisposable.set( return Signal { [weak self] subscriber in
_internal_upgradeStarGift(account: self.account, formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo).startStrict(next: { [weak self] result in guard let self else {
guard let self else { return EmptyDisposable
return }
} let disposable = MetaDisposable()
let _ = self disposable.set(
}) _internal_upgradeStarGift(account: self.account, formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo).startStrict(next: { [weak self] result in
) guard let self else {
self.pushState() return
}
if let index = self.gifts.firstIndex(where: { $0.messageId == messageId }) {
self.gifts[index] = result
self.pushState()
}
subscriber.putNext(result)
}, error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
})
)
return disposable
}
} }
private func pushState() { private func pushState() {
@ -1186,9 +1212,19 @@ public final class ProfileGiftsContext {
} }
} }
public func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) { public func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {
self.impl.with { impl in return Signal { subscriber in
impl.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo) let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo).start(next: { value in
subscriber.putNext(value)
}, error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
}))
}
return disposable
} }
} }

View File

@ -1418,12 +1418,13 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
case .starsChatSubscription: case .starsChatSubscription:
let chats = updates.chats.compactMap { parseTelegramGroupOrChannel(chat: $0) } let chats = updates.chats.compactMap { parseTelegramGroupOrChannel(chat: $0) }
if let first = chats.first { if let first = chats.first {
return .done(receiptMessageId: nil, subscriptionPeerId: first.id) return .done(receiptMessageId: nil, subscriptionPeerId: first.id, uniqueStarGift: nil)
} }
default: default:
break break
} }
var receiptMessageId: MessageId? var receiptMessageId: MessageId?
var resultGift: ProfileGiftsContext.State.StarGift?
for apiMessage in updates.messages { for apiMessage in updates.messages {
if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: account.peerId, peerIsForum: false) { if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: account.peerId, peerIsForum: false) {
for media in message.media { for media in message.media {
@ -1463,12 +1464,28 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer: case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer:
receiptMessageId = nil receiptMessageId = nil
} }
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _) = action.action, case let .Id(messageId) = message.id {
resultGift = ProfileGiftsContext.State.StarGift(
gift: gift,
fromPeer: nil,
date: message.timestamp,
text: nil,
entities: nil,
messageId: messageId,
nameHidden: false,
savedToProfile: savedToProfile,
convertStars: nil,
canUpgrade: false,
canExportDate: canExportDate,
upgradeStars: nil,
transferStars: transferStars
)
} }
} }
} }
} }
} }
return .done(receiptMessageId: receiptMessageId, subscriptionPeerId: nil) return .done(receiptMessageId: receiptMessageId, subscriptionPeerId: nil, uniqueStarGift: resultGift)
case let .paymentVerificationNeeded(url): case let .paymentVerificationNeeded(url):
return .externalVerificationRequired(url: url) return .externalVerificationRequired(url: url)
} }

View File

@ -1066,7 +1066,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = mutableString attributedString = mutableString
case .prizeStars: case .prizeStars:
attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor)
case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _): case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, _):
if !forAdditionalServiceMessage { if !forAdditionalServiceMessage {
if let text { if let text {
let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage())) let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage()))

View File

@ -467,7 +467,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View
hasServiceMessage = false hasServiceMessage = false
} }
case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, _, upgradeStars, isRefunded): case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, _, upgradeStars, isRefunded, _):
if case let .generic(gift) = gift { if case let .generic(gift) = gift {
isStarGift = true isStarGift = true
let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
@ -488,7 +488,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle_Displaying text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle_Displaying
} }
} else { } else {
if let convertStars { if let convertStars, convertStars > 0 {
text = item.presentationData.strings.Notification_StarGift_Subtitle(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars))).string text = item.presentationData.strings.Notification_StarGift_Subtitle(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars))).string
} else { } else {
text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle
@ -500,16 +500,20 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
peerName = EnginePeer(peer).compactDisplayTitle peerName = EnginePeer(peer).compactDisplayTitle
} }
if peerName.isEmpty { if peerName.isEmpty {
if let convertStars { if let convertStars, convertStars > 0 {
text = item.presentationData.strings.Notification_StarGift_Subtitle(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars))).string text = item.presentationData.strings.Notification_StarGift_Subtitle(item.presentationData.strings.Notification_StarGift_Subtitle_Stars(Int32(convertStars))).string
} else { } else {
text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle text = item.presentationData.strings.Notification_StarGift_Bot_Subtitle
} }
} else { } else {
let formattedString = item.presentationData.strings.Notification_StarGift_Subtitle_Other(peerName, item.presentationData.strings.Notification_StarGift_Subtitle_Other_Stars(Int32(convertStars ?? 0))) if let convertStars, convertStars > 0 {
text = formattedString.string let formattedString = item.presentationData.strings.Notification_StarGift_Subtitle_Other(peerName, item.presentationData.strings.Notification_StarGift_Subtitle_Other_Stars(Int32(convertStars)))
if let starsRange = formattedString.ranges.last { text = formattedString.string
entities.append(MessageTextEntity(range: starsRange.range.lowerBound ..< starsRange.range.upperBound, type: .Bold)) if let starsRange = formattedString.ranges.last {
entities.append(MessageTextEntity(range: starsRange.range.lowerBound ..< starsRange.range.upperBound, type: .Bold))
}
} else {
} }
} }
} }
@ -524,17 +528,22 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
ribbonTitle = item.presentationData.strings.Notification_StarGift_OneOf(availabilityString).string ribbonTitle = item.presentationData.strings.Notification_StarGift_OneOf(availabilityString).string
} }
if incoming, let upgradeStars, upgradeStars > 0, !upgraded { if incoming || item.presentationData.isPreview, let upgradeStars, upgradeStars > 0, !upgraded {
buttonTitle = item.presentationData.strings.Notification_StarGift_Unpack buttonTitle = item.presentationData.strings.Notification_StarGift_Unpack
buttonIcon = "Premium/GiftUnpack" buttonIcon = "Premium/GiftUnpack"
} else { } else {
buttonTitle = item.presentationData.strings.Notification_StarGift_View buttonTitle = item.presentationData.strings.Notification_StarGift_View
} }
} }
case let .starGiftUnique(gift, _, _, _, _, _, isRefunded): case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded):
if case let .unique(uniqueGift) = gift { if case let .unique(uniqueGift) = gift {
isStarGift = true isStarGift = true
let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" let authorName: String
if isUpgrade && item.message.author?.id == item.context.account.peerId {
authorName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
} else {
authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
}
title = item.presentationData.strings.Notification_StarGift_Title(authorName).string title = item.presentationData.strings.Notification_StarGift_Title(authorName).string
text = "**\(uniqueGift.title) #\(uniqueGift.number)**" text = "**\(uniqueGift.title) #\(uniqueGift.number)**"
ribbonTitle = item.presentationData.strings.Notification_StarGift_Gift ribbonTitle = item.presentationData.strings.Notification_StarGift_Gift

View File

@ -24,6 +24,9 @@ swift_library(
"//submodules/PresentationDataUtils", "//submodules/PresentationDataUtils",
"//submodules/TextFormat", "//submodules/TextFormat",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/TelegramUI/Components/EmojiTextAttachmentView",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
"//submodules/AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -13,15 +13,21 @@ public final class GiftAnimationComponent: Component {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let file: TelegramMediaFile? let file: TelegramMediaFile?
let still: Bool
let size: CGSize?
public init( public init(
context: AccountContext, context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
file: TelegramMediaFile? file: TelegramMediaFile?,
still: Bool = false,
size: CGSize? = nil
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.file = file self.file = file
self.still = still
self.size = size
} }
public static func ==(lhs: GiftAnimationComponent, rhs: GiftAnimationComponent) -> Bool { public static func ==(lhs: GiftAnimationComponent, rhs: GiftAnimationComponent) -> Bool {
@ -34,6 +40,12 @@ public final class GiftAnimationComponent: Component {
if lhs.file != rhs.file { if lhs.file != rhs.file {
return false return false
} }
if lhs.still != rhs.still {
return false
}
if lhs.size != rhs.size {
return false
}
return true return true
} }
@ -61,7 +73,7 @@ public final class GiftAnimationComponent: Component {
file: component.file file: component.file
) )
let iconSize = availableSize let iconSize = component.size ?? availableSize
if self.animationLayer == nil { if self.animationLayer == nil {
let animationLayer = InlineStickerItemLayer( let animationLayer = InlineStickerItemLayer(
context: .account(component.context), context: .account(component.context),
@ -71,12 +83,12 @@ public final class GiftAnimationComponent: Component {
file: component.file, file: component.file,
cache: component.context.animationCache, cache: component.context.animationCache,
renderer: component.context.animationRenderer, renderer: component.context.animationRenderer,
unique: true, unique: !component.still,
placeholderColor: component.theme.list.mediaPlaceholderColor, placeholderColor: component.theme.list.mediaPlaceholderColor,
pointSize: CGSize(width: iconSize.width * 1.2, height: iconSize.height * 1.2), pointSize: CGSize(width: iconSize.width * 1.2, height: iconSize.height * 1.2),
loopCount: 1 loopCount: component.still ? 0 : 1
) )
animationLayer.isVisibleForAnimations = true animationLayer.isVisibleForAnimations = !component.still
self.animationLayer = animationLayer self.animationLayer = animationLayer
self.layer.addSublayer(animationLayer) self.layer.addSublayer(animationLayer)
} }

View File

@ -13,7 +13,7 @@ import PeerInfoCoverComponent
import AnimatedStickerNode import AnimatedStickerNode
import TelegramAnimatedStickerNode import TelegramAnimatedStickerNode
final class GiftCompositionComponent: Component { public final class GiftCompositionComponent: Component {
public class ExternalState { public class ExternalState {
public fileprivate(set) var previewPatternColor: UIColor? public fileprivate(set) var previewPatternColor: UIColor?
public init() { public init() {
@ -21,7 +21,7 @@ final class GiftCompositionComponent: Component {
} }
} }
enum Subject: Equatable { public enum Subject: Equatable {
case generic(TelegramMediaFile) case generic(TelegramMediaFile)
case unique(StarGift.UniqueGift) case unique(StarGift.UniqueGift)
case preview([StarGift.UniqueGift.Attribute]) case preview([StarGift.UniqueGift.Attribute])
@ -30,15 +30,15 @@ final class GiftCompositionComponent: Component {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
let subject: Subject let subject: Subject
let externalState: ExternalState let externalState: ExternalState?
let requestUpdate: () -> Void let requestUpdate: () -> Void
init( public init(
context: AccountContext, context: AccountContext,
theme: PresentationTheme, theme: PresentationTheme,
subject: Subject, subject: Subject,
externalState: ExternalState, externalState: ExternalState? = nil,
requestUpdate: @escaping () -> Void requestUpdate: @escaping () -> Void = {}
) { ) {
self.context = context self.context = context
self.theme = theme self.theme = theme
@ -47,7 +47,7 @@ final class GiftCompositionComponent: Component {
self.requestUpdate = requestUpdate self.requestUpdate = requestUpdate
} }
static func ==(lhs: GiftCompositionComponent, rhs: GiftCompositionComponent) -> Bool { public static func ==(lhs: GiftCompositionComponent, rhs: GiftCompositionComponent) -> Bool {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
@ -60,7 +60,7 @@ final class GiftCompositionComponent: Component {
return true return true
} }
final class View: UIView { public final class View: UIView {
private var component: GiftCompositionComponent? private var component: GiftCompositionComponent?
private weak var componentState: EmptyComponentState? private weak var componentState: EmptyComponentState?
@ -84,6 +84,8 @@ final class GiftCompositionComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap)))
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -94,7 +96,16 @@ final class GiftCompositionComponent: Component {
self.disposables.dispose() self.disposables.dispose()
} }
func update(component: GiftCompositionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize { @objc private func handleTap() {
guard let animationNode = animationNode as? DefaultAnimatedStickerNodeImpl else {
return
}
if case .once = animationNode.playbackMode, !animationNode.isPlaying {
animationNode.playOnce()
}
}
public func update(component: GiftCompositionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let previousComponent = self.component let previousComponent = self.component
self.component = component self.component = component
@ -107,6 +118,7 @@ final class GiftCompositionComponent: Component {
var patternFile: TelegramMediaFile? var patternFile: TelegramMediaFile?
var files: [Int64: TelegramMediaFile] = [:] var files: [Int64: TelegramMediaFile] = [:]
var loop = false
switch component.subject { switch component.subject {
case let .generic(file): case let .generic(file):
animationFile = file animationFile = file
@ -142,6 +154,8 @@ final class GiftCompositionComponent: Component {
self.previewTimer = nil self.previewTimer = nil
} }
case let .preview(sampleAttributes): case let .preview(sampleAttributes):
loop = true
if self.previewModels.isEmpty { if self.previewModels.isEmpty {
var models: [StarGift.UniqueGift.Attribute] = [] var models: [StarGift.UniqueGift.Attribute] = []
var patterns: [StarGift.UniqueGift.Attribute] = [] var patterns: [StarGift.UniqueGift.Attribute] = []
@ -198,8 +212,21 @@ final class GiftCompositionComponent: Component {
return return
} }
self.previewModelIndex = (self.previewModelIndex + 1) % Int32(self.previewModels.count) self.previewModelIndex = (self.previewModelIndex + 1) % Int32(self.previewModels.count)
self.previewPatternIndex = (self.previewPatternIndex + 1) % Int32(self.previewPatterns.count)
self.previewBackdropIndex = (self.previewBackdropIndex + 1) % Int32(self.previewBackdrops.count) let previousPatternIndex = self.previewPatternIndex
var randomPatternIndex = previousPatternIndex
while randomPatternIndex == previousPatternIndex {
randomPatternIndex = Int32.random(in: 0 ..< Int32(self.previewPatterns.count))
}
self.previewPatternIndex = randomPatternIndex
let previousBackdropIndex = self.previewBackdropIndex
var randomBackdropIndex = previousBackdropIndex
while randomBackdropIndex == previousBackdropIndex {
randomBackdropIndex = Int32.random(in: 0 ..< Int32(self.previewBackdrops.count))
}
self.previewBackdropIndex = randomBackdropIndex
self.animatePreviewTransition = true self.animatePreviewTransition = true
self.componentState?.updated(transition: .easeInOut(duration: 0.25)) self.componentState?.updated(transition: .easeInOut(duration: 0.25))
}, queue: Queue.mainQueue()) }, queue: Queue.mainQueue())
@ -207,7 +234,7 @@ final class GiftCompositionComponent: Component {
} }
} }
component.externalState.previewPatternColor = secondBackgroundColor component.externalState?.previewPatternColor = secondBackgroundColor
var animateTransition = false var animateTransition = false
if self.animatePreviewTransition { if self.animatePreviewTransition {
@ -247,6 +274,7 @@ final class GiftCompositionComponent: Component {
if backgroundView.superview == nil { if backgroundView.superview == nil {
backgroundTransition = .immediate backgroundTransition = .immediate
backgroundView.clipsToBounds = true backgroundView.clipsToBounds = true
backgroundView.isUserInteractionEnabled = false
self.insertSubview(backgroundView, at: 0) self.insertSubview(backgroundView, at: 0)
backgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) backgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
@ -259,7 +287,7 @@ final class GiftCompositionComponent: Component {
}) })
} }
let iconSize = CGSize(width: 128.0, height: 128.0) let iconSize = CGSize(width: 136.0, height: 136.0)
var startFromIndex: Int? var startFromIndex: Int?
if animateTransition, let disappearingAnimationNode = self.animationNode { if animateTransition, let disappearingAnimationNode = self.animationNode {
@ -274,6 +302,7 @@ final class GiftCompositionComponent: Component {
let animationNode: AnimatedStickerNode let animationNode: AnimatedStickerNode
if self.animationNode == nil { if self.animationNode == nil {
animationNode = DefaultAnimatedStickerNodeImpl() animationNode = DefaultAnimatedStickerNodeImpl()
animationNode.isUserInteractionEnabled = false
self.animationNode = animationNode self.animationNode = animationNode
self.addSubview(animationNode.view) self.addSubview(animationNode.view)
@ -282,9 +311,16 @@ final class GiftCompositionComponent: Component {
animationNode.setup(source: AnimatedStickerResourceSource(account: component.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(iconSize.width * 1.6), height: Int(iconSize.height * 1.6), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix)) animationNode.setup(source: AnimatedStickerResourceSource(account: component.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(iconSize.width * 1.6), height: Int(iconSize.height * 1.6), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
if let startFromIndex { if let startFromIndex {
if let animationNode = animationNode as? DefaultAnimatedStickerNodeImpl {
animationNode.playbackMode = loop ? .loop : .once
}
animationNode.play(firstFrame: false, fromIndex: startFromIndex) animationNode.play(firstFrame: false, fromIndex: startFromIndex)
} else { } else {
animationNode.playLoop() if loop {
animationNode.playLoop()
} else {
animationNode.playOnce()
}
} }
animationNode.visibility = true animationNode.visibility = true
animationNode.updateLayout(size: iconSize) animationNode.updateLayout(size: iconSize)
@ -295,7 +331,7 @@ final class GiftCompositionComponent: Component {
} }
} }
if let animationNode = self.animationNode { if let animationNode = self.animationNode {
transition.setFrame(layer: animationNode.layer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: 25.0), size: iconSize)) transition.setFrame(layer: animationNode.layer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: 20.0), size: iconSize))
} }
return availableSize return availableSize

View File

@ -227,7 +227,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode {
case let .starGift(gift): case let .starGift(gift):
media = [ media = [
TelegramMediaAction( TelegramMediaAction(
action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: true, upgradeStars: item.includeUpgrade ? gift.upgradeStars : 0, isRefunded: false) action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, canUpgrade: true, upgradeStars: item.includeUpgrade ? 1 : nil, isRefunded: false, upgradeMessageId: nil)
) )
] ]
} }

View File

@ -139,7 +139,7 @@ final class GiftSetupScreenComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
self.scrollView = ScrollView() self.scrollView = ScrollView()
self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsVerticalScrollIndicator = false
self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.showsHorizontalScrollIndicator = false
self.scrollView.scrollsToTop = false self.scrollView.scrollsToTop = false
self.scrollView.delaysContentTouches = false self.scrollView.delaysContentTouches = false
@ -253,6 +253,10 @@ final class GiftSetupScreenComponent: Component {
|> deliverOnMainQueue).start(next: { [weak self] status in |> deliverOnMainQueue).start(next: { [weak self] status in
if let completion { if let completion {
completion() completion()
if let self, let controller = self.environment?.controller() {
controller.dismiss()
}
} else { } else {
guard let self, case .purchased = status, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else { guard let self, case .purchased = status, let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController else {
return return
@ -631,7 +635,6 @@ final class GiftSetupScreenComponent: Component {
transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame) transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame)
} }
let bottomContentInset: CGFloat = 24.0
let sideInset: CGFloat = 16.0 + environment.safeInsets.left let sideInset: CGFloat = 16.0 + environment.safeInsets.left
let sectionSpacing: CGFloat = 24.0 let sectionSpacing: CGFloat = 24.0
@ -842,8 +845,8 @@ final class GiftSetupScreenComponent: Component {
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor), body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor), bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor), link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor),
linkAttribute: { url in linkAttribute: { contents in
return ("URL", url) return (TelegramTextAttributes.URL, contents)
})) }))
let upgradeFooterText = NSMutableAttributedString(attributedString: parsedString) let upgradeFooterText = NSMutableAttributedString(attributedString: parsedString)
@ -986,20 +989,20 @@ final class GiftSetupScreenComponent: Component {
contentHeight += hideSectionSize.height contentHeight += hideSectionSize.height
} }
contentHeight += bottomContentInset contentHeight += 24.0
let combinedBottomInset = max(inputHeight, environment.safeInsets.bottom)
contentHeight += combinedBottomInset
if self.starImage == nil || self.starImage?.1 !== environment.theme {
self.starImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: environment.theme.list.itemCheckColors.foregroundColor)!, environment.theme)
}
let buttonHeight: CGFloat = 50.0 let buttonHeight: CGFloat = 50.0
let bottomPanelPadding: CGFloat = 12.0 let bottomPanelPadding: CGFloat = 12.0
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset let bottomPanelHeight = bottomPanelPadding + buttonHeight + bottomInset
let combinedBottomInset = max(inputHeight, environment.safeInsets.bottom)
contentHeight += max(bottomPanelHeight, combinedBottomInset)
if self.starImage == nil || self.starImage?.1 !== environment.theme {
self.starImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: environment.theme.list.itemCheckColors.foregroundColor)!, environment.theme)
}
let bottomPanelSize = self.buttonBackground.update( let bottomPanelSize = self.buttonBackground.update(
transition: transition, transition: transition,
component: AnyComponent(BlurredBackgroundComponent( component: AnyComponent(BlurredBackgroundComponent(

View File

@ -37,12 +37,9 @@ swift_library(
"//submodules/TelegramUI/Components/Stars/StarsAvatarComponent", "//submodules/TelegramUI/Components/Stars/StarsAvatarComponent",
"//submodules/TelegramUI/Components/EmojiTextAttachmentView", "//submodules/TelegramUI/Components/EmojiTextAttachmentView",
"//submodules/TelegramUI/Components/Gifts/GiftAnimationComponent", "//submodules/TelegramUI/Components/Gifts/GiftAnimationComponent",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
"//submodules/TelegramUI/Components/CheckComponent", "//submodules/TelegramUI/Components/CheckComponent",
"//submodules/UndoUI", "//submodules/UndoUI",
"//submodules/ConfettiEffect", "//submodules/ConfettiEffect",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
"//submodules/TooltipUI", "//submodules/TooltipUI",
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent", "//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
], ],

View File

@ -259,7 +259,7 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift.
let buttonText: String let buttonText: String
if transferStars > 0 { if transferStars > 0 {
text = strings.Gift_Transfer_Confirmation_Text("\(gift.title) #\(gift.number)", peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), strings.Gift_Transfer_Confirmation_Text_Stars(Int32(transferStars))).string text = strings.Gift_Transfer_Confirmation_Text("\(gift.title) #\(gift.number)", peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), strings.Gift_Transfer_Confirmation_Text_Stars(Int32(transferStars))).string
buttonText = "\(strings.Gift_Transfer_Confirmation_Transfer) ⭐️\(transferStars)" buttonText = "\(strings.Gift_Transfer_Confirmation_Transfer) $ \(transferStars)"
} else { } else {
text = strings.Gift_Transfer_Confirmation_TextFree("\(gift.title) #\(gift.number)", peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string text = strings.Gift_Transfer_Confirmation_TextFree("\(gift.title) #\(gift.number)", peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string
buttonText = strings.Gift_Transfer_Confirmation_TransferFree buttonText = strings.Gift_Transfer_Confirmation_TransferFree

View File

@ -27,6 +27,7 @@ import ConfettiEffect
import PlainButtonComponent import PlainButtonComponent
import CheckComponent import CheckComponent
import TooltipUI import TooltipUI
import GiftAnimationComponent
private let modelButtonTag = GenericComponentViewTag() private let modelButtonTag = GenericComponentViewTag()
private let backdropButtonTag = GenericComponentViewTag() private let backdropButtonTag = GenericComponentViewTag()
@ -45,6 +46,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let sendGift: (EnginePeer.Id) -> Void let sendGift: (EnginePeer.Id) -> Void
let openMyGifts: () -> Void let openMyGifts: () -> Void
let transferGift: () -> Void let transferGift: () -> Void
let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
let showAttributeInfo: (Any, Float) -> Void let showAttributeInfo: (Any, Float) -> Void
let getController: () -> ViewController? let getController: () -> ViewController?
@ -59,6 +61,7 @@ private final class GiftViewSheetContent: CombinedComponent {
sendGift: @escaping (EnginePeer.Id) -> Void, sendGift: @escaping (EnginePeer.Id) -> Void,
openMyGifts: @escaping () -> Void, openMyGifts: @escaping () -> Void,
transferGift: @escaping () -> Void, transferGift: @escaping () -> Void,
upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
showAttributeInfo: @escaping (Any, Float) -> Void, showAttributeInfo: @escaping (Any, Float) -> Void,
getController: @escaping () -> ViewController? getController: @escaping () -> ViewController?
) { ) {
@ -72,6 +75,7 @@ private final class GiftViewSheetContent: CombinedComponent {
self.sendGift = sendGift self.sendGift = sendGift
self.openMyGifts = openMyGifts self.openMyGifts = openMyGifts
self.transferGift = transferGift self.transferGift = transferGift
self.upgradeGift = upgradeGift
self.showAttributeInfo = showAttributeInfo self.showAttributeInfo = showAttributeInfo
self.getController = getController self.getController = getController
} }
@ -88,7 +92,8 @@ private final class GiftViewSheetContent: CombinedComponent {
final class State: ComponentState { final class State: ComponentState {
private let context: AccountContext private let context: AccountContext
var subject: GiftViewScreen.Subject private(set) var subject: GiftViewScreen.Subject
private let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
private let getController: () -> ViewController? private let getController: () -> ViewController?
private var disposable: Disposable? private var disposable: Disposable?
@ -109,6 +114,7 @@ private final class GiftViewSheetContent: CombinedComponent {
var inUpgradePreview = false var inUpgradePreview = false
var upgradeForm: BotPaymentForm? var upgradeForm: BotPaymentForm?
var upgradeFormDisposable: Disposable?
var upgradeDisposable: Disposable? var upgradeDisposable: Disposable?
var sampleGiftAttributes: [StarGift.UniqueGift.Attribute]? var sampleGiftAttributes: [StarGift.UniqueGift.Attribute]?
@ -130,14 +136,24 @@ private final class GiftViewSheetContent: CombinedComponent {
var upgradedMockBackgroundColor: UIColor = .white var upgradedMockBackgroundColor: UIColor = .white
var upgradedMockIcon: TelegramMediaFile? var upgradedMockIcon: TelegramMediaFile?
init(context: AccountContext, subject: GiftViewScreen.Subject, getController: @escaping () -> ViewController?) { init(
context: AccountContext,
subject: GiftViewScreen.Subject,
upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
getController: @escaping () -> ViewController?
) {
self.context = context self.context = context
self.subject = subject self.subject = subject
self.upgradeGift = upgradeGift
self.getController = getController self.getController = getController
super.init() super.init()
if let arguments = subject.arguments { if let arguments = subject.arguments {
if let upgradeStars = arguments.upgradeStars, upgradeStars > 0 {
self.keepOriginalInfo = true
}
var peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId] var peerIds: [EnginePeer.Id] = [arguments.peerId, context.account.peerId]
if let fromPeerId = arguments.fromPeerId, !peerIds.contains(fromPeerId) { if let fromPeerId = arguments.fromPeerId, !peerIds.contains(fromPeerId) {
peerIds.append(fromPeerId) peerIds.append(fromPeerId)
@ -177,7 +193,7 @@ private final class GiftViewSheetContent: CombinedComponent {
})) }))
if arguments.upgradeStars == nil, let messageId = arguments.messageId { if arguments.upgradeStars == nil, let messageId = arguments.messageId {
self.upgradeDisposable = (context.engine.payments.fetchBotPaymentForm(source: .starGiftUpgrade(keepOriginalInfo: false, messageId: messageId), themeParams: nil) self.upgradeFormDisposable = (context.engine.payments.fetchBotPaymentForm(source: .starGiftUpgrade(keepOriginalInfo: false, messageId: messageId), themeParams: nil)
|> deliverOnMainQueue).start(next: { [weak self] paymentForm in |> deliverOnMainQueue).start(next: { [weak self] paymentForm in
guard let self else { guard let self else {
return return
@ -237,11 +253,12 @@ private final class GiftViewSheetContent: CombinedComponent {
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
self.sampleDisposable.dispose() self.sampleDisposable.dispose()
self.upgradeFormDisposable?.dispose()
self.upgradeDisposable?.dispose() self.upgradeDisposable?.dispose()
} }
func requestUpgradePreview() { func requestUpgradePreview() {
guard let _ = self.subject.arguments?.upgradeStars else { guard let arguments = self.subject.arguments, arguments.canUpgrade || arguments.upgradeStars != nil else {
return return
} }
self.context.starsContext?.load(force: false) self.context.starsContext?.load(force: false)
@ -251,7 +268,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
func commitUpgrade() { func commitUpgrade() {
guard let arguments = self.subject.arguments, let messageId = arguments.messageId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { guard let arguments = self.subject.arguments, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else {
return return
} }
let peerId = arguments.peerId let peerId = arguments.peerId
@ -259,7 +276,7 @@ private final class GiftViewSheetContent: CombinedComponent {
self.inProgress = true self.inProgress = true
self.updated() self.updated()
let _ = (self.context.engine.payments.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: self.keepOriginalInfo) self.upgradeDisposable = (self.upgradeGift(formId, self.keepOriginalInfo)
|> deliverOnMainQueue).start(next: { [weak self] result in |> deliverOnMainQueue).start(next: { [weak self] result in
guard let self, let controller = self.getController() as? GiftViewScreen else { guard let self, let controller = self.getController() as? GiftViewScreen else {
return return
@ -307,7 +324,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
func makeState() -> State { func makeState() -> State {
return State(context: self.context, subject: self.subject, getController: self.getController) return State(context: self.context, subject: self.subject, upgradeGift: self.upgradeGift, getController: self.getController)
} }
static var body: Body { static var body: Body {
@ -379,8 +396,6 @@ private final class GiftViewSheetContent: CombinedComponent {
entities = arguments.entities entities = arguments.entities
limitTotal = gift.availability?.total limitTotal = gift.availability?.total
convertStars = arguments.convertStars convertStars = arguments.convertStars
incoming = arguments.incoming || arguments.peerId == component.context.account.peerId
savedToProfile = arguments.savedToProfile
converted = arguments.converted converted = arguments.converted
giftId = gift.id giftId = gift.id
date = arguments.date date = arguments.date
@ -395,6 +410,8 @@ private final class GiftViewSheetContent: CombinedComponent {
convertStars = nil convertStars = nil
uniqueGift = gift uniqueGift = gift
} }
savedToProfile = arguments.savedToProfile
incoming = arguments.incoming || arguments.peerId == component.context.account.peerId
nameHidden = arguments.nameHidden nameHidden = arguments.nameHidden
titleString = incoming ? strings.Gift_View_ReceivedTitle : strings.Gift_View_Title titleString = incoming ? strings.Gift_View_ReceivedTitle : strings.Gift_View_Title
} else { } else {
@ -748,8 +765,8 @@ private final class GiftViewSheetContent: CombinedComponent {
// originY -= 12.0 // originY -= 12.0
// } // }
let linkColor = theme.actionSheet.controlAccentColor
if !descriptionText.isEmpty { if !descriptionText.isEmpty {
let linkColor = theme.actionSheet.controlAccentColor
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme { if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme) state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Settings/TextArrowRight"), color: linkColor)!, theme)
} }
@ -767,7 +784,6 @@ private final class GiftViewSheetContent: CombinedComponent {
textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0) textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0)
textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor
} }
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents) return (TelegramTextAttributes.URL, contents)
}) })
@ -850,45 +866,65 @@ private final class GiftViewSheetContent: CombinedComponent {
if !soldOut { if !soldOut {
if let uniqueGift { if let uniqueGift {
if let peer = state.peerMap[uniqueGift.ownerPeerId] { if let peer = state.peerMap[uniqueGift.ownerPeerId] {
let ownerComponent = AnyComponent( let ownerComponent: AnyComponent<Empty>
HStack([ if let _ = subject.arguments?.transferStars {
AnyComponentWithIdentity( ownerComponent = AnyComponent(
id: AnyHashable(0), HStack([
component: AnyComponent(Button( AnyComponentWithIdentity(
content: AnyComponent( id: AnyHashable(0),
PeerCellComponent( component: AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
strings: strings,
peer: peer
)
),
action: {
component.openPeer(peer)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
))
),
AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: component.context, context: component.context,
theme: theme, text: strings.Gift_Unique_Transfer,
strings: strings, color: theme.list.itemAccentColor
peer: peer )),
) action: {
), component.transferGift()
action: { Queue.mainQueue().after(1.0, {
component.openPeer(peer) component.cancel(false)
Queue.mainQueue().after(1.0, { })
component.cancel(false) }
}) ))
} )
)) ], spacing: 4.0)
)
} else {
ownerComponent = AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
strings: strings,
peer: peer
)
), ),
AnyComponentWithIdentity( action: {
id: AnyHashable(1), component.openPeer(peer)
component: AnyComponent(Button( Queue.mainQueue().after(1.0, {
content: AnyComponent(ButtonContentComponent( component.cancel(false)
context: component.context, })
text: strings.Gift_Unique_Transfer, }
color: theme.list.itemAccentColor ))
)), }
action: {
component.transferGift()
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
))
)
], spacing: 4.0)
)
tableItems.append(.init( tableItems.append(.init(
id: "owner", id: "owner",
title: strings.Gift_Unique_Owner, title: strings.Gift_Unique_Owner,
@ -1042,13 +1078,22 @@ private final class GiftViewSheetContent: CombinedComponent {
let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSenderWithText(senderName!, recipientName, dateString, "") : strings.Gift_Unique_OriginalInfoWithText(recipientName, dateString, "") let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSenderWithText(senderName!, recipientName, dateString, "") : strings.Gift_Unique_OriginalInfoWithText(recipientName, dateString, "")
let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor) let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor)
string.replaceCharacters(in: format.ranges[format.ranges.count - 1].range, with: attributedText) string.replaceCharacters(in: format.ranges[format.ranges.count - 1].range, with: attributedText)
string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) if let _ = senderName {
string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range)
string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[1].range)
} else {
string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range)
}
value = string value = string
} else { } else {
let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSender(senderName!, recipientName, dateString) : strings.Gift_Unique_OriginalInfo(recipientName, dateString) let format = senderName != nil ? strings.Gift_Unique_OriginalInfoSender(senderName!, recipientName, dateString) : strings.Gift_Unique_OriginalInfo(recipientName, dateString)
let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor) let string = NSMutableAttributedString(string: format.string, font: tableFont, textColor: tableTextColor)
string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range) if let _ = senderName {
string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range)
string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[1].range)
} else {
string.addAttribute(NSAttributedString.Key.foregroundColor, value: tableLinkColor, range: format.ranges[0].range)
}
value = string value = string
} }
@ -1068,6 +1113,7 @@ private final class GiftViewSheetContent: CombinedComponent {
animationRenderer: component.context.animationRenderer, animationRenderer: component.context.animationRenderer,
placeholderColor: theme.list.mediaPlaceholderColor, placeholderColor: theme.list.mediaPlaceholderColor,
text: .plain(value), text: .plain(value),
horizontalAlignment: .center,
maximumNumberOfLines: 0, maximumNumberOfLines: 0,
handleSpoilers: true handleSpoilers: true
) )
@ -1281,64 +1327,65 @@ private final class GiftViewSheetContent: CombinedComponent {
.disappear(.default(alpha: true)) .disappear(.default(alpha: true))
) )
originY += table.size.height + 23.0 originY += table.size.height + 23.0
}
if incoming && !converted { if incoming && !converted && !upgraded && !showUpgradePreview {
if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme { let linkColor = theme.actionSheet.controlAccentColor
state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme) if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme {
} state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme)
let descriptionText: String
if savedToProfile {
descriptionText = strings.Gift_View_DisplayedInfoHide
} else if let upgradeStars, upgradeStars > 0 && !upgraded {
descriptionText = strings.Gift_View_HiddenInfoShow
} else {
descriptionText = strings.Gift_View_HiddenInfo
}
let textFont = Font.regular(13.0)
let textColor = theme.list.itemSecondaryTextColor
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
let attributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
}
originY -= 5.0
let additionalText = additionalText.update(
component: MultilineTextComponent(
text: .plain(attributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 5,
lineSpacing: 0.2,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { _, _ in
component.updateSavedToProfile(!savedToProfile)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(additionalText
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + additionalText.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += additionalText.size.height
originY += 16.0
} }
let descriptionText: String
if savedToProfile {
descriptionText = strings.Gift_View_DisplayedInfoHide
} else if let upgradeStars, upgradeStars > 0 && !upgraded {
descriptionText = strings.Gift_View_HiddenInfoShow
} else {
descriptionText = strings.Gift_View_HiddenInfo
}
let textFont = Font.regular(13.0)
let textColor = theme.list.itemSecondaryTextColor
let markdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: textFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: linkColor), linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
})
let attributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedSmallChevronImage?.0 {
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
}
originY -= 5.0
let additionalText = additionalText.update(
component: MultilineTextComponent(
text: .plain(attributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 5,
lineSpacing: 0.2,
highlightColor: linkColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { _, _ in
component.updateSavedToProfile(!savedToProfile)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
context.add(additionalText
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + additionalText.size.height / 2.0))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
originY += additionalText.size.height
originY += 16.0
} }
let buttonChild: _UpdatedChildComponent let buttonChild: _UpdatedChildComponent
@ -1480,6 +1527,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
let sendGift: (EnginePeer.Id) -> Void let sendGift: (EnginePeer.Id) -> Void
let openMyGifts: () -> Void let openMyGifts: () -> Void
let transferGift: () -> Void let transferGift: () -> Void
let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
let showAttributeInfo: (Any, Float) -> Void let showAttributeInfo: (Any, Float) -> Void
init( init(
@ -1492,6 +1540,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
sendGift: @escaping (EnginePeer.Id) -> Void, sendGift: @escaping (EnginePeer.Id) -> Void,
openMyGifts: @escaping () -> Void, openMyGifts: @escaping () -> Void,
transferGift: @escaping () -> Void, transferGift: @escaping () -> Void,
upgradeGift: @escaping ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>),
showAttributeInfo: @escaping (Any, Float) -> Void showAttributeInfo: @escaping (Any, Float) -> Void
) { ) {
self.context = context self.context = context
@ -1503,6 +1552,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
self.sendGift = sendGift self.sendGift = sendGift
self.openMyGifts = openMyGifts self.openMyGifts = openMyGifts
self.transferGift = transferGift self.transferGift = transferGift
self.upgradeGift = upgradeGift
self.showAttributeInfo = showAttributeInfo self.showAttributeInfo = showAttributeInfo
} }
@ -1550,6 +1600,7 @@ private final class GiftViewSheetComponent: CombinedComponent {
sendGift: context.component.sendGift, sendGift: context.component.sendGift,
openMyGifts: context.component.openMyGifts, openMyGifts: context.component.openMyGifts,
transferGift: context.component.transferGift, transferGift: context.component.transferGift,
upgradeGift: context.component.upgradeGift,
showAttributeInfo: context.component.showAttributeInfo, showAttributeInfo: context.component.showAttributeInfo,
getController: controller getController: controller
)), )),
@ -1629,20 +1680,20 @@ public class GiftViewScreen: ViewControllerComponentContainer {
case let .message(message): case let .message(message):
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction {
switch action.action { switch action.action {
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, _): case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, _, _):
return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, nil, nil) return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, nil, nil)
case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _): case let .starGiftUnique(gift, isUpgrade, _, savedToProfile, canExportDate, transferStars, _):
return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, message.flags.contains(.Incoming), gift, message.timestamp, nil, nil, nil, false, savedToProfile, false, true, false, nil, transferStars, canExportDate) var incoming = message.flags.contains(.Incoming)
if isUpgrade && message.author?.id != message.id.peerId {
incoming = true
}
return (message.id.peerId, message.author?.id, message.author?.compactDisplayTitle, message.id, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, false, false, false, nil, transferStars, canExportDate)
default: default:
return nil return nil
} }
} }
case let .profileGift(peerId, gift): case let .profileGift(peerId, gift):
var upgraded = false return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate)
if case .unique = gift.gift {
upgraded = true
}
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, gift.messageId, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, false, upgraded, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate)
case .soldOutGift: case .soldOutGift:
return nil return nil
case .upgradePreview: case .upgradePreview:
@ -1663,7 +1714,9 @@ public class GiftViewScreen: ViewControllerComponentContainer {
subject: GiftViewScreen.Subject, subject: GiftViewScreen.Subject,
forceDark: Bool = false, forceDark: Bool = false,
updateSavedToProfile: ((Bool) -> Void)? = nil, updateSavedToProfile: ((Bool) -> Void)? = nil,
convertToStars: (() -> Void)? = nil convertToStars: (() -> Void)? = nil,
transferGift: ((Bool, EnginePeer.Id) -> Void)? = nil,
upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? = nil
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -1676,6 +1729,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
var openMyGiftsImpl: (() -> Void)? var openMyGiftsImpl: (() -> Void)?
var transferGiftImpl: (() -> Void)? var transferGiftImpl: (() -> Void)?
var showAttributeInfoImpl: ((Any, Float) -> Void)? var showAttributeInfoImpl: ((Any, Float) -> Void)?
var upgradeGiftImpl: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)?
super.init( super.init(
context: context, context: context,
@ -1703,6 +1757,9 @@ public class GiftViewScreen: ViewControllerComponentContainer {
transferGift: { transferGift: {
transferGiftImpl?() transferGiftImpl?()
}, },
upgradeGift: { formId, keepOriginalInfo in
return upgradeGiftImpl?(formId, keepOriginalInfo) ?? .complete()
},
showAttributeInfo: { tag, rarity in showAttributeInfo: { tag, rarity in
showAttributeInfoImpl?(tag, rarity) showAttributeInfoImpl?(tag, rarity)
} }
@ -1737,6 +1794,20 @@ public class GiftViewScreen: ViewControllerComponentContainer {
guard let self, let arguments = self.subject.arguments, let messageId = arguments.messageId else { guard let self, let arguments = self.subject.arguments, let messageId = arguments.messageId else {
return return
} }
var animationFile: TelegramMediaFile?
switch arguments.gift {
case let .generic(gift):
animationFile = gift.file
case let .unique(gift):
for attribute in gift.attributes {
if case let .model(_, file, _) = attribute {
animationFile = file
break
}
}
}
if let updateSavedToProfile { if let updateSavedToProfile {
updateSavedToProfile(added) updateSavedToProfile(added)
} else { } else {
@ -1749,10 +1820,10 @@ public class GiftViewScreen: ViewControllerComponentContainer {
let text = added ? presentationData.strings.Gift_Displayed_NewText : presentationData.strings.Gift_Hidden_NewText let text = added ? presentationData.strings.Gift_Displayed_NewText : presentationData.strings.Gift_Hidden_NewText
if let navigationController = self.navigationController as? NavigationController { if let navigationController = self.navigationController as? NavigationController {
Queue.mainQueue().after(0.5) { Queue.mainQueue().after(0.5) {
if let lastController = navigationController.viewControllers.last as? ViewController, case let .generic(gift) = arguments.gift { if let lastController = navigationController.viewControllers.last as? ViewController, let animationFile {
let resultController = UndoOverlayController( let resultController = UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .sticker(context: context, file: gift.file, loop: false, title: nil, text: text, undoText: updateSavedToProfile == nil ? presentationData.strings.Gift_Displayed_View : nil, customAction: nil), content: .sticker(context: context, file: animationFile, loop: false, title: nil, text: text, undoText: updateSavedToProfile == nil ? presentationData.strings.Gift_Displayed_View : nil, customAction: nil),
elevatedLayout: lastController is ChatController, elevatedLayout: lastController is ChatController,
action: { [weak navigationController] action in action: { [weak navigationController] action in
if case .undo = action, let navigationController { if case .undo = action, let navigationController {
@ -1792,7 +1863,6 @@ public class GiftViewScreen: ViewControllerComponentContainer {
let starsConvertMaxDate = arguments.date + configuration.convertToStarsPeriod let starsConvertMaxDate = arguments.date + configuration.convertToStarsPeriod
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if currentTime > starsConvertMaxDate { if currentTime > starsConvertMaxDate {
let days: Int32 = Int32(ceil(Float(configuration.convertToStarsPeriod) / 86400.0)) let days: Int32 = Int32(ceil(Float(configuration.convertToStarsPeriod) / 86400.0))
let controller = textAlertController( let controller = textAlertController(
@ -1857,6 +1927,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
self.present(controller, in: .window(.root)) self.present(controller, in: .window(.root))
} }
} }
openStarsIntroImpl = { [weak self] in openStarsIntroImpl = { [weak self] in
guard let self else { guard let self else {
return return
@ -1864,6 +1935,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
let introController = context.sharedContext.makeStarsIntroScreen(context: context) let introController = context.sharedContext.makeStarsIntroScreen(context: context)
self.push(introController) self.push(introController)
} }
sendGiftImpl = { [weak self] peerId in sendGiftImpl = { [weak self] peerId in
guard let self else { guard let self else {
return return
@ -1876,6 +1948,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
self.push(controller) self.push(controller)
}) })
} }
openMyGiftsImpl = { [weak self] in openMyGiftsImpl = { [weak self] in
guard let self, let navigationController = self.navigationController as? NavigationController else { guard let self, let navigationController = self.navigationController as? NavigationController else {
return return
@ -1906,11 +1979,42 @@ public class GiftViewScreen: ViewControllerComponentContainer {
let _ = (context.account.stateManager.contactBirthdays let _ = (context.account.stateManager.contactBirthdays
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { birthdays in |> deliverOnMainQueue).start(next: { birthdays in
let controller = context.sharedContext.makePremiumGiftController(context: context, source: .starGiftTransfer(birthdays, messageId, gift, transferStars, arguments.canExportDate), completion: nil) let controller = context.sharedContext.makePremiumGiftController(context: context, source: .starGiftTransfer(birthdays, messageId, gift, transferStars, arguments.canExportDate), completion: { peerIds in
guard let peerId = peerIds.first else {
return
}
if let transferGift {
transferGift(transferStars == 0, peerId)
} else {
let _ = (context.engine.payments.transferStarGift(prepaid: transferStars == 0, messageId: messageId, peerId: peerId)
|> deliverOnMainQueue).start()
}
Queue.mainQueue().after(1.0, {
if transferStars > 0 {
context.starsContext?.load(force: true)
}
})
})
navigationController.pushViewController(controller) navigationController.pushViewController(controller)
}) })
} }
upgradeGiftImpl = { [weak self] formId, keepOriginalInfo in
guard let self, let arguments = self.subject.arguments, let messageId = arguments.messageId else {
return .complete()
}
if let upgradeGift {
return upgradeGift(formId, keepOriginalInfo)
} else {
return self.context.engine.payments.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo)
|> afterCompleted {
if formId != nil {
context.starsContext?.load(force: true)
}
}
}
}
showAttributeInfoImpl = { [weak self] tag, rarity in showAttributeInfoImpl = { [weak self] tag, rarity in
guard let self else { guard let self else {
return return
@ -1950,6 +2054,17 @@ public class GiftViewScreen: ViewControllerComponentContainer {
fileprivate func animateSuccess() { fileprivate func animateSuccess() {
self.navigationController?.view.addSubview(ConfettiView(frame: self.view.bounds)) self.navigationController?.view.addSubview(ConfettiView(frame: self.view.bounds))
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
self.present(UndoOverlayController(presentationData: presentationData, content: .universal(
animation: "GiftUpgraded",
scale: 0.066,
colors: [:],
title: presentationData.strings.Gift_Upgrade_Succeed_Title,
text: presentationData.strings.Gift_Upgrade_Succeed_Text,
customUndoText: nil,
timeout: 4.0
), elevatedLayout: false, position: .bottom, action: { _ in return true }), in: .current)
} }
public func dismissAnimated() { public func dismissAnimated() {

View File

@ -351,10 +351,21 @@ public final class PeerInfoCoverComponent: Component {
self.backgroundView.backgroundColor = secondaryBackgroundColor self.backgroundView.backgroundColor = secondaryBackgroundColor
self.backgroundGradientLayer.type = .axial if case .custom = component.subject {
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0) if availableSize.width < availableSize.height {
self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0) self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.25)
self.backgroundGradientLayer.colors = [backgroundColor.cgColor, secondaryBackgroundColor.cgColor] } else {
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
}
self.backgroundGradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
self.backgroundGradientLayer.type = .radial
self.backgroundGradientLayer.colors = [secondaryBackgroundColor.cgColor, backgroundColor.cgColor]
} else {
self.backgroundGradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
self.backgroundGradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
self.backgroundGradientLayer.type = .axial
self.backgroundGradientLayer.colors = [backgroundColor.cgColor, secondaryBackgroundColor.cgColor]
}
self.backgroundGradientLayer.anchorPoint = CGPoint(x: 0.0, y: 1.0) self.backgroundGradientLayer.anchorPoint = CGPoint(x: 0.0, y: 1.0)
let gradientHeight: CGFloat = component.defaultHeight let gradientHeight: CGFloat = component.defaultHeight
@ -431,7 +442,7 @@ public final class PeerInfoCoverComponent: Component {
} else if availableSize.width < 150.0 { } else if availableSize.width < 150.0 {
baseDistance *= 0.6 baseDistance *= 0.6
baseRowDistance *= 0.6 baseRowDistance *= 0.6
baseItemSize *= 0.75 baseItemSize *= 0.83
} }
var avatarBackgroundPatternLayerCount = 0 var avatarBackgroundPatternLayerCount = 0

View File

@ -1306,7 +1306,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var nextIconX: CGFloat = titleSize.width var nextIconX: CGFloat = titleSize.width
var nextExpandedIconX: CGFloat = titleExpandedSize.width var nextExpandedIconX: CGFloat = titleExpandedSize.width
if let credibilityIconSize = self.credibilityIconSize, let titleExpandedCredibilityIconSize = self.titleExpandedCredibilityIconSize { if let credibilityIconSize = self.credibilityIconSize, let titleExpandedCredibilityIconSize = self.titleExpandedCredibilityIconSize, credibilityIconSize.width > 0.0 {
let offset = (credibilityIconSize.width + 4.0) / 2.0 let offset = (credibilityIconSize.width + 4.0) / 2.0
let leftOffset: CGFloat = nextIconX + 4.0 let leftOffset: CGFloat = nextIconX + 4.0
@ -1325,13 +1325,21 @@ final class PeerInfoHeaderNode: ASDisplayNode {
nextExpandedIconX += 4.0 + titleExpandedCredibilityIconSize.width nextExpandedIconX += 4.0 + titleExpandedCredibilityIconSize.width
} }
if let verifiedIconSize = self.verifiedIconSize, let titleExpandedVerifiedIconSize = self.titleExpandedVerifiedIconSize { if let verifiedIconSize = self.verifiedIconSize, let titleExpandedVerifiedIconSize = self.titleExpandedVerifiedIconSize, verifiedIconSize.width > 0.0 {
let offset = (verifiedIconSize.width + 4.0) / 2.0 let leftOffset: CGFloat
titleHorizontalOffset += offset let leftExpandedOffset: CGFloat
titleExpandedHorizontalOffset += offset if case .verified = verifiedIcon {
titleHorizontalOffset -= (verifiedIconSize.width + 4.0) / 2.0
let leftOffset: CGFloat = -verifiedIconSize.width - 4.0 leftOffset = nextIconX + 4.0
let leftExpandedOffset: CGFloat = -titleExpandedVerifiedIconSize.width - 4.0 leftExpandedOffset = nextExpandedIconX + 4.0
} else {
titleHorizontalOffset += (verifiedIconSize.width + 4.0) / 2.0
titleExpandedHorizontalOffset += titleExpandedVerifiedIconSize.width
leftOffset = -verifiedIconSize.width - 4.0
leftExpandedOffset = -titleExpandedVerifiedIconSize.width - 8.0
}
var collapsedTransitionOffset: CGFloat = 0.0 var collapsedTransitionOffset: CGFloat = 0.0
if let navigationTransition = self.navigationTransition { if let navigationTransition = self.navigationTransition {
@ -1340,6 +1348,11 @@ final class PeerInfoHeaderNode: ASDisplayNode {
transition.updateFrame(view: self.titleVerifiedIconView, frame: CGRect(origin: CGPoint(x: leftOffset + collapsedTransitionOffset, y: floor((titleSize.height - verifiedIconSize.height) / 2.0)), size: verifiedIconSize)) transition.updateFrame(view: self.titleVerifiedIconView, frame: CGRect(origin: CGPoint(x: leftOffset + collapsedTransitionOffset, y: floor((titleSize.height - verifiedIconSize.height) / 2.0)), size: verifiedIconSize))
transition.updateFrame(view: self.titleExpandedVerifiedIconView, frame: CGRect(origin: CGPoint(x: leftExpandedOffset, y: floor((titleExpandedSize.height - titleExpandedVerifiedIconSize.height) / 2.0) + 1.0), size: titleExpandedVerifiedIconSize)) transition.updateFrame(view: self.titleExpandedVerifiedIconView, frame: CGRect(origin: CGPoint(x: leftExpandedOffset, y: floor((titleExpandedSize.height - titleExpandedVerifiedIconSize.height) / 2.0) + 1.0), size: titleExpandedVerifiedIconSize))
if case .verified = verifiedIcon {
nextIconX += 4.0 + verifiedIconSize.width
nextExpandedIconX += 4.0 + titleExpandedVerifiedIconSize.width
}
} }
var titleFrame: CGRect var titleFrame: CGRect
@ -1759,7 +1772,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var titleFrame = titleFrame var titleFrame = titleFrame
if !self.isAvatarExpanded { if !self.isAvatarExpanded {
titleFrame = titleFrame.offsetBy(dx: self.isAvatarExpanded ? titleExpandedHorizontalOffset : titleHorizontalOffset * titleScale, dy: 0.0) titleFrame = titleFrame.offsetBy(dx: titleHorizontalOffset * titleScale, dy: 0.0)
} else {
titleFrame = titleFrame.offsetBy(dx: titleExpandedHorizontalOffset, dy: 0.0)
} }
let titleCenter = CGPoint(x: transitionFraction * transitionSourceTitleFrame.midX + (1.0 - transitionFraction) * titleFrame.midX, y: transitionFraction * transitionSourceTitleFrame.midY + (1.0 - transitionFraction) * titleFrame.midY) let titleCenter = CGPoint(x: transitionFraction * transitionSourceTitleFrame.midX + (1.0 - transitionFraction) * titleFrame.midX, y: transitionFraction * transitionSourceTitleFrame.midY + (1.0 - transitionFraction) * titleFrame.midY)

View File

@ -3986,8 +3986,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self?.controller?.updateProfilePhoto(image, mode: .generic) self?.controller?.updateProfilePhoto(image, mode: .generic)
} }
galleryController.avatarVideoEditCompletion = { [weak self] image, asset, adjustments in galleryController.avatarVideoEditCompletion = { [weak self] image, asset, adjustments in
let _ = self self?.controller?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: .generic)
//self?.controller?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: .generic)
} }
galleryController.removedEntry = { [weak self] entry in galleryController.removedEntry = { [weak self] entry in
if let item = PeerInfoAvatarListItem(entry: entry) { if let item = PeerInfoAvatarListItem(entry: entry) {
@ -9605,7 +9604,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
} }
fileprivate func oldOpenAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) { func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) {
guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else { guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else {
return return
} }
@ -9694,21 +9693,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings || strongSelf.isMyProfile, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))! let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings || strongSelf.isMyProfile, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))!
mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context) mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context)
let _ = strongSelf.currentAvatarMixin.swap(mixin) let _ = strongSelf.currentAvatarMixin.swap(mixin)
// mixin.requestSearchController = { [weak self, weak parentController] assetsController in
// guard let strongSelf = self else {
// return
// }
// let controller = WebSearchController(context: strongSelf.context, updatedPresentationData: strongSelf.controller?.updatedPresentationData, peer: peer, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: (strongSelf.isSettings || strongSelf.isMyProfile) ? nil : peer.compactDisplayTitle, completion: { [weak self] result in
// assetsController?.dismiss()
// self?.updateProfilePhoto(result, mode: mode)
// }))
// controller.navigationPresentation = .modal
// parentController?.push(controller)
//
// if fromGallery {
// completion(nil)
// }
// }
var isFromEditor = false var isFromEditor = false
mixin.requestAvatarEditor = { [weak self, weak parentController] imageCompletion, videoCompletion in mixin.requestAvatarEditor = { [weak self, weak parentController] imageCompletion, videoCompletion in
guard let strongSelf = self, let imageCompletion, let videoCompletion else { guard let strongSelf = self, let imageCompletion, let videoCompletion else {
@ -9755,18 +9739,18 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
} }
} }
// mixin.didFinishWithImage = { [weak self] image in mixin.didFinishWithImage = { [weak self] image in
// if let image = image { if let image = image {
// completion(image) completion(image)
// self?.updateProfilePhoto(image, mode: mode) self?.controller?.updateProfilePhoto(image, mode: mode)
// } }
// } }
// mixin.didFinishWithVideo = { [weak self] image, asset, adjustments in mixin.didFinishWithVideo = { [weak self] image, asset, adjustments in
// if let image = image, let asset = asset { if let image = image, let asset = asset {
// completion(image) completion(image)
// self?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: mode) self?.controller?.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: mode)
// } }
// } }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -12767,6 +12751,10 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
} }
} }
func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) {
self.controllerNode.openAvatarForEditing(mode: mode, fromGallery: fromGallery, completion: completion)
}
static func openPeer(context: AccountContext, peerId: PeerId, navigation: ChatControllerInteractionNavigateToPeer, navigationController: NavigationController) { static func openPeer(context: AccountContext, peerId: PeerId, navigation: ChatControllerInteractionNavigateToPeer, navigationController: NavigationController) {
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).startStandalone(next: { peer in |> deliverOnMainQueue).startStandalone(next: { peer in

View File

@ -15,166 +15,167 @@ import OverlayStatusController
import UndoUI import UndoUI
import PeerAvatarGalleryUI import PeerAvatarGalleryUI
import PresentationDataUtils import PresentationDataUtils
import LegacyComponents
extension PeerInfoScreenImpl { extension PeerInfoScreenImpl {
func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) { // func newopenAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) {
guard let data = self.controllerNode.data, let peer = data.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: data.threadData) else { // guard let data = self.controllerNode.data, let peer = data.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: data.threadData) else {
return // return
} // }
self.view.endEditing(true) // self.view.endEditing(true)
//
let peerId = self.peerId // let peerId = self.peerId
var isForum = false // var isForum = false
if let peer = peer as? TelegramChannel, peer.flags.contains(.isForum) { // if let peer = peer as? TelegramChannel, peer.flags.contains(.isForum) {
isForum = true // isForum = true
} // }
//
var currentIsVideo = false // var currentIsVideo = false
var emojiMarkup: TelegramMediaImage.EmojiMarkup? // var emojiMarkup: TelegramMediaImage.EmojiMarkup?
let item = self.controllerNode.headerNode.avatarListNode.listContainerNode.currentItemNode?.item // let item = self.controllerNode.headerNode.avatarListNode.listContainerNode.currentItemNode?.item
if let item = item, case let .image(_, _, videoRepresentations, _, _, emojiMarkupValue) = item { // if let item = item, case let .image(_, _, videoRepresentations, _, _, emojiMarkupValue) = item {
currentIsVideo = !videoRepresentations.isEmpty // currentIsVideo = !videoRepresentations.isEmpty
emojiMarkup = emojiMarkupValue // emojiMarkup = emojiMarkupValue
} // }
//
let _ = isForum // let _ = isForum
let _ = currentIsVideo // let _ = currentIsVideo
//
let _ = (self.context.engine.data.get( // let _ = (self.context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) // TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
) // )
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in // |> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
guard let self, let peer else { // guard let self, let peer else {
return // return
} // }
//
let keyboardInputData = Promise<AvatarKeyboardInputData>() // let keyboardInputData = Promise<AvatarKeyboardInputData>()
keyboardInputData.set(AvatarEditorScreen.inputData(context: self.context, isGroup: peer.id.namespace != Namespaces.Peer.CloudUser)) // keyboardInputData.set(AvatarEditorScreen.inputData(context: self.context, isGroup: peer.id.namespace != Namespaces.Peer.CloudUser))
//
var hasPhotos = false // var hasPhotos = false
if !peer.profileImageRepresentations.isEmpty { // if !peer.profileImageRepresentations.isEmpty {
hasPhotos = true // hasPhotos = true
} // }
//
var hasDeleteButton = false // var hasDeleteButton = false
if case .generic = mode { // if case .generic = mode {
hasDeleteButton = hasPhotos && !fromGallery // hasDeleteButton = hasPhotos && !fromGallery
} else if case .custom = mode { // } else if case .custom = mode {
hasDeleteButton = peer.profileImageRepresentations.first?.isPersonal == true // hasDeleteButton = peer.profileImageRepresentations.first?.isPersonal == true
} else if case .fallback = mode { // } else if case .fallback = mode {
if let cachedData = data.cachedData as? CachedUserData, case let .known(photo) = cachedData.fallbackPhoto { // if let cachedData = data.cachedData as? CachedUserData, case let .known(photo) = cachedData.fallbackPhoto {
hasDeleteButton = photo != nil // hasDeleteButton = photo != nil
} // }
} // }
//
let _ = hasDeleteButton // let _ = hasDeleteButton
//
let parentController = (self.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController // let parentController = (self.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController
//
var dismissImpl: (() -> Void)? // var dismissImpl: (() -> Void)?
let mainController = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in // let mainController = self.context.sharedContext.makeAvatarMediaPickerScreen(context: self.context, getSourceRect: { return nil }, canDelete: hasDeleteButton, performDelete: { [weak self] in
self?.openAvatarRemoval(mode: mode, peer: peer, item: item) // self?.openAvatarRemoval(mode: mode, peer: peer, item: item)
}, completion: { result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in // }, completion: { result, transitionView, transitionRect, transitionImage, fromCamera, transitionOut, cancelled in
let subject: Signal<MediaEditorScreenImpl.Subject?, NoError> // let subject: Signal<MediaEditorScreenImpl.Subject?, NoError>
if let asset = result as? PHAsset { // if let asset = result as? PHAsset {
subject = .single(.asset(asset)) // subject = .single(.asset(asset))
} else if let image = result as? UIImage { // } else if let image = result as? UIImage {
subject = .single(.image(image: image, dimensions: PixelDimensions(image.size), additionalImage: nil, additionalImagePosition: .bottomRight)) // subject = .single(.image(image: image, dimensions: PixelDimensions(image.size), additionalImage: nil, additionalImagePosition: .bottomRight))
} else if let result = result as? Signal<CameraScreenImpl.Result, NoError> { // } else if let result = result as? Signal<CameraScreenImpl.Result, NoError> {
subject = result // subject = result
|> map { value -> MediaEditorScreenImpl.Subject? in // |> map { value -> MediaEditorScreenImpl.Subject? in
switch value { // switch value {
case .pendingImage: // case .pendingImage:
return nil // return nil
case let .image(image): // case let .image(image):
return .image(image: image.image, dimensions: PixelDimensions(image.image.size), additionalImage: nil, additionalImagePosition: .topLeft) // return .image(image: image.image, dimensions: PixelDimensions(image.image.size), additionalImage: nil, additionalImagePosition: .topLeft)
case let .video(video): // case let .video(video):
return .video(videoPath: video.videoPath, thumbnail: video.coverImage, mirror: video.mirror, additionalVideoPath: nil, additionalThumbnail: nil, dimensions: video.dimensions, duration: video.duration, videoPositionChanges: [], additionalVideoPosition: .topLeft) // return .video(videoPath: video.videoPath, thumbnail: video.coverImage, mirror: video.mirror, additionalVideoPath: nil, additionalThumbnail: nil, dimensions: video.dimensions, duration: video.duration, videoPositionChanges: [], additionalVideoPosition: .topLeft)
default: // default:
return nil // return nil
} // }
} // }
} else { // } else {
let peerType: AvatarEditorScreen.PeerType // let peerType: AvatarEditorScreen.PeerType
if mode == .suggest { // if mode == .suggest {
peerType = .suggest // peerType = .suggest
} else if case .legacyGroup = peer { // } else if case .legacyGroup = peer {
peerType = .group // peerType = .group
} else if case let .channel(channel) = peer { // } else if case let .channel(channel) = peer {
if case .group = channel.info { // if case .group = channel.info {
peerType = channel.flags.contains(.isForum) ? .forum : .group // peerType = channel.flags.contains(.isForum) ? .forum : .group
} else { // } else {
peerType = .channel // peerType = .channel
} // }
} else { // } else {
peerType = .user // peerType = .user
} // }
let controller = AvatarEditorScreen(context: self.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup) // let controller = AvatarEditorScreen(context: self.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup)
//controller.imageCompletion = imageCompletion // //controller.imageCompletion = imageCompletion
//controller.videoCompletion = videoCompletion // //controller.videoCompletion = videoCompletion
parentController?.push(controller) // parentController?.push(controller)
//isFromEditor = true // //isFromEditor = true
return // return
} // }
//
let editorController = MediaEditorScreenImpl( // let editorController = MediaEditorScreenImpl(
context: self.context, // context: self.context,
mode: .avatarEditor, // mode: .avatarEditor,
subject: subject, // subject: subject,
transitionIn: fromCamera ? .camera : transitionView.flatMap({ .gallery( // transitionIn: fromCamera ? .camera : transitionView.flatMap({ .gallery(
MediaEditorScreenImpl.TransitionIn.GalleryTransitionIn( // MediaEditorScreenImpl.TransitionIn.GalleryTransitionIn(
sourceView: $0, // sourceView: $0,
sourceRect: transitionRect, // sourceRect: transitionRect,
sourceImage: transitionImage // sourceImage: transitionImage
) // )
) }), // ) }),
transitionOut: { finished, isNew in // transitionOut: { finished, isNew in
if !finished, let transitionView { // if !finished, let transitionView {
return MediaEditorScreenImpl.TransitionOut( // return MediaEditorScreenImpl.TransitionOut(
destinationView: transitionView, // destinationView: transitionView,
destinationRect: transitionView.bounds, // destinationRect: transitionView.bounds,
destinationCornerRadius: 0.0 // destinationCornerRadius: 0.0
) // )
} // }
return nil // return nil
}, completion: { [weak self] result, commit in // }, completion: { [weak self] result, commit in
dismissImpl?() // dismissImpl?()
//
switch result.media { // switch result.media {
case let .image(image, _): // case let .image(image, _):
self?.updateProfilePhoto(image, mode: mode) // self?.updateProfilePhoto(image, mode: mode)
commit({}) // commit({})
case let .video(video, coverImage, values, _, _): // case let .video(video, coverImage, values, _, _):
if let coverImage { // if let coverImage {
self?.updateProfileVideo(coverImage, asset: video, adjustments: values, mode: mode) // self?.updateProfileVideo(coverImage, asset: video, adjustments: values, mode: mode)
} // }
commit({}) // commit({})
default: // default:
break // break
} // }
} as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void // } as (MediaEditorScreenImpl.Result, @escaping (@escaping () -> Void) -> Void) -> Void
) // )
editorController.cancelled = { _ in // editorController.cancelled = { _ in
cancelled() // cancelled()
} // }
self.push(editorController) // self.push(editorController)
}, dismissed: { // }, dismissed: {
//
}) // })
dismissImpl = { [weak mainController] in // dismissImpl = { [weak mainController] in
if let mainController, let navigationController = mainController.navigationController { // if let mainController, let navigationController = mainController.navigationController {
var viewControllers = navigationController.viewControllers // var viewControllers = navigationController.viewControllers
viewControllers = viewControllers.filter { c in // viewControllers = viewControllers.filter { c in
return !(c is CameraScreen) && c !== mainController // return !(c is CameraScreen) && c !== mainController
} // }
navigationController.setViewControllers(viewControllers, animated: false) // navigationController.setViewControllers(viewControllers, animated: false)
} // }
} // }
mainController.navigationPresentation = .flatModal // mainController.navigationPresentation = .flatModal
mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) // mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.push(mainController) // self.push(mainController)
}) // })
} // }
func openAvatarRemoval(mode: PeerInfoAvatarEditingMode, peer: EnginePeer? = nil, item: PeerInfoAvatarListItem? = nil, completion: @escaping () -> Void = {}) { func openAvatarRemoval(mode: PeerInfoAvatarEditingMode, peer: EnginePeer? = nil, item: PeerInfoAvatarListItem? = nil, completion: @escaping () -> Void = {}) {
let proceed = { [weak self] in let proceed = { [weak self] in
@ -368,7 +369,7 @@ extension PeerInfoScreenImpl {
})) }))
} }
public func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: MediaEditorValues?, mode: PeerInfoAvatarEditingMode) { public func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, mode: PeerInfoAvatarEditingMode) {
guard let data = image.jpegData(compressionQuality: 0.6) else { guard let data = image.jpegData(compressionQuality: 0.6) else {
return return
} }

View File

@ -234,6 +234,18 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
return return
} }
self.profileGifts.convertStarGift(messageId: messageId) self.profileGifts.convertStarGift(messageId: messageId)
},
transferGift: { [weak self] prepaid, peerId in
guard let self, let messageId = product.messageId else {
return
}
self.profileGifts.transferStarGift(prepaid: prepaid, messageId: messageId, peerId: peerId)
},
upgradeGift: { [weak self] formId, keepOriginalInfo in
guard let self, let messageId = product.messageId else {
return .never()
}
return self.profileGifts.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo)
} }
) )
self.parentController?.push(controller) self.parentController?.push(controller)

View File

@ -18,15 +18,26 @@ public final class StarsAvatarComponent: Component {
let peer: StarsContext.State.Transaction.Peer? let peer: StarsContext.State.Transaction.Peer?
let photo: TelegramMediaWebFile? let photo: TelegramMediaWebFile?
let media: [Media] let media: [Media]
let uniqueGift: StarGift.UniqueGift?
let backgroundColor: UIColor let backgroundColor: UIColor
let size: CGSize? let size: CGSize?
public init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer?, photo: TelegramMediaWebFile?, media: [Media], backgroundColor: UIColor, size: CGSize? = nil) { public init(
context: AccountContext,
theme: PresentationTheme,
peer: StarsContext.State.Transaction.Peer?,
photo: TelegramMediaWebFile?,
media: [Media],
uniqueGift: StarGift.UniqueGift?,
backgroundColor: UIColor,
size: CGSize? = nil
) {
self.context = context self.context = context
self.theme = theme self.theme = theme
self.peer = peer self.peer = peer
self.photo = photo self.photo = photo
self.media = media self.media = media
self.uniqueGift = uniqueGift
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.size = size self.size = size
} }
@ -47,6 +58,9 @@ public final class StarsAvatarComponent: Component {
if !areMediaArraysEqual(lhs.media, rhs.media) { if !areMediaArraysEqual(lhs.media, rhs.media) {
return false return false
} }
if lhs.uniqueGift != rhs.uniqueGift {
return false
}
if lhs.backgroundColor != rhs.backgroundColor { if lhs.backgroundColor != rhs.backgroundColor {
return false return false
} }

View File

@ -39,6 +39,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let openAppExamples: () -> Void let openAppExamples: () -> Void
let copyTransactionId: (String) -> Void let copyTransactionId: (String) -> Void
let updateSubscription: () -> Void let updateSubscription: () -> Void
let sendGift: (EnginePeer.Id) -> Void
init( init(
context: AccountContext, context: AccountContext,
@ -49,7 +50,8 @@ 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 () -> Void updateSubscription: @escaping () -> Void,
sendGift: @escaping (EnginePeer.Id) -> Void
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -60,6 +62,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
self.openAppExamples = openAppExamples self.openAppExamples = openAppExamples
self.copyTransactionId = copyTransactionId self.copyTransactionId = copyTransactionId
self.updateSubscription = updateSubscription self.updateSubscription = updateSubscription
self.sendGift = sendGift
} }
static func ==(lhs: StarsTransactionSheetContent, rhs: StarsTransactionSheetContent) -> Bool { static func ==(lhs: StarsTransactionSheetContent, rhs: StarsTransactionSheetContent) -> Bool {
@ -80,6 +83,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
var peerMap: [EnginePeer.Id: EnginePeer] = [:] var peerMap: [EnginePeer.Id: EnginePeer] = [:]
var cachedCloseImage: (UIImage, PresentationTheme)? var cachedCloseImage: (UIImage, PresentationTheme)?
var cachedOverlayCloseImage: UIImage?
var cachedChevronImage: (UIImage, PresentationTheme)? var cachedChevronImage: (UIImage, PresentationTheme)?
var inProgress = false var inProgress = false
@ -149,7 +153,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let title = Child(MultilineTextComponent.self) let title = Child(MultilineTextComponent.self)
let star = Child(StarsImageComponent.self) let star = Child(StarsImageComponent.self)
let activeStar = Child(PremiumStarComponent.self) let activeStar = Child(PremiumStarComponent.self)
let gift = Child(GiftAnimationComponent.self) let gift = Child(GiftCompositionComponent.self)
let amountBackground = Child(RoundedRectangle.self) let amountBackground = Child(RoundedRectangle.self)
let amount = Child(BalancedTextComponent.self) let amount = Child(BalancedTextComponent.self)
let amountStar = Child(BundleIconComponent.self) let amountStar = Child(BundleIconComponent.self)
@ -188,16 +192,13 @@ private final class StarsTransactionSheetContent: CombinedComponent {
state.cachedCloseImage = (closeImage, theme) state.cachedCloseImage = (closeImage, theme)
} }
let closeButton = closeButton.update( let closeOverlayImage: UIImage
component: Button( if let image = state.cachedOverlayCloseImage {
content: AnyComponent(Image(image: closeImage)), closeOverlayImage = image
action: { [weak component] in } else {
component?.cancel(true) closeOverlayImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.1), foregroundColor: .white)!
} state.cachedOverlayCloseImage = closeOverlayImage
), }
availableSize: CGSize(width: 30.0, height: 30.0),
transition: .immediate
)
let titleText: String let titleText: String
let amountText: String let amountText: String
@ -219,7 +220,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
var via: String? var via: String?
var messageId: EngineMessage.Id? var messageId: EngineMessage.Id?
var toPeer: EnginePeer? var toPeer: EnginePeer?
// var toString: String?
var transactionPeer: StarsContext.State.Transaction.Peer? var transactionPeer: StarsContext.State.Transaction.Peer?
var media: [AnyMediaReference] = [] var media: [AnyMediaReference] = []
var photo: TelegramMediaWebFile? var photo: TelegramMediaWebFile?
@ -234,7 +234,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
var isReaction = false var isReaction = false
var giveawayMessageId: MessageId? var giveawayMessageId: MessageId?
var isBoost = false var isBoost = false
var giftAnimation: TelegramMediaFile? var giftAnimationSubject: GiftCompositionComponent.Subject?
var isGiftUpgrade = false
var giftAvailability: StarGift.Gift.Availability?
var isRefProgram = false var isRefProgram = false
var delayedCloseOnOpenPeer = true var delayedCloseOnOpenPeer = true
@ -250,7 +252,6 @@ private final class StarsTransactionSheetContent: CombinedComponent {
count = StarsAmount(value: stars, nanos: 0) count = StarsAmount(value: stars, nanos: 0)
date = boost.date date = boost.date
toPeer = state.peerMap[peerId] toPeer = state.peerMap[peerId]
// toString = strings.Stars_Transaction_Giveaway_Boost_Subscribers(boost.quantity)
giveawayMessageId = boost.giveawayMessageId giveawayMessageId = boost.giveawayMessageId
isBoost = true isBoost = true
case let .importer(peer, pricing, importer, usdRate): case let .importer(peer, pricing, importer, usdRate):
@ -368,9 +369,15 @@ private final class StarsTransactionSheetContent: CombinedComponent {
toPeer = peer toPeer = peer
} }
transactionPeer = transaction.peer transactionPeer = transaction.peer
if case let .generic(gift) = starGift {
giftAnimation = gift.file switch starGift {
case let .generic(gift):
giftAnimationSubject = .generic(gift.file)
giftAvailability = gift.availability
case let .unique(gift):
giftAnimationSubject = .unique(gift)
} }
isGiftUpgrade = transaction.flags.contains(.isStarGiftUpgrade)
} else if let giveawayMessageIdValue = transaction.giveawayMessageId { } else if let giveawayMessageIdValue = transaction.giveawayMessageId {
titleText = strings.Stars_Transaction_Giveaway_Title titleText = strings.Stars_Transaction_Giveaway_Title
descriptionText = "" descriptionText = ""
@ -583,6 +590,28 @@ private final class StarsTransactionSheetContent: CombinedComponent {
descriptionText = modifiedString descriptionText = modifiedString
} }
var closeButtonImage = closeImage
if case .unique = giftAnimationSubject {
closeButtonImage = closeOverlayImage
}
let closeButton = closeButton.update(
component: Button(
content: AnyComponent(Image(image: closeButtonImage)),
action: { [weak component] in
component?.cancel(true)
}
),
availableSize: CGSize(width: 30.0, height: 30.0),
transition: .immediate
)
let headerTextColor: UIColor
if case .unique = giftAnimationSubject {
headerTextColor = .white
} else {
headerTextColor = theme.actionSheet.primaryTextColor
}
let absCount = StarsAmount(value: abs(count.value), nanos: abs(count.nanos)) let absCount = StarsAmount(value: abs(count.value), nanos: abs(count.nanos))
let formattedAmount = presentationStringsFormattedNumber(absCount, dateTimeFormat.groupingSeparator) let formattedAmount = presentationStringsFormattedNumber(absCount, dateTimeFormat.groupingSeparator)
let countColor: UIColor let countColor: UIColor
@ -601,7 +630,11 @@ private final class StarsTransactionSheetContent: CombinedComponent {
countColor = theme.list.itemPrimaryTextColor countColor = theme.list.itemPrimaryTextColor
} else if count < StarsAmount.zero { } else if count < StarsAmount.zero {
amountText = "- \(formattedAmount)" amountText = "- \(formattedAmount)"
countColor = theme.list.itemDestructiveColor if case .unique = giftAnimationSubject {
countColor = .white
} else {
countColor = theme.list.itemDestructiveColor
}
} else { } else {
amountText = "+ \(formattedAmount)" amountText = "+ \(formattedAmount)"
countColor = theme.list.itemDisclosureActions.constructive.fillColor countColor = theme.list.itemDisclosureActions.constructive.fillColor
@ -612,7 +645,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: titleText, string: titleText,
font: Font.bold(25.0), font: Font.bold(25.0),
textColor: theme.actionSheet.primaryTextColor, textColor: headerTextColor,
paragraphAlignment: .center paragraphAlignment: .center
)), )),
horizontalAlignment: .center, horizontalAlignment: .center,
@ -647,17 +680,25 @@ private final class StarsTransactionSheetContent: CombinedComponent {
imageIcon = nil imageIcon = nil
} }
var starOriginY: CGFloat = 81.0
var starChild: _UpdatedChildComponent var starChild: _UpdatedChildComponent
if let giftAnimation { if let giftAnimationSubject {
let animationHeight: CGFloat
if case .unique = giftAnimationSubject {
animationHeight = 240.0
} else {
animationHeight = 210.0
}
starChild = gift.update( starChild = gift.update(
component: GiftAnimationComponent( component: GiftCompositionComponent(
context: component.context, context: component.context,
theme: theme, theme: theme,
file: giftAnimation subject: giftAnimationSubject
), ),
availableSize: CGSize(width: 128.0, height: 128.0), availableSize: CGSize(width: context.availableSize.width, height: animationHeight),
transition: .immediate transition: .immediate
) )
starOriginY = animationHeight / 2.0
} else if isBoost { } else if isBoost {
starChild = activeStar.update( starChild = activeStar.update(
component: PremiumStarComponent( component: PremiumStarComponent(
@ -721,6 +762,16 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let tableLinkColor = theme.list.itemAccentColor let tableLinkColor = theme.list.itemAccentColor
var tableItems: [TableComponent.Item] = [] var tableItems: [TableComponent.Item] = []
if isGiftUpgrade {
tableItems.append(.init(
id: "reason",
title: strings.Stars_Transaction_Giveaway_Reason,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Stars_Transaction_GiftUpgrade, font: tableFont, textColor: tableTextColor)))
)
))
}
if isGift, toPeer == nil { if isGift, toPeer == nil {
tableItems.append(.init( tableItems.append(.init(
id: "from", id: "from",
@ -746,7 +797,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
)) ))
} else if let toPeer, !isRefProgram { } else if let toPeer, !isRefProgram {
let title: String let title: String
if isSubscription { if isGiftUpgrade {
title = strings.Stars_Transaction_GiftFrom
} else if isSubscription {
if isBotSubscription { if isBotSubscription {
title = strings.Stars_Transaction_Subscription_Bot title = strings.Stars_Transaction_Subscription_Bot
} else if isBusinessSubscription { } else if isBusinessSubscription {
@ -759,10 +812,56 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} else { } else {
title = count < StarsAmount.zero || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From title = count < StarsAmount.zero || countIsGeneric ? strings.Stars_Transaction_To : strings.Stars_Transaction_From
} }
tableItems.append(.init(
id: "to", let toComponent: AnyComponent<Empty>
title: title, if let _ = giftAnimationSubject, !toPeer.isDeleted && !isGiftUpgrade {
component: AnyComponent( toComponent = AnyComponent(
HStack([
AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
context: component.context,
theme: theme,
peer: toPeer
)
),
action: {
if delayedCloseOnOpenPeer {
component.openPeer(toPeer, false)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
} else {
if let controller = controller() as? StarsTransactionScreen, let navigationController = controller.navigationController, let chatController = navigationController.viewControllers.first(where: { $0 is ChatController }) as? ChatController {
chatController.playShakeAnimation()
}
component.cancel(true)
}
}
))
),
AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: component.context,
text: strings.Gift_View_Send,
color: theme.list.itemAccentColor
)),
action: {
component.sendGift(toPeer.id)
Queue.mainQueue().after(1.0, {
component.cancel(false)
})
}
))
)
], spacing: 4.0)
)
} else {
toComponent = AnyComponent(
Button( Button(
content: AnyComponent( content: AnyComponent(
PeerCellComponent( PeerCellComponent(
@ -786,6 +885,11 @@ private final class StarsTransactionSheetContent: CombinedComponent {
} }
) )
) )
}
tableItems.append(.init(
id: "to",
title: title,
component: toComponent
)) ))
if case let .subscription(subscription) = component.subject, let title = subscription.title { if case let .subscription(subscription) = component.subject, let title = subscription.title {
tableItems.append(.init( tableItems.append(.init(
@ -1001,6 +1105,7 @@ private final class StarsTransactionSheetContent: CombinedComponent {
Button( Button(
content: AnyComponent( content: AnyComponent(
TransactionCellComponent( TransactionCellComponent(
backgroundColor: theme.actionSheet.opaqueItemBackgroundColor,
textColor: tableTextColor, textColor: tableTextColor,
accentColor: tableLinkColor, accentColor: tableLinkColor,
transactionId: transactionId transactionId: transactionId
@ -1048,6 +1153,17 @@ private final class StarsTransactionSheetContent: CombinedComponent {
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor))) MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: date, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
) )
)) ))
if let giftAvailability {
let remainsString = presentationStringsFormattedNumber(giftAvailability.remains, environment.dateTimeFormat.groupingSeparator)
let totalString = presentationStringsFormattedNumber(giftAvailability.total, environment.dateTimeFormat.groupingSeparator)
tableItems.append(.init(
id: "availability",
title: strings.Gift_View_Availability,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_View_Availability_NewOf("\(remainsString)", "\(totalString)").string, font: tableFont, textColor: tableTextColor)))
)
))
}
if isSubscriber, let additionalDate { if isSubscriber, let additionalDate {
tableItems.append(.init( tableItems.append(.init(
@ -1103,15 +1219,17 @@ private final class StarsTransactionSheetContent: CombinedComponent {
) )
context.add(starChild context.add(starChild
.position(CGPoint(x: context.availableSize.width / 2.0, y: 200.0 / 2.0 - 19.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: starOriginY))
) )
var originY: CGFloat = 156.0
if let _ = giftAnimationSubject {
originY += 18.0
}
context.add(title context.add(title
.position(CGPoint(x: context.availableSize.width / 2.0, y: 31.0 + 125.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: originY))
) )
originY += 21.0
var originY: CGFloat = 0.0
originY += 200.0 - 23.0
var descriptionSize: CGSize = .zero var descriptionSize: CGSize = .zero
if !descriptionText.isEmpty { if !descriptionText.isEmpty {
@ -1238,6 +1356,10 @@ private final class StarsTransactionSheetContent: CombinedComponent {
.position(CGPoint(x: amountStarOriginX, y: amountOrigin + amountStar.size.height / 2.0 - UIScreenPixel + amountStarOffsetY)) .position(CGPoint(x: amountStarOriginX, y: amountOrigin + amountStar.size.height / 2.0 - UIScreenPixel + amountStarOffsetY))
) )
if case .unique = giftAnimationSubject {
originY += 21.0
}
context.add(table context.add(table
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + table.size.height / 2.0))
) )
@ -1353,6 +1475,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
let openAppExamples: () -> Void let openAppExamples: () -> Void
let copyTransactionId: (String) -> Void let copyTransactionId: (String) -> Void
let updateSubscription: () -> Void let updateSubscription: () -> Void
let sendGift: (EnginePeer.Id) -> Void
init( init(
context: AccountContext, context: AccountContext,
@ -1362,7 +1485,8 @@ 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 () -> Void updateSubscription: @escaping () -> Void,
sendGift: @escaping (EnginePeer.Id) -> Void
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -1372,6 +1496,7 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
self.openAppExamples = openAppExamples self.openAppExamples = openAppExamples
self.copyTransactionId = copyTransactionId self.copyTransactionId = copyTransactionId
self.updateSubscription = updateSubscription self.updateSubscription = updateSubscription
self.sendGift = sendGift
} }
static func ==(lhs: StarsTransactionSheetComponent, rhs: StarsTransactionSheetComponent) -> Bool { static func ==(lhs: StarsTransactionSheetComponent, rhs: StarsTransactionSheetComponent) -> Bool {
@ -1416,7 +1541,8 @@ private final class StarsTransactionSheetComponent: CombinedComponent {
openMedia: context.component.openMedia, openMedia: context.component.openMedia,
openAppExamples: context.component.openAppExamples, openAppExamples: context.component.openAppExamples,
copyTransactionId: context.component.copyTransactionId, copyTransactionId: context.component.copyTransactionId,
updateSubscription: context.component.updateSubscription updateSubscription: context.component.updateSubscription,
sendGift: context.component.sendGift
)), )),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true, followContentSizeChanges: true,
@ -1516,6 +1642,7 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
var openAppExamplesImpl: (() -> Void)? var openAppExamplesImpl: (() -> Void)?
var copyTransactionIdImpl: ((String) -> Void)? var copyTransactionIdImpl: ((String) -> Void)?
var updateSubscriptionImpl: (() -> Void)? var updateSubscriptionImpl: (() -> Void)?
var sendGiftImpl: ((EnginePeer.Id) -> Void)?
super.init( super.init(
context: context, context: context,
@ -1539,6 +1666,9 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
}, },
updateSubscription: { updateSubscription: {
updateSubscriptionImpl?() updateSubscriptionImpl?()
},
sendGift: { peerId in
sendGiftImpl?(peerId)
} }
), ),
navigationBarAppearance: .none, navigationBarAppearance: .none,
@ -1690,6 +1820,19 @@ public class StarsTransactionScreen: ViewControllerComponentContainer {
} }
} }
} }
sendGiftImpl = { [weak self] peerId in
guard let self else {
return
}
let _ = (context.engine.payments.premiumGiftCodeOptions(peerId: nil, onlyCached: true)
|> filter { !$0.isEmpty }
|> deliverOnMainQueue).start(next: { giftOptions in
let premiumOptions = giftOptions.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
let controller = context.sharedContext.makeGiftOptionsController(context: context, peerId: peerId, premiumOptions: premiumOptions, hasBirthday: false)
self.push(controller)
})
}
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
@ -2011,7 +2154,7 @@ private final class PeerCellComponent: Component {
let avatarNaturalSize = self.avatar.update( let avatarNaturalSize = self.avatar.update(
transition: .immediate, transition: .immediate,
component: AnyComponent( component: AnyComponent(
StarsAvatarComponent(context: component.context, theme: component.theme, peer: peer, photo: nil, media: [], backgroundColor: .clear) StarsAvatarComponent(context: component.context, theme: component.theme, peer: peer, photo: nil, media: [], uniqueGift: nil, backgroundColor: .clear)
), ),
environment: {}, environment: {},
containerSize: CGSize(width: 40.0, height: 40.0) containerSize: CGSize(width: 40.0, height: 40.0)
@ -2063,18 +2206,23 @@ private final class PeerCellComponent: Component {
} }
private final class TransactionCellComponent: Component { private final class TransactionCellComponent: Component {
let backgroundColor: UIColor
let textColor: UIColor let textColor: UIColor
let accentColor: UIColor let accentColor: UIColor
let transactionId: String let transactionId: String
init(textColor: UIColor, accentColor: UIColor, transactionId: String) { init(backgroundColor: UIColor, textColor: UIColor, accentColor: UIColor, transactionId: String) {
self.backgroundColor = backgroundColor
self.textColor = textColor self.textColor = textColor
self.accentColor = accentColor self.accentColor = accentColor
self.transactionId = transactionId self.transactionId = transactionId
} }
static func ==(lhs: TransactionCellComponent, rhs: TransactionCellComponent) -> Bool { static func ==(lhs: TransactionCellComponent, rhs: TransactionCellComponent) -> Bool {
if lhs.textColor !== rhs.textColor { if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.textColor != rhs.textColor {
return false return false
} }
if lhs.accentColor != rhs.accentColor { if lhs.accentColor != rhs.accentColor {
@ -2089,12 +2237,17 @@ private final class TransactionCellComponent: Component {
final class View: UIView { final class View: UIView {
private let text = ComponentView<Empty>() private let text = ComponentView<Empty>()
private let button = ComponentView<Empty>() private let button = ComponentView<Empty>()
private let gradientView = UIImageView()
private var component: TransactionCellComponent? private var component: TransactionCellComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
self.layer.allowsGroupOpacity = true
self.gradientView.image = generateGradientImage(size: CGSize(width: 40.0, height: 1.0), colors: [UIColor.white.withAlphaComponent(0.0), UIColor.white, UIColor.white], locations: [0.0, 0.65, 1.0], direction: .horizontal)?.withRenderingMode(.alwaysTemplate)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -2105,54 +2258,48 @@ private final class TransactionCellComponent: Component {
self.component = component self.component = component
self.state = state self.state = state
let spacing: CGFloat = 6.0 self.gradientView.tintColor = component.backgroundColor
let buttonSize = self.button.update( let buttonSize = self.button.update(
transition: .immediate, transition: .immediate,
component: AnyComponent( component: AnyComponent(
BundleIconComponent(name: "Chat/Context Menu/Copy", tintColor: component.accentColor) BundleIconComponent(
name: "Chat/Context Menu/Copy",
tintColor: component.accentColor
)
), ),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width, height: availableSize.height) containerSize: CGSize(width: availableSize.width, height: availableSize.height)
) )
func brokenLine(_ string: String) -> String {
if string.count > 30 {
return string
}
let middleIndex = string.index(string.startIndex, offsetBy: string.count / 2)
var newString = string
newString.insert("\n", at: middleIndex)
return newString
}
let text: String
if availableSize.width > 230.0 {
text = component.transactionId
} else {
text = brokenLine(component.transactionId)
}
let textSize = self.text.update( let textSize = self.text.update(
transition: .immediate, transition: .immediate,
component: AnyComponent( component: AnyComponent(
MultilineTextComponent( MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: text, string: component.transactionId,
font: Font.monospace(15.0), font: Font.monospace(15.0),
textColor: component.textColor, textColor: component.textColor,
paragraphAlignment: .left paragraphAlignment: .left
)), )),
maximumNumberOfLines: 0, maximumNumberOfLines: 1
lineSpacing: 0.2
) )
), ),
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width - buttonSize.width - spacing, height: availableSize.height) containerSize: CGSize(width: availableSize.width - buttonSize.width + 10.0, height: availableSize.height)
) )
let size = CGSize(width: availableSize.width, height: textSize.height) let size = CGSize(width: availableSize.width, height: textSize.height)
let textFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0) + 1.0), size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
self.addSubview(textView)
self.addSubview(self.gradientView)
}
transition.setFrame(view: textView, frame: textFrame)
}
let buttonFrame = CGRect(origin: CGPoint(x: availableSize.width - buttonSize.width - 2.0, y: floorToScreenPixels((size.height - buttonSize.height) / 2.0)), size: buttonSize) let buttonFrame = CGRect(origin: CGPoint(x: availableSize.width - buttonSize.width - 2.0, y: floorToScreenPixels((size.height - buttonSize.height) / 2.0)), size: buttonSize)
if let buttonView = self.button.view { if let buttonView = self.button.view {
if buttonView.superview == nil { if buttonView.superview == nil {
@ -2161,13 +2308,7 @@ private final class TransactionCellComponent: Component {
transition.setFrame(view: buttonView, frame: buttonFrame) transition.setFrame(view: buttonView, frame: buttonFrame)
} }
let textFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - textSize.height) / 2.0) + 1.0), size: textSize) self.gradientView.frame = CGRect(x: size.width - buttonSize.width - 32.0, y: 0.0, width: 40.0, height: size.height)
if let textView = self.text.view {
if textView.superview == nil {
self.addSubview(textView)
}
transition.setFrame(view: textView, frame: textFrame)
}
return size return size
} }
@ -2202,3 +2343,92 @@ private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor:
context.strokePath() context.strokePath()
}) })
} }
private final class ButtonContentComponent: Component {
let context: AccountContext
let text: String
let color: UIColor
public init(
context: AccountContext,
text: String,
color: UIColor
) {
self.context = context
self.text = text
self.color = color
}
public static func ==(lhs: ButtonContentComponent, rhs: ButtonContentComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.text != rhs.text {
return false
}
if lhs.color != rhs.color {
return false
}
return true
}
public final class View: UIView {
private var component: ButtonContentComponent?
private weak var componentState: EmptyComponentState?
private let backgroundLayer = SimpleLayer()
private let title = ComponentView<Empty>()
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.addSublayer(self.backgroundLayer)
self.backgroundLayer.masksToBounds = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ButtonContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.componentState = state
let attributedText = NSAttributedString(string: component.text, font: Font.regular(11.0), textColor: component.color)
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(
MultilineTextComponent(text: .plain(attributedText))
),
environment: {},
containerSize: availableSize
)
let padding: CGFloat = 6.0
let size = CGSize(width: titleSize.width + padding * 2.0, height: 18.0)
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: titleFrame)
}
let backgroundColor = component.color.withAlphaComponent(0.1)
self.backgroundLayer.backgroundColor = backgroundColor.cgColor
transition.setFrame(layer: self.backgroundLayer, frame: CGRect(origin: .zero, size: size))
self.backgroundLayer.cornerRadius = size.height / 2.0
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -49,6 +49,7 @@ swift_library(
"//submodules/TelegramUI/Components/Stars/StarsAvatarComponent", "//submodules/TelegramUI/Components/Stars/StarsAvatarComponent",
"//submodules/TelegramUI/Components/LottieComponent", "//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/LottieComponentResourceContent", "//submodules/TelegramUI/Components/LottieComponentResourceContent",
"//submodules/TelegramUI/Components/Gifts/GiftAnimationComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -16,7 +16,7 @@ import AvatarNode
import BundleIconComponent import BundleIconComponent
import PhotoResources import PhotoResources
import StarsAvatarComponent import StarsAvatarComponent
import LottieComponent import GiftAnimationComponent
private extension StarsContext.State.Transaction { private extension StarsContext.State.Transaction {
var extendedId: String { var extendedId: String {
@ -300,13 +300,20 @@ final class StarsTransactionsListPanelComponent: Component {
var itemDate: String var itemDate: String
var itemPeer = item.peer var itemPeer = item.peer
var itemFile: TelegramMediaFile? var itemFile: TelegramMediaFile?
var uniqueGift: StarGift.UniqueGift?
switch item.peer { switch item.peer {
case let .peer(peer): case let .peer(peer):
if let starGift = item.starGift { if let starGift = item.starGift {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) if item.flags.contains(.isStarGiftUpgrade), case let .unique(gift) = starGift {
itemSubtitle = item.count > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_ConvertedGift : environment.strings.Stars_Intro_Transaction_Gift itemTitle = "\(gift.title) #\(gift.number)"
if case let .generic(gift) = starGift { itemSubtitle = environment.strings.Stars_Intro_Transaction_GiftUpgrade
itemFile = gift.file uniqueGift = gift
} else {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
itemSubtitle = item.count > StarsAmount.zero ? environment.strings.Stars_Intro_Transaction_ConvertedGift : environment.strings.Stars_Intro_Transaction_Gift
if case let .generic(gift) = starGift {
itemFile = gift.file
}
} }
} else if let _ = item.giveawayMessageId { } else if let _ = item.giveawayMessageId {
itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast) itemTitle = peer.displayTitle(strings: environment.strings, displayOrder: .firstLast)
@ -411,24 +418,23 @@ final class StarsTransactionsListPanelComponent: Component {
if let itemFile { if let itemFile {
subtitleComponent = AnyComponent( subtitleComponent = AnyComponent(
HStack([ HStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(LottieComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(
content: LottieComponent.ResourceContent( GiftAnimationComponent(
context: component.context, context: component.context,
theme: environment.theme,
file: itemFile, file: itemFile,
attemptSynchronously: false, still: true,
providesPlaceholder: true size: CGSize(width: 20.0, height: 20.0)
), )
color: nil, )),
placeholderColor: environment.theme.list.mediaPlaceholderColor, AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(
size: CGSize(width: 20.0, height: 20.0), MultilineTextComponent(
loop: false text: .plain(NSAttributedString(
))), string: itemSubtitle,
AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( font: Font.regular(fontBaseDisplaySize * 16.0 / 17.0),
text: .plain(NSAttributedString( textColor: environment.theme.list.itemPrimaryTextColor
string: itemSubtitle, )
font: Font.regular(fontBaseDisplaySize * 16.0 / 17.0), )
textColor: environment.theme.list.itemPrimaryTextColor
))
))) )))
], spacing: 2.0) ], spacing: 2.0)
) )
@ -463,7 +469,7 @@ final class StarsTransactionsListPanelComponent: Component {
theme: environment.theme, theme: environment.theme,
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)),
contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right), contentInsets: UIEdgeInsets(top: 9.0, left: environment.containerInsets.left, bottom: 8.0, right: environment.containerInsets.right),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: itemPeer, photo: item.photo, media: item.media, backgroundColor: environment.theme.list.plainBackgroundColor))), false), leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: itemPeer, photo: item.photo, media: item.media, uniqueGift: uniqueGift, backgroundColor: environment.theme.list.plainBackgroundColor))), false),
icon: nil, icon: nil,
accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), accessory: .custom(ListActionItemComponent.CustomAccessory(component: AnyComponentWithIdentity(id: "label", component: AnyComponent(StarsLabelComponent(text: itemLabel))), insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
action: { [weak self] _ in action: { [weak self] _ in

View File

@ -765,7 +765,7 @@ final class StarsTransactionsScreenComponent: Component {
if let photo = subscription.photo { if let photo = subscription.photo {
nameGroupComponent = AnyComponent( nameGroupComponent = AnyComponent(
HStack([ HStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: nil, photo: photo, media: [], backgroundColor: .clear, size: CGSize(width: 19.0, height: 19.0)))), AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: nil, photo: photo, media: [], uniqueGift: nil, backgroundColor: .clear, size: CGSize(width: 19.0, height: 19.0)))),
AnyComponentWithIdentity(id: AnyHashable(1), component: nameComponent) AnyComponentWithIdentity(id: AnyHashable(1), component: nameComponent)
], spacing: 6.0) ], spacing: 6.0)
) )
@ -806,7 +806,7 @@ final class StarsTransactionsScreenComponent: Component {
theme: environment.theme, theme: environment.theme,
title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)), title: AnyComponent(VStack(titleComponents, alignment: .left, spacing: 2.0)),
contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0), contentInsets: UIEdgeInsets(top: 9.0, left: 0.0, bottom: 8.0, right: 0.0),
leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: .peer(subscription.peer), photo: nil, media: [], backgroundColor: environment.theme.list.plainBackgroundColor))), false), leftIcon: .custom(AnyComponentWithIdentity(id: "avatar", component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: .peer(subscription.peer), photo: nil, media: [], uniqueGift: nil, backgroundColor: environment.theme.list.plainBackgroundColor))), false),
icon: nil, icon: nil,
accessory: .custom(ListActionItemComponent.CustomAccessory(component: labelComponent, insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))), accessory: .custom(ListActionItemComponent.CustomAccessory(component: labelComponent, insets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16.0))),
action: { [weak self] _ in action: { [weak self] _ in

View File

@ -1228,8 +1228,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
controller.videoCompletion = { [weak self] image, url, adjustments, commit in controller.videoCompletion = { [weak self] image, url, adjustments, commit in
if let strongSelf = self { if let strongSelf = self {
if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let _ = rootController.accountSettingsController as? PeerInfoScreenImpl { if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
//settingsController.updateProfileVideo(image, mode: .accept, asset: AVURLAsset(url: url), adjustments: adjustments) settingsController.updateProfileVideo(image, asset: AVURLAsset(url: url), adjustments: adjustments, mode: .accept)
commit() commit()
} }
} }
@ -1263,8 +1263,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
}, videoCompletion: { [weak self] image, url, adjustments in }, videoCompletion: { [weak self] image, url, adjustments in
if let strongSelf = self { if let strongSelf = self {
if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let _ = rootController.accountSettingsController as? PeerInfoScreenImpl { if let rootController = strongSelf.effectiveNavigationController as? TelegramRootController, let settingsController = rootController.accountSettingsController as? PeerInfoScreenImpl {
//settingsController.updateProfileVideo(image, mode: .accept, asset: AVURLAsset(url: url), adjustments: adjustments) settingsController.updateProfileVideo(image, asset: AVURLAsset(url: url), adjustments: adjustments, mode: .accept)
} }
} }
}) })

View File

@ -2472,12 +2472,31 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
presentTransferAlertImpl = { [weak controller] peer in presentTransferAlertImpl = { [weak controller] peer in
guard let controller, case let .starGiftTransfer(_, messageId, gift, transferStars, _) = source else { guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _) = source else {
return return
} }
let alertController = giftTransferAlertController(context: context, gift: gift, peer: peer, transferStars: transferStars, commit: { [weak controller] in let alertController = giftTransferAlertController(context: context, gift: gift, peer: peer, transferStars: transferStars, commit: { [weak controller] in
controller?.dismiss() completion?([peer.id])
let _ = context.engine.payments.transferStarGift(prepaid: transferStars == 0, messageId: messageId, peerId: peer.id).start()
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
return
}
var controllers = navigationController.viewControllers
controllers = controllers.filter { !($0 is ContactSelectionController) }
var foundController = false
for controller in controllers.reversed() {
if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation {
chatController.hintPlayNextOutgoingGift()
foundController = true
break
}
}
if !foundController {
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
chatController.hintPlayNextOutgoingGift()
controllers.append(chatController)
}
navigationController.setViewControllers(controllers, animated: true)
}) })
controller.present(alertController, in: .window(.root)) controller.present(alertController, in: .window(.root))
} }