diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 6fe7f155f5..02432016b8 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13469,15 +13469,21 @@ Sorry for the inconvenience."; "Gift.View.Status.NonUnique" = "Non-Unique"; "Gift.View.Status.Upgrade" = "upgrade"; "Gift.View.DisplayedInfoHide" = "The gift is visible on your Page. [Hide >]()"; +"Gift.View.HiddenInfoShow" = "This gift is hidden. Only you can see it. [Show >]()"; "Gift.Upgrade.Title" = "Upgrade Gift"; +"Gift.Upgrade.IncludeTitle" = "Make Unique"; "Gift.Upgrade.Description" = "Turn your gift into a unique collectible that you can transfer or auction."; +"Gift.Upgrade.IncludeDescription" = "Let %@ turn your gift into a unique collectible."; "Gift.Upgrade.Unique.Title" = "Unique"; -"Gift.Upgrade.Unique.Description" = "Get a unique number, model backdrop and and symbol for your gift."; +"Gift.Upgrade.Unique.Description" = "Get a unique number, model, backdrop, and symbol for your gift."; +"Gift.Upgrade.Unique.IncludeDescription" = "The recipient will get a unique number, model, backdrop, and symbol for the gift."; "Gift.Upgrade.Transferable.Title" = "Transferable"; "Gift.Upgrade.Transferable.Description" = "Send your upgraded gift to any of your friends on Telegram."; +"Gift.Upgrade.Transferable.IncludeDescription" = "The recipient will be able to send the gift to anyone Telegram."; "Gift.Upgrade.Tradable.Title" = "Tradable"; "Gift.Upgrade.Tradable.Description" = "Sell or auction your gift on third-party NFT marketplaces."; +"Gift.Upgrade.Tradable.IncludeDescription" = "The recipient will be able to auction the gift on third-party NFT marketplaces."; "Gift.Upgrade.Soon" = "SOON"; "Gift.Upgrade.AddName" = "Add sender's name"; "Gift.Upgrade.AddNameAndComment" = "Add sender's name and comment"; @@ -13525,6 +13531,7 @@ Sorry for the inconvenience."; "Gift.Transfer.Confirmation.Transfer" = "Transfer for"; "Gift.Transfer.Confirmation.TransferFree" = "Transfer"; +"Gift.View.UpgradeForFree" = "Upgrade for Free"; "Gift.View.KeepUpgradeOrConvertDescription" = "You can keep this gift, upgrade it, or sell it for %@. [More About Stars >]()"; "PeerInfo.VerificationInfo.Bot" = "This bot is verified as official by the representatives of Telegram."; @@ -13537,8 +13544,21 @@ Sorry for the inconvenience."; "Gift.Send.Upgrade" = "Make Unique for %@"; "Gift.Send.Upgrade.Info" = "Enable this to let %1$@ turn your gift into a unique collectible. [Learn More >]()"; +"Notification.StarGift.Unpack" = "Unpack"; + +"Notification.StarGift.Model" = "Model"; +"Notification.StarGift.Backdrop" = "Backdrop"; +"Notification.StarGift.Symbol" = "Symbol"; + +"Notification.StarGift.Gift" = "gift"; + "Notification.StarsGift.Upgrade" = "%@ turned the gift from you to a unique collectible"; "Notification.StarsGift.UpgradeYou" = "You turned the gift from %@ to a unique collectible"; "Notification.StarsGift.Transfer" = "%@ transferred you a unique collectible"; "Notification.StarsGift.TransferYou" = "You transferred a unique collectible"; + +"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."; + +"Gift.View.KeepOrUpgradeDescription" = "You can keep this gift or upgrade it."; diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 4dd5ff549f..c56255ddea 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3107,7 +3107,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { var titleLeftOffset: CGFloat = 0.0 if let currentVerifiedIconContent { - if titleLeftOffset.isZero, currentVerifiedIconContent != .none { + if titleLeftOffset.isZero, case .animation = currentVerifiedIconContent { titleLeftOffset += 20.0 } @@ -3127,10 +3127,6 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } if let currentCredibilityIconContent { - if titleLeftOffset.isZero, case .verified = currentCredibilityIconContent { - titleLeftOffset += 20.0 - } - if titleIconsWidth.isZero { titleIconsWidth += 4.0 } else { @@ -4500,11 +4496,8 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { ) strongSelf.credibilityIconComponent = credibilityIconComponent - var iconOrigin: CGFloat = nextTitleIconOrigin + let iconOrigin: CGFloat = nextTitleIconOrigin let containerSize = CGSize(width: 20.0, height: 20.0) - if case .verified = currentCredibilityIconContent { - iconOrigin = contentRect.origin.x - } let iconSize = credibilityIconView.update( transition: .immediate, component: AnyComponent(credibilityIconComponent), @@ -4512,10 +4505,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { containerSize: containerSize ) transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: iconOrigin, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - iconSize.height / 2.0) - UIScreenPixel), size: iconSize)) - if case .verified = currentCredibilityIconContent { - } else { - nextTitleIconOrigin += credibilityIconView.bounds.width + 4.0 - } + nextTitleIconOrigin += credibilityIconView.bounds.width + 4.0 } else if let credibilityIconView = strongSelf.credibilityIconView { strongSelf.credibilityIconView = nil credibilityIconView.removeFromSuperview() @@ -4541,7 +4531,12 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { ) strongSelf.verifiedIconComponent = verifiedIconComponent - let iconOrigin = contentRect.origin.x + let iconOrigin: CGFloat + if case .animation = currentVerifiedIconContent { + iconOrigin = contentRect.origin.x + } else { + iconOrigin = nextTitleIconOrigin + } let containerSize = CGSize(width: 16.0, height: 16.0) let iconSize = verifiedIconView.update( diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index 089c47be8c..8aa2fb2c5d 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -582,77 +582,6 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { view.cornerRadius = layer.cornerRadius view.backgroundColor = layer.backgroundColor view.layerTintColor = layer.layerTintColor - - /* - open var path: CGPath? - - - /* The color to fill the path, or nil for no fill. Defaults to opaque - * black. Animatable. */ - - open var fillColor: CGColor? - - - /* The fill rule used when filling the path. Options are `non-zero' and - * `even-odd'. Defaults to `non-zero'. */ - - open var fillRule: CAShapeLayerFillRule - - - /* The color to fill the path's stroked outline, or nil for no stroking. - * Defaults to nil. Animatable. */ - - open var strokeColor: CGColor? - - - /* These values define the subregion of the path used to draw the - * stroked outline. The values must be in the range [0,1] with zero - * representing the start of the path and one the end. Values in - * between zero and one are interpolated linearly along the path - * length. strokeStart defaults to zero and strokeEnd to one. Both are - * animatable. */ - - open var strokeStart: CGFloat - - open var strokeEnd: CGFloat - - - /* The line width used when stroking the path. Defaults to one. - * Animatable. */ - - open var lineWidth: CGFloat - - - /* The miter limit used when stroking the path. Defaults to ten. - * Animatable. */ - - open var miterLimit: CGFloat - - - /* The cap style used when stroking the path. Options are `butt', `round' - * and `square'. Defaults to `butt'. */ - - open var lineCap: CAShapeLayerLineCap - - - /* The join style used when stroking the path. Options are `miter', `round' - * and `bevel'. Defaults to `miter'. */ - - open var lineJoin: CAShapeLayerLineJoin - - - /* The phase of the dashing pattern applied when creating the stroke. - * Defaults to zero. Animatable. */ - - open var lineDashPhase: CGFloat - - - /* The dash pattern (an array of NSNumbers) applied when creating the - * stroked version of the path. Defaults to nil. */ - - open var lineDashPattern: [NSNumber]? - */ - view.path = layer.path view.fillColor = layer.fillColor view.fillRule = layer.fillRule @@ -666,6 +595,40 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { view.lineDashPhase = layer.lineDashPhase view.lineDashPattern = layer.lineDashPattern + if let sublayers = layer.sublayers { + for sublayer in sublayers { + let subtree = makeLayerSubtreeSnapshot(layer: sublayer) + if let subtree = subtree { + subtree.transform = sublayer.transform + subtree.position = sublayer.position + subtree.bounds = sublayer.bounds + subtree.anchorPoint = sublayer.anchorPoint + view.addSublayer(subtree) + } else { + return nil + } + } + } + return view + } else if let layer = layer as? CAGradientLayer { + let view = CAGradientLayer() + view.isHidden = layer.isHidden + view.opacity = layer.opacity + view.contents = layer.contents + view.contentsRect = layer.contentsRect + view.contentsScale = layer.contentsScale + view.contentsCenter = layer.contentsCenter + view.contentsGravity = layer.contentsGravity + view.masksToBounds = layer.masksToBounds + view.cornerRadius = layer.cornerRadius + view.backgroundColor = layer.backgroundColor + view.layerTintColor = layer.layerTintColor + view.colors = layer.colors + view.locations = layer.locations + view.startPoint = layer.startPoint + view.endPoint = layer.endPoint + view.type = layer.type + if let sublayers = layer.sublayers { for sublayer in sublayers { let subtree = makeLayerSubtreeSnapshot(layer: sublayer) diff --git a/submodules/DrawingUI/Sources/DrawingMetalView.swift b/submodules/DrawingUI/Sources/DrawingMetalView.swift index 2ad7288a25..38a11c72d6 100644 --- a/submodules/DrawingUI/Sources/DrawingMetalView.swift +++ b/submodules/DrawingUI/Sources/DrawingMetalView.swift @@ -47,6 +47,7 @@ final class DrawingMetalView: MTKView { super.init(frame: CGRect(origin: .zero, size: size), device: device) self.drawableSize = self.size + self.colorPixelFormat = .bgra8Unorm self.autoResizeDrawable = false self.isOpaque = false self.contentScaleFactor = 1.0 @@ -123,7 +124,7 @@ final class DrawingMetalView: MTKView { let pipelineDescription = MTLRenderPipelineDescriptor() pipelineDescription.vertexFunction = vertexFunction pipelineDescription.fragmentFunction = fragmentFunction - pipelineDescription.colorAttachments[0].pixelFormat = colorPixelFormat + pipelineDescription.colorAttachments[0].pixelFormat = self.colorPixelFormat do { self.pipelineState = try self.device?.makeRenderPipelineState(descriptor: pipelineDescription) @@ -250,6 +251,7 @@ private class Drawable { attachment?.texture = self.texture?.texture attachment?.loadAction = .load attachment?.storeAction = .store + attachment?.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0) self.updateBuffer(with: size) } @@ -288,7 +290,6 @@ private class Drawable { return commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) } - internal func commit(wait: Bool = false) { self.commandBuffer?.commit() if wait { @@ -673,9 +674,9 @@ final class Texture { origin: MTLOrigin(x: 0, y: 0, z: 0), size: MTLSize(width: self.width, height: self.height, depth: 1) ) - let data = Data(capacity: Int(self.bytesPerRow * self.height)) - if let bytes = data.withUnsafeBytes({ $0.baseAddress }) { - self.texture.replace(region: region, mipmapLevel: 0, withBytes: bytes, bytesPerRow: self.bytesPerRow) + let zeroData = [UInt8](repeating: 0, count: self.bytesPerRow * self.height) + zeroData.withUnsafeBytes { bytes in + self.texture.replace(region: region, mipmapLevel: 0, withBytes: bytes.baseAddress!, bytesPerRow: self.bytesPerRow) } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 2529308a07..b04192dc84 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -583,7 +583,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } - dict[1785072017] = { return Api.MessageAction.parse_messageActionStarGift($0) } + dict[-655036249] = { return Api.MessageAction.parse_messageActionStarGift($0) } dict[638024601] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 00dd86b553..5ed410d6d3 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -373,7 +373,7 @@ public extension Api { case messageActionSetChatTheme(emoticon: String) case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) - case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeStars: Int64?) + case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?) case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) @@ -720,14 +720,15 @@ public extension Api { serializeInt32(period, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)} break - case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeStars): + case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeMsgId, let upgradeStars): if boxed { - buffer.appendInt32(1785072017) + buffer.appendInt32(-655036249) } serializeInt32(flags, buffer: buffer, boxed: false) gift.serialize(buffer, true) if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)} if Int(flags) & Int(1 << 4) != 0 {serializeInt64(convertStars!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {serializeInt32(upgradeMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)} break case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars): @@ -864,8 +865,8 @@ public extension Api { return ("messageActionSetChatWallPaper", [("flags", flags as Any), ("wallpaper", wallpaper as Any)]) case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) - case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeStars): - return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("upgradeStars", upgradeStars as Any)]) + case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeMsgId, let upgradeStars): + return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("upgradeMsgId", upgradeMsgId as Any), ("upgradeStars", upgradeStars as Any)]) case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars): return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any)]) case .messageActionSuggestProfilePhoto(let photo): @@ -1528,15 +1529,18 @@ public extension Api { } } var _4: Int64? if Int(_1!) & Int(1 << 4) != 0 {_4 = reader.readInt64() } - var _5: Int64? - if Int(_1!) & Int(1 << 8) != 0 {_5 = reader.readInt64() } + var _5: Int32? + if Int(_1!) & Int(1 << 5) != 0 {_5 = reader.readInt32() } + var _6: Int64? + if Int(_1!) & Int(1 << 8) != 0 {_6 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil - let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, upgradeStars: _5) + let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 8) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.MessageAction.messageActionStarGift(flags: _1!, gift: _2!, message: _3, convertStars: _4, upgradeMsgId: _5, upgradeStars: _6) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index e3d9eba791..52fe9e9c13 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -9463,6 +9463,25 @@ public extension Api.functions.payments { }) } } +public extension Api.functions.payments { + static func getUserStarGift(msgId: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1258101595) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(msgId.count)) + for item in msgId { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "payments.getUserStarGift", parameters: [("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.UserStarGifts? in + let reader = BufferReader(buffer) + var result: Api.payments.UserStarGifts? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.payments.UserStarGifts + } + return result + }) + } +} public extension Api.functions.payments { static func getUserStarGifts(userId: Api.InputUser, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index 3acd9cd6cb..1b02ac704f 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -132,7 +132,6 @@ enum AccountStateMutationOperation { case UpdateStarsBalance(peerId: PeerId, balance: Api.StarsAmount) case UpdateStarsRevenueStatus(peerId: PeerId, status: StarsRevenueStats.Balances) case UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: Bool) - case UpdateUpgradedStarGift(from: Api.UserStarGift, to: Api.UserStarGift) } struct HoleFromPreviousState { @@ -702,14 +701,10 @@ struct AccountMutableState { mutating func updateStarsReactionsAreAnonymousByDefault(isAnonymous: Bool) { self.addOperation(.UpdateStarsReactionsAreAnonymousByDefault(isAnonymous: isAnonymous)) } - - mutating func updateUpgradedStarGift(from: Api.UserStarGift, to: Api.UserStarGift) { - self.addOperation(.UpdateUpgradedStarGift(from: from, to: to)) - } mutating func addOperation(_ operation: AccountStateMutationOperation) { 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, .UpdateUpgradedStarGift: + 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: break case let .AddMessages(messages, location): for message in messages { @@ -856,7 +851,6 @@ struct AccountReplayedFinalState { let updatedRevenueBalances: [PeerId: RevenueStats.Balances] let updatedStarsBalance: [PeerId: StarsAmount] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] - let updatedUpgradedStarGifts:[(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)] let sentScheduledMessageIds: Set } @@ -888,13 +882,12 @@ struct AccountFinalStateEvents { let updatedRevenueBalances: [PeerId: RevenueStats.Balances] let updatedStarsBalance: [PeerId: StarsAmount] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] - let updatedUpgradedStarGifts: [(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)] 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 && self.updatedUpgradedStarGifts.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 } - 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 = 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 = Set(), updatedUpgradedStarGifts: [(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)] = []) { + 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 = 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 = Set()) { self.addedIncomingMessageIds = addedIncomingMessageIds self.addedReactionEvents = addedReactionEvents self.wasScheduledMessageIds = wasScheduledMessageIds @@ -921,7 +914,6 @@ struct AccountFinalStateEvents { self.updatedRevenueBalances = updatedRevenueBalances self.updatedStarsBalance = updatedStarsBalance self.updatedStarsRevenueStatus = updatedStarsRevenueStatus - self.updatedUpgradedStarGifts = updatedUpgradedStarGifts self.sentScheduledMessageIds = sentScheduledMessageIds } @@ -952,7 +944,6 @@ struct AccountFinalStateEvents { self.updatedRevenueBalances = state.updatedRevenueBalances self.updatedStarsBalance = state.updatedStarsBalance self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus - self.updatedUpgradedStarGifts = state.updatedUpgradedStarGifts self.sentScheduledMessageIds = state.sentScheduledMessageIds } @@ -986,6 +977,6 @@ struct AccountFinalStateEvents { var sentScheduledMessageIds = self.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, updatedUpgradedStarGifts: self.updatedUpgradedStarGifts + other.updatedUpgradedStarGifts) + 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) } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index fc555ed7d1..65752ba302 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -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)) 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))) - case let .messageActionStarGift(flags, apiGift, message, convertStars, upgradeStars): + case let .messageActionStarGift(flags, apiGift, message, convertStars, _, upgradeStars): let text: String? let entities: [MessageTextEntity]? switch message { @@ -185,12 +185,12 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe guard let gift = StarGift(apiStarGift: apiGift) else { 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, upgradeStars: upgradeStars)) + 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)) case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars): guard let gift = StarGift(apiStarGift: apiGift) else { return nil } - return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars)) + return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0)) } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index 49a733168b..e008aa32e1 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -3282,7 +3282,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddQuickReplyMessages: OptimizeAddMessagesState? for operation in operations { 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, .UpdateUpgradedStarGift: + 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: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -3421,7 +3421,6 @@ func replayFinalState( var updatedStarsBalance: [PeerId: StarsAmount] = [:] var updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:] var updatedStarsReactionsAreAnonymousByDefault: Bool? - var updatedUpgradedStarGifts: [(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)] = [] var holesFromPreviousStateMessageIds: [MessageId] = [] var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:] @@ -4856,10 +4855,6 @@ func replayFinalState( updatedStarsRevenueStatus[peerId] = status case let .UpdateStarsReactionsAreAnonymousByDefault(value): updatedStarsReactionsAreAnonymousByDefault = value - case let .UpdateUpgradedStarGift(from, to): - if let fromGift = ProfileGiftsContext.State.StarGift(apiUserStarGift: from, transaction: transaction), let toGift = ProfileGiftsContext.State.StarGift(apiUserStarGift: to, transaction: transaction) { - updatedUpgradedStarGifts.append((fromGift, toGift)) - } } } @@ -5381,7 +5376,6 @@ func replayFinalState( updatedRevenueBalances: updatedRevenueBalances, updatedStarsBalance: updatedStarsBalance, updatedStarsRevenueStatus: updatedStarsRevenueStatus, - updatedUpgradedStarGifts: updatedUpgradedStarGifts, sentScheduledMessageIds: finalState.state.sentScheduledMessageIds ) } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index b97fb4e1a5..d975eef92f 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -56,10 +56,6 @@ private final class UpdatedStarsRevenueStatusSubscriberContext { let subscribers = Bag<([PeerId: StarsRevenueStats.Balances]) -> Void>() } -private final class UpgradedStarGiftsSubscriberContext { - let subscribers = Bag<([(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)]) -> Void>() -} - public enum DeletedMessageId: Hashable { case global(Int32) case messageId(MessageId) @@ -350,7 +346,6 @@ public final class AccountStateManager { private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext() private var updatedStarsBalanceContext = UpdatedStarsBalanceSubscriberContext() private var updatedStarsRevenueStatusContext = UpdatedStarsRevenueStatusSubscriberContext() - private var upgradedStarGiftsContext = UpgradedStarGiftsSubscriberContext() private let delayNotificatonsUntil = Atomic(value: nil) private let appliedMaxMessageIdPromise = Promise(nil) @@ -1113,9 +1108,6 @@ public final class AccountStateManager { if !events.updatedStarsRevenueStatus.isEmpty { strongSelf.notifyUpdatedStarsRevenueStatus(events.updatedStarsRevenueStatus) } - if !events.updatedUpgradedStarGifts.isEmpty { - strongSelf.notifyUpgradedStarGifts(events.updatedUpgradedStarGifts) - } if !events.updatedCalls.isEmpty { for call in events.updatedCalls { strongSelf.callSessionManager?.updateSession(call, completion: { _ in }) @@ -1774,34 +1766,7 @@ public final class AccountStateManager { subscriber(updatedStarsRevenueStatus) } } - - public func upgradedStarGifts() -> Signal<[(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)], NoError> { - let queue = self.queue - return Signal { [weak self] subscriber in - let disposable = MetaDisposable() - queue.async { - if let strongSelf = self { - let index = strongSelf.upgradedStarGiftsContext.subscribers.add({ upgradedGifts in - subscriber.putNext(upgradedGifts) - }) - - disposable.set(ActionDisposable { - if let strongSelf = self { - strongSelf.upgradedStarGiftsContext.subscribers.remove(index) - } - }) - } - } - return disposable - } - } - - private func notifyUpgradedStarGifts(_ upgradedStarGifts: [(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)]) { - for subscriber in self.upgradedStarGiftsContext.subscribers.copyItems() { - subscriber(upgradedStarGifts) - } - } - + func notifyDeletedMessages(messageIds: [MessageId]) { self.deletedMessagesPipe.putNext(messageIds.map { .messageId($0) }) } @@ -2143,12 +2108,6 @@ public final class AccountStateManager { } } - public func upgradedStarGifts() -> Signal<[(ProfileGiftsContext.State.StarGift, ProfileGiftsContext.State.StarGift)], NoError> { - return self.impl.signalWith { impl, subscriber in - return impl.upgradedStarGifts().start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) - } - } - func addCustomOperation(_ f: Signal) -> Signal { return self.impl.signalWith { impl, subscriber in return impl.addCustomOperation(f).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index 9109fdecc6..ba7b2a4dd4 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -137,6 +137,7 @@ public struct Namespaces { public static let recommendedApps: Int8 = 40 public static let starsReactionDefaultToPrivate: Int8 = 41 public static let cachedPremiumGiftCodeOptions: Int8 = 42 + public static let cachedProfileGifts: Int8 = 43 } public struct UnorderedItemList { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 68faf63ac7..bd1dde1685 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -130,8 +130,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { 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 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, upgradeStars: Int64?) - case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?) + 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 starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -253,9 +253,9 @@ 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) 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), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars")) + 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)) 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")) + 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: self = .unknown } @@ -548,7 +548,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "giveawayMsgId") } - case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, upgradeStars): + case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded): encoder.encodeInt32(44, forKey: "_rawValue") encoder.encodeObject(gift, forKey: "gift") if let convertStars { @@ -567,12 +567,14 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeBool(savedToProfile, forKey: "savedToProfile") encoder.encodeBool(converted, forKey: "converted") encoder.encodeBool(upgraded, forKey: "upgraded") + encoder.encodeBool(canUpgrade, forKey: "canUpgrade") if let upgradeStars { encoder.encodeInt64(upgradeStars, forKey: "upgradeStars") } else { encoder.encodeNil(forKey: "upgradeStars") } - case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars): + encoder.encodeBool(isRefunded, forKey: "isRefunded") + case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded): encoder.encodeInt32(45, forKey: "_rawValue") encoder.encodeObject(gift, forKey: "gift") encoder.encodeBool(isUpgrade, forKey: "isUpgrade") @@ -588,6 +590,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "transferStars") } + encoder.encodeBool(isRefunded, forKey: "isRefunded") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 2899205ec7..ecd058ecb1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -707,8 +707,17 @@ public enum UpgradeStarGiftError { case generic } -func _internal_upgradeStarGift(account: Account, prepaid: Bool, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal { - if prepaid { +func _internal_upgradeStarGift(account: Account, formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal { + if let formId { + let source: BotPaymentInvoiceSource = .starGiftUpgrade(keepOriginalInfo: keepOriginalInfo, messageId: messageId) + return _internal_sendStarsPaymentForm(account: account, formId: formId, source: source) + |> mapError { _ -> UpgradeStarGiftError in + return .generic + } + |> mapToSignal { _ in + return .complete() + } + } else { var flags: Int32 = 0 if keepOriginalInfo { flags |= (1 << 0) @@ -719,57 +728,35 @@ func _internal_upgradeStarGift(account: Account, prepaid: Bool, messageId: Engin } |> mapToSignal { updates in account.stateManager.addUpdates(updates) - - return account.stateManager.upgradedStarGifts() - |> castError(UpgradeStarGiftError.self) - |> take(until: { updates in - for update in updates { - if update.0.messageId == messageId { - return .init(passthrough: true, complete: true) - } - } - return .init(passthrough: false, complete: false) - }) - |> mapToSignal { updates in - for update in updates { - if update.0.messageId == messageId { - return .single(update.1) - } - } - return .complete() - } - } - } else { - let source: BotPaymentInvoiceSource = .starGiftUpgrade(keepOriginalInfo: keepOriginalInfo, messageId: messageId) - return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil) - |> mapError { _ -> UpgradeStarGiftError in - return .generic - } - |> mapToSignal { paymentForm in - return _internal_sendStarsPaymentForm(account: account, formId: paymentForm.id, source: source) - |> mapError { _ -> UpgradeStarGiftError in - return .generic - } - |> mapToSignal { _ in - return account.stateManager.upgradedStarGifts() - |> castError(UpgradeStarGiftError.self) - |> take(until: { updates in - for update in updates { - if update.0.messageId == messageId { - return .init(passthrough: true, complete: true) + for update in updates.allUpdates { + switch update { + case let .updateNewMessage(message, _, _): + if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) { + for media in message.media { + if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _) = action.action, case let .Id(messageId) = message.id { + return .single(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 .init(passthrough: false, complete: false) - }) - |> mapToSignal { updates in - for update in updates { - if update.0.messageId == messageId { - return .single(update.1) - } - } - return .complete() + default: + break } } + return .fail(.generic) } } } @@ -791,7 +778,49 @@ func _internal_starGiftUpgradePreview(account: Account, giftId: Int64) -> Signal } } -private var cachedAccountGifts: [EnginePeer.Id: [ProfileGiftsContext.State.StarGift]] = [:] +private final class CachedProfileGifts: Codable { + enum CodingKeys: String, CodingKey { + case gifts + case count + } + + var gifts: [ProfileGiftsContext.State.StarGift] + let count: Int32 + + init(gifts: [ProfileGiftsContext.State.StarGift], count: Int32) { + self.gifts = gifts + self.count = count + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.gifts = try container.decode([ProfileGiftsContext.State.StarGift].self, forKey: .gifts) + self.count = try container.decode(Int32.self, forKey: .count) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.gifts, forKey: .gifts) + try container.encode(self.count, forKey: .count) + } + + func render(transaction: Transaction) { + for i in 0 ..< self.gifts.count { + let gift = self.gifts[i] + if gift.fromPeer == nil, let fromPeerId = gift._fromPeerId, let peer = transaction.getPeer(fromPeerId) { + self.gifts[i] = gift.withFromPeer(EnginePeer(peer)) + } + } + } +} + +private func entryId(peerId: EnginePeer.Id) -> ItemCacheEntryId { + let cacheKey = ValueBoxKey(length: 8) + cacheKey.setInt64(0, value: peerId.toInt64()) + return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedProfileGifts, key: cacheKey) +} private final class ProfileGiftsContextImpl { private let queue: Queue @@ -799,6 +828,7 @@ private final class ProfileGiftsContextImpl { private let peerId: PeerId private let disposable = MetaDisposable() + private let cacheDisposable = MetaDisposable() private let actionDisposable = MetaDisposable() private var gifts: [ProfileGiftsContext.State.StarGift] = [] @@ -821,22 +851,37 @@ private final class ProfileGiftsContextImpl { deinit { self.disposable.dispose() + self.cacheDisposable.dispose() self.actionDisposable.dispose() } func loadMore() { + let peerId = self.peerId + let accountPeerId = self.account.peerId + let network = self.account.network + let postbox = self.account.postbox + if case let .ready(true, initialNextOffset) = self.dataState { - if self.gifts.isEmpty, self.peerId == self.account.peerId, let cachedGifts = cachedAccountGifts[self.peerId] { - self.gifts = cachedGifts + if self.gifts.isEmpty, initialNextOffset == nil { + self.cacheDisposable.set((self.account.postbox.transaction { transaction -> CachedProfileGifts? in + let cachedGifts = transaction.retrieveItemCacheEntry(id: entryId(peerId: peerId))?.get(CachedProfileGifts.self) + cachedGifts?.render(transaction: transaction) + return cachedGifts + } |> deliverOn(self.queue)).start(next: { [weak self] cachedGifts in + guard let self, let cachedGifts else { + return + } + if case .loading = self.dataState { + self.gifts = cachedGifts.gifts + self.count = cachedGifts.count + self.pushState() + } + })) } self.dataState = .loading self.pushState() - - let peerId = self.peerId - let accountPeerId = self.account.peerId - let network = self.account.network - let postbox = self.account.postbox + let signal: Signal<([ProfileGiftsContext.State.StarGift], Int32, String?), NoError> = self.account.postbox.transaction { transaction -> Api.InputUser? in return transaction.getPeer(peerId).flatMap(apiInputUser) } @@ -871,10 +916,15 @@ private final class ProfileGiftsContextImpl { guard let strongSelf = self else { return } - if initialNextOffset == nil, strongSelf.peerId == strongSelf.account.peerId { - cachedAccountGifts[strongSelf.peerId] = gifts + if initialNextOffset == nil { strongSelf.gifts = gifts - } else { + + strongSelf.cacheDisposable.set(strongSelf.account.postbox.transaction { transaction in + if let entry = CodableEntry(CachedProfileGifts(gifts: gifts, count: count)) { + transaction.putItemCacheEntry(id: entryId(peerId: peerId), entry: entry) + } + }.start()) + } else { for gift in gifts { strongSelf.gifts.append(gift) } @@ -920,9 +970,14 @@ private final class ProfileGiftsContextImpl { self.pushState() } - func upgradeStarGift(prepaid: Bool, messageId: EngineMessage.Id, keepOriginalInfo: Bool) { + func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) { self.actionDisposable.set( - _internal_upgradeStarGift(account: self.account, prepaid: prepaid, messageId: messageId, keepOriginalInfo: keepOriginalInfo).startStrict() + _internal_upgradeStarGift(account: self.account, formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo).startStrict(next: { [weak self] result in + guard let self else { + return + } + let _ = self + }) ) self.pushState() } @@ -935,7 +990,23 @@ private final class ProfileGiftsContextImpl { public final class ProfileGiftsContext { public struct State: Equatable { - public struct StarGift: Equatable { + public struct StarGift: Equatable, Codable { + enum CodingKeys: String, CodingKey { + case gift + case fromPeerId + case date + case text + case entities + case messageId + case nameHidden + case savedToProfile + case convertStars + case canUpgrade + case canExportDate + case upgradeStars + case transferStars + } + public let gift: TelegramCore.StarGift public let fromPeer: EnginePeer? public let date: Int32 @@ -950,6 +1021,76 @@ public final class ProfileGiftsContext { public let upgradeStars: Int64? public let transferStars: Int64? + fileprivate let _fromPeerId: EnginePeer.Id? + + public init ( + gift: TelegramCore.StarGift, + fromPeer: EnginePeer?, + date: Int32, + text: String?, + entities: [MessageTextEntity]?, + messageId: EngineMessage.Id?, + nameHidden: Bool, + savedToProfile: Bool, + convertStars: Int64?, + canUpgrade: Bool, + canExportDate: Int32?, + upgradeStars: Int64?, + transferStars: Int64? + ) { + self.gift = gift + self.fromPeer = fromPeer + self._fromPeerId = fromPeer?.id + self.date = date + self.text = text + self.entities = entities + self.messageId = messageId + self.nameHidden = nameHidden + self.savedToProfile = savedToProfile + self.convertStars = convertStars + self.canUpgrade = canUpgrade + self.canExportDate = canExportDate + self.upgradeStars = upgradeStars + self.transferStars = transferStars + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.gift = try container.decode(TelegramCore.StarGift.self, forKey: .gift) + self.fromPeer = nil + self._fromPeerId = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .fromPeerId) + self.date = try container.decode(Int32.self, forKey: .date) + self.text = try container.decodeIfPresent(String.self, forKey: .text) + self.entities = try container.decodeIfPresent([MessageTextEntity].self, forKey: .entities) + self.messageId = try container.decodeIfPresent(EngineMessage.Id.self, forKey: .messageId) + self.nameHidden = try container.decode(Bool.self, forKey: .nameHidden) + self.savedToProfile = try container.decode(Bool.self, forKey: .savedToProfile) + self.convertStars = try container.decodeIfPresent(Int64.self, forKey: .convertStars) + self.canUpgrade = try container.decode(Bool.self, forKey: .canUpgrade) + self.canExportDate = try container.decodeIfPresent(Int32.self, forKey: .canExportDate) + self.upgradeStars = try container.decodeIfPresent(Int64.self, forKey: .upgradeStars) + self.transferStars = try container.decodeIfPresent(Int64.self, forKey: .transferStars) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.gift, forKey: .gift) + try container.encodeIfPresent(self.fromPeer?.id, forKey: .fromPeerId) + try container.encode(self.date, forKey: .date) + try container.encodeIfPresent(self.text, forKey: .text) + try container.encodeIfPresent(self.entities, forKey: .entities) + try container.encodeIfPresent(self.messageId, forKey: .messageId) + try container.encode(self.nameHidden, forKey: .nameHidden) + try container.encode(self.savedToProfile, forKey: .savedToProfile) + try container.encodeIfPresent(self.convertStars, forKey: .convertStars) + try container.encode(self.canUpgrade, forKey: .canUpgrade) + try container.encodeIfPresent(self.canExportDate, forKey: .canExportDate) + try container.encodeIfPresent(self.upgradeStars, forKey: .upgradeStars) + try container.encodeIfPresent(self.transferStars, forKey: .transferStars) + } + public func withSavedToProfile(_ savedToProfile: Bool) -> StarGift { return StarGift( gift: self.gift, @@ -967,6 +1108,24 @@ public final class ProfileGiftsContext { transferStars: self.transferStars ) } + + fileprivate func withFromPeer(_ fromPeer: EnginePeer?) -> StarGift { + return StarGift( + gift: self.gift, + fromPeer: fromPeer, + date: self.date, + text: self.text, + entities: self.entities, + messageId: self.messageId, + nameHidden: self.nameHidden, + savedToProfile: savedToProfile, + convertStars: self.convertStars, + canUpgrade: self.canUpgrade, + canExportDate: self.canExportDate, + upgradeStars: self.upgradeStars, + transferStars: self.transferStars + ) + } } public enum DataState: Equatable { @@ -1027,9 +1186,9 @@ public final class ProfileGiftsContext { } } - public func upgradeStarGift(prepaid: Bool, messageId: EngineMessage.Id, keepOriginalInfo: Bool) { + public func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) { self.impl.with { impl in - impl.upgradeStarGift(prepaid: prepaid, messageId: messageId, keepOriginalInfo: keepOriginalInfo) + impl.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo) } } @@ -1055,6 +1214,7 @@ extension ProfileGiftsContext.State.StarGift { } else { self.fromPeer = nil } + self._fromPeerId = self.fromPeer?.id self.date = date if let message { @@ -1081,7 +1241,7 @@ extension ProfileGiftsContext.State.StarGift { self.nameHidden = (flags & (1 << 0)) != 0 self.savedToProfile = (flags & (1 << 5)) == 0 self.convertStars = convertStars - self.canUpgrade = (flags & (1 << 6)) != 0 + self.canUpgrade = (flags & (1 << 10)) != 0 self.canExportDate = canExportDate self.upgradeStars = upgradeStars self.transferStars = transferStars diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index 06dee162be..283a831b99 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -125,8 +125,8 @@ public extension TelegramEngine { return _internal_transferStarGift(account: self.account, prepaid: prepaid, messageId: messageId, peerId: peerId) } - public func upgradeStarGift(prepaid: Bool, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal { - return _internal_upgradeStarGift(account: self.account, prepaid: prepaid, messageId: messageId, keepOriginalInfo: keepOriginalInfo) + public func upgradeStarGift(formId: Int64?, messageId: EngineMessage.Id, keepOriginalInfo: Bool) -> Signal { + return _internal_upgradeStarGift(account: self.account, formId: formId, messageId: messageId, keepOriginalInfo: keepOriginalInfo) } public func starGiftUpgradePreview(giftId: Int64) -> Signal<[StarGift.UniqueGift.Attribute], NoError> { diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 7ec5ce8b36..d00b9ac2e9 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1066,7 +1066,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = mutableString case .prizeStars: attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor) - case let .starGift(gift, _, text, entities, _, _, _, _, _): + case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _): if !forAdditionalServiceMessage { 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())) @@ -1075,7 +1075,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = NSAttributedString(string: strings.Notification_Gift, font: titleFont, textColor: primaryTextColor) } } else if case let .generic(gift) = gift { - let starsPrice = strings.Notification_StarsGift_Stars(Int32(gift.price)) + var finalPrice = gift.price + if let upgradeStars { + finalPrice += upgradeStars + } + let starsPrice = strings.Notification_StarsGift_Stars(Int32(finalPrice)) var authorName = compactAuthorName var peerIds: [(Int, EnginePeer.Id?)] = [(0, message.author?.id)] if message.id.peerId.namespace == Namespaces.Peer.CloudUser && message.id.peerId.id._internalGetInt64Value() == 777000 { @@ -1090,7 +1094,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes) } } - case let .starGiftUnique(gift, isUpgrade, isTransferred, _, _, _): + case let .starGiftUnique(gift, isUpgrade, isTransferred, _, _, _, _): if case let .unique(gift) = gift { if !forAdditionalServiceMessage { attributedString = NSAttributedString(string: "\(gift.title) #\(gift.number)", font: titleFont, textColor: primaryTextColor) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD index 512d7f7c6d..b5039cdd81 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/BUILD @@ -32,6 +32,7 @@ swift_library( "//submodules/TelegramUI/Components/Chat/ChatMessageItemCommon", "//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/InvisibleInkDustNode", + "//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index eba460acc5..40f72151cc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -3,6 +3,7 @@ import UIKit import AsyncDisplayKit import Display import SwiftSignalKit +import ComponentFlow import TelegramCore import AccountContext import TelegramPresentationData @@ -22,6 +23,7 @@ import ChatMessageBubbleContentNode import ChatMessageItemCommon import TextNodeWithEntities import InvisibleInkDustNode +import PeerInfoCoverComponent private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? { return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false, forAdditionalServiceMessage: true) @@ -33,6 +35,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let backgroundMaskNode: ASImageNode private var linkHighlightingNode: LinkHighlightingNode? + private let patternView = ComponentView() private let mediaBackgroundMaskNode: ASImageNode private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? private let titleNode: TextNode @@ -43,6 +46,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let placeholderNode: StickerShimmerEffectNode private let animationNode: AnimatedStickerNode + private let modelTitleTextNode: TextNode + private let modelValueTextNode: TextNode + private let backdropTitleTextNode: TextNode + private let backdropValueTextNode: TextNode + private let symbolTitleTextNode: TextNode + private let symbolValueTextNode: TextNode + private let ribbonBackgroundNode: ASImageNode private let ribbonTextNode: TextNode @@ -50,6 +60,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let buttonNode: HighlightTrackingButtonNode private let buttonStarsNode: PremiumStarsNode private let buttonTitleNode: TextNode + private let buttonIconNode: ASImageNode private let moreTextNode: TextNode @@ -120,6 +131,25 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.textClippingNode = ASDisplayNode() self.textClippingNode.clipsToBounds = true + self.modelTitleTextNode = TextNode() + self.modelTitleTextNode.isUserInteractionEnabled = false + self.modelTitleTextNode.displaysAsynchronously = false + self.modelValueTextNode = TextNode() + self.modelValueTextNode.isUserInteractionEnabled = false + self.modelValueTextNode.displaysAsynchronously = false + self.backdropTitleTextNode = TextNode() + self.backdropTitleTextNode.isUserInteractionEnabled = false + self.backdropTitleTextNode.displaysAsynchronously = false + self.backdropValueTextNode = TextNode() + self.backdropValueTextNode.isUserInteractionEnabled = false + self.backdropValueTextNode.displaysAsynchronously = false + self.symbolTitleTextNode = TextNode() + self.symbolTitleTextNode.isUserInteractionEnabled = false + self.symbolTitleTextNode.displaysAsynchronously = false + self.symbolValueTextNode = TextNode() + self.symbolValueTextNode.isUserInteractionEnabled = false + self.symbolValueTextNode.displaysAsynchronously = false + self.buttonNode = HighlightTrackingButtonNode() self.buttonNode.clipsToBounds = true self.buttonNode.cornerRadius = 17.0 @@ -136,6 +166,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.buttonTitleNode.isUserInteractionEnabled = false self.buttonTitleNode.displaysAsynchronously = false + self.buttonIconNode = ASImageNode() + self.buttonIconNode.displaysAsynchronously = false + self.buttonIconNode.isUserInteractionEnabled = false + self.ribbonBackgroundNode = ASImageNode() self.ribbonBackgroundNode.displaysAsynchronously = false @@ -161,6 +195,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.buttonNode) self.buttonNode.addSubnode(self.buttonStarsNode) self.buttonNode.addSubnode(self.buttonTitleNode) + self.buttonNode.addSubnode(self.buttonIconNode) self.addSubnode(self.ribbonBackgroundNode) self.addSubnode(self.ribbonTextNode) @@ -292,6 +327,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let makeRibbonTextLayout = TextNode.asyncLayout(self.ribbonTextNode) let makeMeasureTextLayout = TextNode.asyncLayout(nil) let makeMoreTextLayout = TextNode.asyncLayout(self.moreTextNode) + + let makeModelTitleLayout = TextNode.asyncLayout(self.modelTitleTextNode) + let makeModelValueLayout = TextNode.asyncLayout(self.modelValueTextNode) + let makeBackdropTitleLayout = TextNode.asyncLayout(self.backdropTitleTextNode) + let makeBackdropValueLayout = TextNode.asyncLayout(self.backdropValueTextNode) + let makeSymbolTitleLayout = TextNode.asyncLayout(self.symbolTitleTextNode) + let makeSymbolValueLayout = TextNode.asyncLayout(self.symbolValueTextNode) let cachedMaskBackgroundImage = self.cachedMaskBackgroundImage @@ -303,7 +345,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in var giftSize = CGSize(width: 220.0, height: 240.0) - let incoming = item.message.effectivelyIncoming(item.context.account.peerId) + let incoming: Bool + if item.message.id.peerId == item.context.account.peerId && item.message.forwardInfo == nil { + incoming = true + } else { + incoming = item.message.effectivelyIncoming(item.context.account.peerId) + } let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId) @@ -314,12 +361,26 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { var animationFile: TelegramMediaFile? var title = item.presentationData.strings.Notification_PremiumGift_Title var text = "" + var subtitleColor = primaryTextColor var entities: [MessageTextEntity] = [] var buttonTitle = item.presentationData.strings.Notification_PremiumGift_View + var buttonIcon: String? var ribbonTitle = "" var hasServiceMessage = true var textSpacing: CGFloat = 0.0 var isStarGift = false + + var modelTitle: String? + var modelValue: String? + var backdropTitle: String? + var backdropValue: String? + var symbolTitle: String? + var symbolValue: String? + var uniqueBackgroundColor: UIColor? + var uniqueSecondBackgroundColor: UIColor? + var uniquePatternColor: UIColor? + var uniquePatternFile: TelegramMediaFile? + for media in item.message.media { if let action = media as? TelegramMediaAction { switch action.action { @@ -406,7 +467,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { buttonTitle = item.presentationData.strings.Notification_PremiumPrize_View hasServiceMessage = false } - case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, _, _): + case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, _, upgradeStars, isRefunded): if case let .generic(gift) = gift { isStarGift = true let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" @@ -415,7 +476,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { text = giftText entities = giftEntities ?? [] } else { - if incoming { + if isRefunded { + text = item.presentationData.strings.Notification_StarGift_Subtitle_Refunded + } else if incoming { if converted { text = item.presentationData.strings.Notification_StarGift_Subtitle_Converted(item.presentationData.strings.Notification_StarGift_Subtitle_Converted_Stars(Int32(convertStars ?? 0))).string } else if savedToProfile { @@ -461,26 +524,49 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } ribbonTitle = item.presentationData.strings.Notification_StarGift_OneOf(availabilityString).string } - if incoming { - buttonTitle = item.presentationData.strings.Notification_StarGift_View + if incoming, let upgradeStars, upgradeStars > 0, !upgraded { + buttonTitle = item.presentationData.strings.Notification_StarGift_Unpack + buttonIcon = "Premium/GiftUnpack" } else { - buttonTitle = "" + buttonTitle = item.presentationData.strings.Notification_StarGift_View } } - case let .starGiftUnique(gift, _, _, _, _, _): + case let .starGiftUnique(gift, _, _, _, _, _, isRefunded): if case let .unique(uniqueGift) = gift { isStarGift = true let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" title = item.presentationData.strings.Notification_StarGift_Title(authorName).string + text = "**\(uniqueGift.title) #\(uniqueGift.number)**" + ribbonTitle = item.presentationData.strings.Notification_StarGift_Gift + buttonTitle = item.presentationData.strings.Notification_StarGift_View + modelTitle = item.presentationData.strings.Notification_StarGift_Model + backdropTitle = item.presentationData.strings.Notification_StarGift_Backdrop + symbolTitle = item.presentationData.strings.Notification_StarGift_Symbol + for attribute in uniqueGift.attributes { - if case let .model(_, file, _) = attribute { + switch attribute { + case let .model(name, file, _): + modelValue = name animationFile = file + case let .backdrop(name, innerColor, outerColor, patternColor, _, _): + uniqueBackgroundColor = UIColor(rgb: UInt32(bitPattern: outerColor)) + uniqueSecondBackgroundColor = UIColor(rgb: UInt32(bitPattern: innerColor)) + uniquePatternColor = UIColor(rgb: UInt32(bitPattern: patternColor)) + backdropValue = name + subtitleColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3) + case let .pattern(name, file, _): + symbolValue = name + uniquePatternFile = file + default: break } } - //TODO:localize - ribbonTitle = "gift" - buttonTitle = item.presentationData.strings.Notification_StarGift_View + } else if isRefunded, case let .generic(gift) = gift { + isStarGift = true + let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" + title = item.presentationData.strings.Notification_StarGift_Title(authorName).string + text = item.presentationData.strings.Notification_StarGift_Subtitle_Refunded + animationFile = gift.file } default: break @@ -510,9 +596,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: Font.regular(13.0), linkFont: Font.regular(13.0), boldFont: Font.semibold(13.0), italicFont: Font.italic(13.0), boldItalicFont: Font.semiboldItalic(13.0), fixedFont: Font.monospace(13.0), blockQuoteFont: Font.regular(13.0), message: nil) } else { attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes( - body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), - bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor), - link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor), + body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: subtitleColor), + bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: subtitleColor), + link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: subtitleColor), linkAttribute: { url in return ("URL", url) } @@ -533,12 +619,57 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { clippedTextHeight = measuredTextLayout.size.height } } - + + let infoConstrainedSize = CGSize(width: (giftSize.width - 32.0) * 0.7, height: CGFloat.greatestFiniteMagnitude) + let modelTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if let modelTitle { + modelTitleLayoutAndApply = makeModelTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: modelTitle, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets())) + } else { + modelTitleLayoutAndApply = nil + } + let modelValueLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if let modelValue { + modelValueLayoutAndApply = makeModelValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: modelValue, font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets())) + } else { + modelValueLayoutAndApply = nil + } + + let backdropTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if let backdropTitle { + backdropTitleLayoutAndApply = makeBackdropTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: backdropTitle, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets())) + } else { + backdropTitleLayoutAndApply = nil + } + let backdropValueLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if let backdropValue { + backdropValueLayoutAndApply = makeBackdropValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: backdropValue, font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets())) + } else { + backdropValueLayoutAndApply = nil + } + + let symbolTitleLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if let symbolTitle { + symbolTitleLayoutAndApply = makeSymbolTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: symbolTitle, font: Font.regular(13.0), textColor: subtitleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets())) + } else { + symbolTitleLayoutAndApply = nil + } + let symbolValueLayoutAndApply: (TextNodeLayout, () -> TextNode)? + if let symbolValue { + symbolValueLayoutAndApply = makeSymbolValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: symbolValue, font: Font.semibold(13.0), textColor: primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: infoConstrainedSize, alignment: .center, cutout: nil, insets: UIEdgeInsets())) + } else { + symbolValueLayoutAndApply = nil + } + let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (ribbonTextLayout, ribbonTextApply) = makeRibbonTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: ribbonTitle, font: Font.semibold(11.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) giftSize.height = titleLayout.size.height + textSpacing + clippedTextHeight + 164.0 + + if let _ = modelTitle { + giftSize.height += 70.0 + } + if !buttonTitle.isEmpty { giftSize.height += 48.0 } @@ -614,6 +745,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.buttonNode.isHidden = buttonTitle.isEmpty strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty + strongSelf.buttonIconNode.isHidden = buttonIcon == nil if strongSelf.item == nil { strongSelf.animationNode.started = { [weak self] in @@ -677,7 +809,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let titleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - titleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 151.0), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame - let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight)) + let clippingTextFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0), y: titleFrame.maxY + textSpacing), size: CGSize(width: subtitleLayout.size.width, height: clippedTextHeight)) let subtitleFrame = CGRect(origin: .zero, size: subtitleLayout.size) strongSelf.subtitleNode.textNode.frame = subtitleFrame @@ -727,19 +859,97 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.dustNode = nil } - let buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) - strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((buttonSize.width - buttonTitleLayout.size.width) / 2.0), y: 8.0), size: buttonTitleLayout.size) + var middleX = mediaBackgroundFrame.width / 2.0 + if let (modelValueLayout, _) = modelValueLayoutAndApply, let (backdropValueLayout, _) = backdropValueLayoutAndApply, let (symbolValueLayout, _) = symbolValueLayoutAndApply { + let maxWidth = max(modelValueLayout.size.width, max(backdropValueLayout.size.width, symbolValueLayout.size.width)) + middleX = min(mediaBackgroundFrame.width - maxWidth - 16.0, middleX) + } + + let titleMaxX: CGFloat = mediaBackgroundFrame.minX + middleX - 2.0 + let valueMinX: CGFloat = mediaBackgroundFrame.minX + middleX + 3.0 + + if let (modelTitleLayout, modelTitleApply) = modelTitleLayoutAndApply { + if strongSelf.modelTitleTextNode.supernode == nil { + strongSelf.addSubnode(strongSelf.modelTitleTextNode) + } + let _ = modelTitleApply() + strongSelf.modelTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - modelTitleLayout.size.width, y: clippingTextFrame.maxY + 10.0), size: modelTitleLayout.size) + } - animation.animator.updateFrame(layer: strongSelf.buttonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: clippingTextFrame.maxY + 10.0), size: buttonSize), completion: nil) + if let (modelValueLayout, modelValueApply) = modelValueLayoutAndApply { + if strongSelf.modelValueTextNode.supernode == nil { + strongSelf.addSubnode(strongSelf.modelValueTextNode) + } + let _ = modelValueApply() + strongSelf.modelValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 10.0), size: modelValueLayout.size) + } + + if let (backdropTitleLayout, backdropTitleApply) = backdropTitleLayoutAndApply { + if strongSelf.backdropTitleTextNode.supernode == nil { + strongSelf.addSubnode(strongSelf.backdropTitleTextNode) + } + let _ = backdropTitleApply() + strongSelf.backdropTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - backdropTitleLayout.size.width, y: clippingTextFrame.maxY + 32.0), size: backdropTitleLayout.size) + } + + if let (backdropValueLayout, backdropValueApply) = backdropValueLayoutAndApply { + if strongSelf.backdropValueTextNode.supernode == nil { + strongSelf.addSubnode(strongSelf.backdropValueTextNode) + } + let _ = backdropValueApply() + strongSelf.backdropValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 32.0), size: backdropValueLayout.size) + } + + if let (symbolTitleLayout, symbolTitleApply) = symbolTitleLayoutAndApply { + if strongSelf.symbolTitleTextNode.supernode == nil { + strongSelf.addSubnode(strongSelf.symbolTitleTextNode) + } + let _ = symbolTitleApply() + strongSelf.symbolTitleTextNode.frame = CGRect(origin: CGPoint(x: titleMaxX - symbolTitleLayout.size.width, y: clippingTextFrame.maxY + 54.0), size: symbolTitleLayout.size) + } + + if let (symbolValueLayout, symbolValueApply) = symbolValueLayoutAndApply { + if strongSelf.symbolValueTextNode.supernode == nil { + strongSelf.addSubnode(strongSelf.symbolValueTextNode) + } + let _ = symbolValueApply() + strongSelf.symbolValueTextNode.frame = CGRect(origin: CGPoint(x: valueMinX, y: clippingTextFrame.maxY + 54.0), size: symbolValueLayout.size) + } + + var buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) + var buttonOriginY = clippingTextFrame.maxY + 10.0 + if modelTitleLayoutAndApply != nil { + buttonOriginY = clippingTextFrame.maxY + 80.0 + } + if let buttonIcon { + buttonSize.width += 15.0 + if strongSelf.buttonIconNode.image == nil { + strongSelf.buttonIconNode.image = generateTintedImage(image: UIImage(bundleImageName: buttonIcon), color: .white) + } + } + strongSelf.buttonTitleNode.frame = CGRect(origin: CGPoint(x: 19.0, y: 8.0), size: buttonTitleLayout.size) + strongSelf.buttonIconNode.frame = CGRect(origin: CGPoint(x: buttonSize.width - 30.0, y: 9.0), size: CGSize(width: 14.0, height: 14.0)) + + animation.animator.updateFrame(layer: strongSelf.buttonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: buttonOriginY), size: buttonSize), completion: nil) strongSelf.buttonStarsNode.frame = CGRect(origin: .zero, size: buttonSize) if ribbonTextLayout.size.width > 0.0 { if strongSelf.ribbonBackgroundNode.image == nil { - let ribbonImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/GiftRibbon"), color: overlayColor) - strongSelf.ribbonBackgroundNode.image = ribbonImage + if let uniqueBackgroundColor { + let colors = [ + uniqueBackgroundColor.withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89), + uniqueBackgroundColor.withMultiplied(hue: 1.01, saturation: 1.22, brightness: 1.04) + ] + strongSelf.ribbonBackgroundNode.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: colors, direction: .mirroredDiagonal) + } else { + strongSelf.ribbonBackgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/GiftRibbon"), color: overlayColor) + } } if let ribbonImage = strongSelf.ribbonBackgroundNode.image { - let ribbonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.maxX - ribbonImage.size.width + 2.0, y: mediaBackgroundFrame.minY - 2.0), size: ribbonImage.size) + var ribbonFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.maxX - ribbonImage.size.width + 2.0, y: mediaBackgroundFrame.minY - 2.0), size: ribbonImage.size) + if let _ = uniqueBackgroundColor { + ribbonFrame = ribbonFrame.offsetBy(dx: -4.0, dy: 4.0) + } strongSelf.ribbonBackgroundNode.frame = ribbonFrame strongSelf.ribbonTextNode.transform = CATransform3DMakeRotation(.pi / 4.0, 0.0, 0.0, 1.0) @@ -757,7 +967,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } if let backgroundContent = strongSelf.mediaBackgroundContent { - if ribbonTextLayout.size.width > 0.0 { + if ribbonTextLayout.size.width > 0.0, uniqueBackgroundColor == nil { let backgroundMaskFrame = mediaBackgroundFrame.insetBy(dx: -2.0, dy: -2.0) backgroundContent.frame = backgroundMaskFrame animation.animator.updateFrame(layer: backgroundContent.layer, frame: backgroundMaskFrame, completion: nil) @@ -787,6 +997,36 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } + if let uniqueBackgroundColor, let uniqueSecondBackgroundColor, let uniquePatternColor, let uniquePatternFile { + let patternInset: CGFloat = 4.0 + let patternSize = CGSize(width: mediaBackgroundFrame.width - patternInset * 2.0, height: mediaBackgroundFrame.height - patternInset * 2.0) + let files: [Int64: TelegramMediaFile] = [uniquePatternFile.fileId.id: uniquePatternFile] + let _ = strongSelf.patternView.update( + transition: .immediate, + component: AnyComponent(PeerInfoCoverComponent( + context: item.context, + subject: .custom(uniqueBackgroundColor, uniqueSecondBackgroundColor, uniquePatternColor, uniquePatternFile.fileId.id), + files: files, + isDark: false, + avatarCenter: CGPoint(x: patternSize.width / 2.0, y: 106.0), + avatarScale: 1.0, + defaultHeight: patternSize.height, + avatarTransitionFraction: 0.0, + patternTransitionFraction: 0.0 + )), + environment: {}, + containerSize: patternSize + ) + if let backgroundView = strongSelf.patternView.view { + if backgroundView.superview == nil { + backgroundView.layer.cornerRadius = 20.0 + backgroundView.clipsToBounds = true + strongSelf.view.insertSubview(backgroundView, belowSubview: strongSelf.titleNode.view) + } + backgroundView.frame = CGRect(origin: .zero, size: patternSize).offsetBy(dx: mediaBackgroundFrame.minX + patternInset, dy: mediaBackgroundFrame.minY + patternInset) + } + } + let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) if let (offset, image) = backgroundMaskImage { if strongSelf.backgroundNode == nil { diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 4eefdbb859..b79872b7a1 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -909,7 +909,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var titleFrame: CGRect if size.height > 40.0 { var titleInsets: UIEdgeInsets = .zero - if verifiedIconWidth > 0.0 { + if case .emojiStatus = self.titleVerifiedIcon, verifiedIconWidth > 0.0 { titleInsets.left = verifiedIconWidth + 2.0 } @@ -951,7 +951,18 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var nextIconX: CGFloat = titleFrame.width - self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) + var verifiedIconX: CGFloat + if case .emojiStatus = self.titleVerifiedIcon { + verifiedIconX = 0.0 + } else { + verifiedIconX = nextIconX - titleVerifiedSize.width + } + + self.titleVerifiedIconView.frame = CGRect(origin: CGPoint(x: verifiedIconX, y: floor((titleFrame.height - titleVerifiedSize.height) / 2.0)), size: titleVerifiedSize) + if case .emojiStatus = self.titleVerifiedIcon { + } else { + nextIconX -= titleVerifiedSize.width + } self.titleCredibilityIconView.frame = CGRect(origin: CGPoint(x: nextIconX - titleCredibilitySize.width, y: floor((titleFrame.height - titleCredibilitySize.height) / 2.0)), size: titleCredibilitySize) nextIconX -= titleCredibilitySize.width diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD index 34ea9fff0c..019908ec4f 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/BUILD @@ -26,6 +26,7 @@ swift_library( "//submodules/Components/ViewControllerComponent", "//submodules/Components/BundleIconComponent", "//submodules/Components/MultilineTextComponent", + "//submodules/Components/MultilineTextWithEntitiesComponent", "//submodules/Components/BalancedTextComponent", "//submodules/TelegramUI/Components/ListSectionComponent", "//submodules/TelegramUI/Components/ListActionItemComponent", @@ -45,6 +46,7 @@ swift_library( "//submodules/InAppPurchaseManager", "//submodules/Components/BlurredBackgroundComponent", "//submodules/ProgressNavigationButtonNode", + "//submodules/TelegramUI/Components/Gifts/GiftViewScreen", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift index b3dec0d005..f7b3331e93 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/ChatGiftPreviewItem.swift @@ -143,6 +143,9 @@ final class ChatGiftPreviewItem: ListViewItem, ItemListItem, ListItemComponentAd if lhs.entities != rhs.entities { return false } + if lhs.includeUpgrade != rhs.includeUpgrade { + return false + } return true } } @@ -224,7 +227,7 @@ final class ChatGiftPreviewItemNode: ListViewItemNode { case let .starGift(gift): media = [ TelegramMediaAction( - action: .starGift(gift: .generic(gift), convertStars: gift.convertStars, text: item.text, entities: item.entities, nameHidden: false, savedToProfile: false, converted: false, upgraded: false, upgradeStars: item.includeUpgrade ? 0 : gift.upgradeStars) + 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) ) ] } diff --git a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift index af940f77a8..e382678172 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftSetupScreen/Sources/GiftSetupScreen.swift @@ -13,6 +13,7 @@ import AccountContext import ComponentFlow import ViewControllerComponent import MultilineTextComponent +import MultilineTextWithEntitiesComponent import BalancedTextComponent import ListSectionComponent import ListActionItemComponent @@ -32,6 +33,7 @@ import InAppPurchaseManager import BlurredBackgroundComponent import ProgressNavigationButtonNode import Markdown +import GiftViewScreen final class GiftSetupScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -131,6 +133,7 @@ final class GiftSetupScreenComponent: Component { } } private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil) + private let previewPromise = Promise<[StarGift.UniqueGift.Attribute]?>(nil) private var cachedChevronImage: (UIImage, PresentationTheme)? @@ -578,7 +581,7 @@ final class GiftSetupScreenComponent: Component { } ) - if case .starGift = component.subject { + if case let .starGift(gift) = component.subject { self.optionsDisposable = (component.context.engine.payments.starsTopUpOptions() |> deliverOnMainQueue).start(next: { [weak self] options in guard let self else { @@ -586,6 +589,13 @@ final class GiftSetupScreenComponent: Component { } self.options = options }) + + if let _ = gift.upgradeStars { + self.previewPromise.set( + component.context.engine.payments.starGiftUpgradePreview(giftId: gift.id) + |> map(Optional.init) + ) + } } } @@ -845,6 +855,13 @@ final class GiftSetupScreenComponent: Component { upgradeFooterText.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: upgradeFooterText.string)) } + let upgradeAttributedText = NSMutableAttributedString(string: environment.strings.Gift_Send_Upgrade("#\(upgradeStars)").string, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor) + let range = (upgradeAttributedText.string as NSString).range(of: "#") + if range.location != NSNotFound { + upgradeAttributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) + upgradeAttributedText.addAttribute(.baselineOffset, value: 1.0, range: range) + } + let upgradeSectionSize = self.upgradeSection.update( transition: transition, component: AnyComponent(ListSectionComponent( @@ -852,20 +869,47 @@ final class GiftSetupScreenComponent: Component { header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(upgradeFooterText), - maximumNumberOfLines: 0 + maximumNumberOfLines: 0, + highlightColor: environment.theme.list.itemAccentColor.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: { [weak self] _, _ in + guard let self else { + return + } + let _ = (self.previewPromise.get() + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] attributes in + guard let self, let component = self.component, let controller = self.environment?.controller(), let attributes else { + return + } + let previewController = GiftViewScreen( + context: component.context, + subject: .upgradePreview(attributes, peerName) + ) + controller.push(previewController) + }) + } )), items: [ AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( theme: environment.theme, title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.Gift_Send_Upgrade("\(upgradeStars)").string, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - ))), + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent( + MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + placeholderColor: environment.theme.list.mediaPlaceholderColor, + text: .plain(upgradeAttributedText) + ) + )), ], alignment: .left, spacing: 2.0)), accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.includeUpgrade, action: { [weak self] _ in guard let self else { @@ -982,7 +1026,11 @@ final class GiftSetupScreenComponent: Component { let amountString = product.price buttonString = "\(environment.strings.Gift_Send_Send) \(amountString)" case let .starGift(starGift): - let amountString = presentationStringsFormattedNumber(Int32(starGift.price), presentationData.dateTimeFormat.groupingSeparator) + var finalPrice: Int64 = starGift.price + if self.includeUpgrade, let upgradePrice = starGift.upgradeStars { + finalPrice += upgradePrice + } + let amountString = presentationStringsFormattedNumber(Int32(finalPrice), presentationData.dateTimeFormat.groupingSeparator) buttonString = "\(environment.strings.Gift_Send_Send) # \(amountString)" if let availability = starGift.availability, availability.remains == 0 { buttonIsEnabled = false diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftCompositionComponent.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftCompositionComponent.swift index afb69d18bd..8f084fc3f8 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftCompositionComponent.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftCompositionComponent.swift @@ -14,6 +14,13 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode final class GiftCompositionComponent: Component { + public class ExternalState { + public fileprivate(set) var previewPatternColor: UIColor? + public init() { + self.previewPatternColor = nil + } + } + enum Subject: Equatable { case generic(TelegramMediaFile) case unique(StarGift.UniqueGift) @@ -23,15 +30,21 @@ final class GiftCompositionComponent: Component { let context: AccountContext let theme: PresentationTheme let subject: Subject + let externalState: ExternalState + let requestUpdate: () -> Void init( context: AccountContext, theme: PresentationTheme, - subject: Subject + subject: Subject, + externalState: ExternalState, + requestUpdate: @escaping () -> Void ) { self.context = context self.theme = theme self.subject = subject + self.externalState = externalState + self.requestUpdate = requestUpdate } static func ==(lhs: GiftCompositionComponent, rhs: GiftCompositionComponent) -> Bool { @@ -193,6 +206,8 @@ final class GiftCompositionComponent: Component { self.previewTimer?.start() } } + + component.externalState.previewPatternColor = secondBackgroundColor var animateTransition = false if self.animatePreviewTransition { @@ -200,6 +215,10 @@ final class GiftCompositionComponent: Component { self.animatePreviewTransition = false } else if let previousComponent, case .preview = previousComponent.subject, case .unique = component.subject { animateTransition = true + } else if let previousComponent, case .generic = previousComponent.subject, case .preview = component.subject { + animateTransition = true + } else if let previousComponent, case .preview = previousComponent.subject, case .generic = component.subject { + animateTransition = true } if let backgroundColor { @@ -235,7 +254,7 @@ final class GiftCompositionComponent: Component { backgroundTransition.setFrame(view: backgroundView, frame: CGRect(origin: .zero, size: availableSize)) } } else if let backgroundView = self.background.view, backgroundView.superview != nil { - backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, completion: { _ in + backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in backgroundView.removeFromSuperview() }) } @@ -264,7 +283,6 @@ final class GiftCompositionComponent: Component { if let startFromIndex { animationNode.play(firstFrame: false, fromIndex: startFromIndex) - //animationNode.seekTo(.frameIndex(startFromIndex)) } else { animationNode.playLoop() } @@ -276,31 +294,6 @@ final class GiftCompositionComponent: Component { } } } - -// if self.animationLayer == nil, let animationFile { -// let emoji = ChatTextInputTextCustomEmojiAttribute( -// interactivelySelectedFromPackId: nil, -// fileId: animationFile.fileId.id, -// file: animationFile -// ) -// -// let animationLayer = InlineStickerItemLayer( -// context: .account(component.context), -// userLocation: .other, -// attemptSynchronousLoad: false, -// emoji: emoji, -// file: animationFile, -// cache: component.context.animationCache, -// renderer: component.context.animationRenderer, -// unique: true, -// placeholderColor: component.theme.list.mediaPlaceholderColor, -// pointSize: CGSize(width: iconSize.width * 1.2, height: iconSize.height * 1.2), -// loopCount: 1 -// ) -// animationLayer.isVisibleForAnimations = true -// self.animationLayer = animationLayer -// self.layer.addSublayer(animationLayer) -// } 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)) } diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index cd50c72e75..75cbba5bbb 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -51,7 +51,7 @@ private final class GiftViewSheetContent: CombinedComponent { init( context: AccountContext, subject: GiftViewScreen.Subject, - cancel: @escaping (Bool) -> Void, + cancel: @escaping (Bool) -> Void, openPeer: @escaping (EnginePeer) -> Void, updateSavedToProfile: @escaping (Bool) -> Void, convertToStars: @escaping () -> Void, @@ -108,11 +108,13 @@ private final class GiftViewSheetContent: CombinedComponent { var inProgress = false var inUpgradePreview = false + var upgradeForm: BotPaymentForm? + var upgradeDisposable: Disposable? + var sampleGiftAttributes: [StarGift.UniqueGift.Attribute]? - var sampleDisposable: Disposable? + let sampleDisposable = DisposableSet() var keepOriginalInfo = false - var upgradeDisposable: Disposable? private var optionsDisposable: Disposable? private(set) var options: [StarsTopUpOption] = [] { @@ -151,15 +153,40 @@ private final class GiftViewSheetContent: CombinedComponent { break } } - } else if case let .generic(gift) = arguments.gift, let _ = arguments.upgradeStars { - self.sampleDisposable = (context.engine.payments.starGiftUpgradePreview(giftId: gift.id) - |> deliverOnMainQueue).start(next: { [weak self] attributes in - guard let self else { - return + } else if case let .generic(gift) = arguments.gift { + if arguments.canUpgrade || arguments.upgradeStars != nil { + self.sampleDisposable.add((context.engine.payments.starGiftUpgradePreview(giftId: gift.id) + |> deliverOnMainQueue).start(next: { [weak self] attributes in + guard let self else { + return + } + self.sampleGiftAttributes = attributes + + for attribute in attributes { + switch attribute { + case let .model(_, file, _): + self.sampleDisposable.add(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start()) + case let .pattern(_, file, _): + self.sampleDisposable.add(freeMediaFileResourceInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start()) + default: + break + } + } + + self.updated() + })) + + if arguments.upgradeStars == nil, let messageId = arguments.messageId { + self.upgradeDisposable = (context.engine.payments.fetchBotPaymentForm(source: .starGiftUpgrade(keepOriginalInfo: false, messageId: messageId), themeParams: nil) + |> deliverOnMainQueue).start(next: { [weak self] paymentForm in + guard let self else { + return + } + self.upgradeForm = paymentForm + self.updated() + }) } - self.sampleGiftAttributes = attributes - self.updated() - }) + } } self.disposable = combineLatest(queue: Queue.mainQueue(), context.engine.data.get(EngineDataMap( @@ -209,7 +236,7 @@ private final class GiftViewSheetContent: CombinedComponent { deinit { self.disposable?.dispose() - self.sampleDisposable?.dispose() + self.sampleDisposable.dispose() self.upgradeDisposable?.dispose() } @@ -217,20 +244,22 @@ private final class GiftViewSheetContent: CombinedComponent { guard let _ = self.subject.arguments?.upgradeStars else { return } + self.context.starsContext?.load(force: false) + self.inUpgradePreview = true self.updated(transition: .spring(duration: 0.4)) } func commitUpgrade() { - guard let arguments = self.subject.arguments, let messageId = arguments.messageId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState, let upgradeStars = arguments.upgradeStars else { + guard let arguments = self.subject.arguments, let messageId = arguments.messageId, let starsContext = self.context.starsContext, let starsState = starsContext.currentState else { return } let peerId = arguments.peerId - let proceed: (Bool) -> Void = { prepaid in + let proceed: (Int64?) -> Void = { formId in self.inProgress = true self.updated() - let _ = (self.context.engine.payments.upgradeStarGift(prepaid: prepaid, messageId: messageId, keepOriginalInfo: self.keepOriginalInfo) + let _ = (self.context.engine.payments.upgradeStarGift(formId: formId, messageId: messageId, keepOriginalInfo: self.keepOriginalInfo) |> deliverOnMainQueue).start(next: { [weak self] result in guard let self, let controller = self.getController() as? GiftViewScreen else { return @@ -245,8 +274,10 @@ private final class GiftViewSheetContent: CombinedComponent { }) } - if upgradeStars > 0 { - if starsState.balance < StarsAmount(value: upgradeStars, nanos: 0) { + if let upgradeStars = arguments.upgradeStars, upgradeStars > 0 { + proceed(nil) + } else if let upgradeForm = self.upgradeForm, let price = upgradeForm.invoice.prices.first?.amount { + if starsState.balance < StarsAmount(value: price, nanos: 0) { let _ = (self.optionsPromise.get() |> filter { $0 != nil } |> take(1) @@ -258,21 +289,19 @@ private final class GiftViewSheetContent: CombinedComponent { context: self.context, starsContext: starsContext, options: options ?? [], - purpose: .upgradeStarGift(requiredStars: upgradeStars), + purpose: .upgradeStarGift(requiredStars: price), completion: { [weak starsContext] stars in starsContext?.add(balance: StarsAmount(value: stars, nanos: 0)) Queue.mainQueue().after(2.0) { - proceed(false) + proceed(upgradeForm.id) } } ) controller.push(purchaseController) }) } else { - proceed(false) + proceed(upgradeForm.id) } - } else { - proceed(true) } } } @@ -298,6 +327,8 @@ private final class GiftViewSheetContent: CombinedComponent { let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: []) + let giftCompositionExternalState = GiftCompositionComponent.ExternalState() + return { context in let environment = context.environment[ViewControllerComponentContainer.Environment.self].value @@ -326,6 +357,8 @@ private final class GiftViewSheetContent: CombinedComponent { var date: Int32? var soldOut = false var nameHidden = false + var upgraded = false + var canUpgrade = false var upgradeStars: Int64? var uniqueGift: StarGift.UniqueGift? if case let .soldOutGift(gift) = subject { @@ -351,6 +384,8 @@ private final class GiftViewSheetContent: CombinedComponent { converted = arguments.converted giftId = gift.id date = arguments.date + upgraded = arguments.upgraded + canUpgrade = arguments.canUpgrade upgradeStars = arguments.upgradeStars case let .unique(gift): stars = 0 @@ -387,11 +422,27 @@ private final class GiftViewSheetContent: CombinedComponent { state.cachedOverlayCloseImage = closeOverlayImage } + var showUpgradePreview = false + if state.inUpgradePreview, let _ = state.sampleGiftAttributes { + showUpgradePreview = true + } else if case .upgradePreview = component.subject { + showUpgradePreview = true + } + + let cancel = component.cancel let closeButton = closeButton.update( component: Button( - content: AnyComponent(Image(image: state.inUpgradePreview || uniqueGift != nil ? closeOverlayImage : closeImage)), - action: { [weak component] in - component?.cancel(true) + content: AnyComponent(Image(image: showUpgradePreview || uniqueGift != nil ? closeOverlayImage : closeImage)), + action: { [weak state] in + guard let state else { + return + } + if state.inUpgradePreview { + state.inUpgradePreview = false + state.updated(transition: .spring(duration: 0.4)) + } else { + cancel(true) + } } ), availableSize: CGSize(width: 30.0, height: 30.0), @@ -405,9 +456,12 @@ private final class GiftViewSheetContent: CombinedComponent { if let uniqueGift { animationHeight = 240.0 animationSubject = .unique(uniqueGift) - } else if state.inUpgradePreview, let sampleGiftAttributes = state.sampleGiftAttributes { + } else if state.inUpgradePreview, let attributes = state.sampleGiftAttributes { animationHeight = 258.0 - animationSubject = .preview(sampleGiftAttributes) + animationSubject = .preview(attributes) + } else if case let .upgradePreview(attributes, _) = component.subject { + animationHeight = 258.0 + animationSubject = .preview(attributes) } else if let animationFile { animationHeight = 210.0 animationSubject = .generic(animationFile) @@ -420,7 +474,11 @@ private final class GiftViewSheetContent: CombinedComponent { component: GiftCompositionComponent( context: component.context, theme: environment.theme, - subject: animationSubject + subject: animationSubject, + externalState: giftCompositionExternalState, + requestUpdate: { [weak state] in + state?.updated() + } ), availableSize: CGSize(width: context.availableSize.width, height: animationHeight), transition: .immediate @@ -430,13 +488,31 @@ private final class GiftViewSheetContent: CombinedComponent { ) } originY += animationHeight - - - if state.inUpgradePreview, let _ = state.sampleGiftAttributes { + + if showUpgradePreview { + let title: String + let description: String + let uniqueText: String + let transferableText: String + let tradableText: String + if case let .upgradePreview(_, name) = component.subject { + title = environment.strings.Gift_Upgrade_IncludeTitle + description = environment.strings.Gift_Upgrade_IncludeDescription(name).string + uniqueText = strings.Gift_Upgrade_Unique_IncludeDescription + transferableText = strings.Gift_Upgrade_Transferable_IncludeDescription + tradableText = strings.Gift_Upgrade_Tradable_IncludeDescription + } else { + title = environment.strings.Gift_Upgrade_Title + description = environment.strings.Gift_Upgrade_Description + uniqueText = strings.Gift_Upgrade_Unique_Description + transferableText = strings.Gift_Upgrade_Transferable_Description + tradableText = strings.Gift_Upgrade_Tradable_Description + } + let upgradeTitle = upgradeTitle.update( component: MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.Gift_Upgrade_Title, + string: title, font: Font.bold(20.0), textColor: .white, paragraphAlignment: .center @@ -447,18 +523,18 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) - context.add(upgradeTitle - .position(CGPoint(x: context.availableSize.width / 2.0, y: 191.0)) - .appear(.default(alpha: true)) - .disappear(.default(alpha: true)) - ) - + let descriptionColor: UIColor + if let previewPatternColor = giftCompositionExternalState.previewPatternColor { + descriptionColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3) + } else { + descriptionColor = UIColor.white.withAlphaComponent(0.6) + } let upgradeDescription = upgradeDescription.update( component: BalancedTextComponent( text: .plain(NSAttributedString( - string: environment.strings.Gift_Upgrade_Description, + string: description, font: Font.regular(13.0), - textColor: UIColor.white.withAlphaComponent(0.6), + textColor: descriptionColor, paragraphAlignment: .center )), horizontalAlignment: .center, @@ -468,8 +544,18 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) + + let spacing: CGFloat = 6.0 + let totalHeight: CGFloat = upgradeTitle.size.height + spacing + upgradeDescription.size.height + + context.add(upgradeTitle + .position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 - totalHeight / 2.0 + upgradeTitle.size.height / 2.0))) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + context.add(upgradeDescription - .position(CGPoint(x: context.availableSize.width / 2.0, y: 208.0 + upgradeDescription.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 + totalHeight / 2.0 - upgradeDescription.size.height / 2.0))) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) ) @@ -486,7 +572,7 @@ private final class GiftViewSheetContent: CombinedComponent { component: AnyComponent(ParagraphComponent( title: strings.Gift_Upgrade_Unique_Title, titleColor: textColor, - text: strings.Gift_Upgrade_Unique_Description, + text: uniqueText, textColor: secondaryTextColor, accentColor: linkColor, iconName: "Premium/Collectible/Unique", @@ -500,7 +586,7 @@ private final class GiftViewSheetContent: CombinedComponent { component: AnyComponent(ParagraphComponent( title: strings.Gift_Upgrade_Transferable_Title, titleColor: textColor, - text: strings.Gift_Upgrade_Transferable_Description, + text: transferableText, textColor: secondaryTextColor, accentColor: linkColor, iconName: "Premium/Collectible/Transferable", @@ -514,7 +600,7 @@ private final class GiftViewSheetContent: CombinedComponent { component: AnyComponent(ParagraphComponent( title: strings.Gift_Upgrade_Tradable_Title, titleColor: textColor, - text: strings.Gift_Upgrade_Tradable_Description, + text: tradableText, textColor: secondaryTextColor, accentColor: linkColor, iconName: "Premium/Collectible/Tradable", @@ -538,55 +624,59 @@ private final class GiftViewSheetContent: CombinedComponent { originY += upgradePerks.size.height originY += 16.0 - let checkTheme = CheckComponent.Theme( - backgroundColor: theme.list.itemCheckColors.fillColor, - strokeColor: theme.list.itemCheckColors.foregroundColor, - borderColor: theme.list.itemCheckColors.strokeColor, - overlayBorder: false, - hasInset: false, - hasShadow: false - ) - let keepInfoText: String - if let nameHidden = subject.arguments?.nameHidden, nameHidden { - keepInfoText = strings.Gift_Upgrade_AddMyName + if case .upgradePreview = component.subject { + } else { - keepInfoText = text != nil ? strings.Gift_Upgrade_AddNameAndComment : strings.Gift_Upgrade_AddName + let checkTheme = CheckComponent.Theme( + backgroundColor: theme.list.itemCheckColors.fillColor, + strokeColor: theme.list.itemCheckColors.foregroundColor, + borderColor: theme.list.itemCheckColors.strokeColor, + overlayBorder: false, + hasInset: false, + hasShadow: false + ) + let keepInfoText: String + if let nameHidden = subject.arguments?.nameHidden, nameHidden { + keepInfoText = strings.Gift_Upgrade_AddMyName + } else { + keepInfoText = text != nil ? strings.Gift_Upgrade_AddNameAndComment : strings.Gift_Upgrade_AddName + } + let upgradeKeepName = upgradeKeepName.update( + component: PlainButtonComponent( + content: AnyComponent(HStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent( + theme: checkTheme, + size: CGSize(width: 18.0, height: 18.0), + selected: state.keepOriginalInfo + ))), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: keepInfoText, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor)) + ))) + ], + spacing: 10.0 + )), + effectAlignment: .center, + action: { [weak state] in + guard let state else { + return + } + state.keepOriginalInfo = !state.keepOriginalInfo + state.updated(transition: .easeInOut(duration: 0.2)) + }, + animateAlpha: false, + animateScale: false + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 1000.0), + transition: context.transition + ) + context.add(upgradeKeepName + .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + upgradeKeepName.size.height / 2.0)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + originY += upgradeKeepName.size.height + originY += 18.0 } - let upgradeKeepName = upgradeKeepName.update( - component: PlainButtonComponent( - content: AnyComponent(HStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(CheckComponent( - theme: checkTheme, - size: CGSize(width: 18.0, height: 18.0), - selected: state.keepOriginalInfo - ))), - AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: keepInfoText, font: Font.regular(13.0), textColor: theme.list.itemSecondaryTextColor)) - ))) - ], - spacing: 10.0 - )), - effectAlignment: .center, - action: { [weak state] in - guard let state else { - return - } - state.keepOriginalInfo = !state.keepOriginalInfo - state.updated(transition: .easeInOut(duration: 0.2)) - }, - animateAlpha: false, - animateScale: false - ), - availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 1000.0), - transition: context.transition - ) - context.add(upgradeKeepName - .position(CGPoint(x: context.availableSize.width / 2.0, y: originY + upgradeKeepName.size.height / 2.0)) - .appear(.default(alpha: true)) - .disappear(.default(alpha: true)) - ) - originY += upgradeKeepName.size.height - originY += 18.0 } else { var descriptionText: String if let uniqueGift { @@ -595,12 +685,18 @@ private final class GiftViewSheetContent: CombinedComponent { } else if soldOut { descriptionText = strings.Gift_View_UnavailableDescription } else if incoming { - if let convertStars { + if let convertStars, !upgraded { if !converted { - descriptionText = strings.Gift_View_KeepUpgradeOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string + if canUpgrade || upgradeStars != nil { + descriptionText = strings.Gift_View_KeepUpgradeOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string + } else { + descriptionText = strings.Gift_View_KeepOrConvertDescription(strings.Gift_View_KeepOrConvertDescription_Stars(Int32(convertStars))).string + } } else { descriptionText = strings.Gift_View_ConvertedDescription(strings.Gift_View_ConvertedDescription_Stars(Int32(convertStars))).string } + } else if (canUpgrade || upgradeStars != nil) && !upgraded { + descriptionText = strings.Gift_View_KeepOrUpgradeDescription } else { descriptionText = strings.Gift_View_BotDescription } @@ -660,17 +756,13 @@ private final class GiftViewSheetContent: CombinedComponent { let textFont: UIFont let textColor: UIColor - if let uniqueGift { + if let _ = uniqueGift { textFont = Font.regular(13.0) - - var giftTextColor: UIColor? - for attribute in uniqueGift.attributes { - if case let .backdrop(_, _, _, _, textColor, _) = attribute { - giftTextColor = UIColor(rgb: UInt32(bitPattern: textColor)) - break - } + if let previewPatternColor = giftCompositionExternalState.previewPatternColor { + textColor = previewPatternColor.withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3) + } else { + textColor = UIColor.white.withAlphaComponent(0.6) } - textColor = giftTextColor ?? UIColor.white.withAlphaComponent(0.6) } else { textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0) textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor @@ -1043,7 +1135,11 @@ private final class GiftViewSheetContent: CombinedComponent { )) } - let valueString = "⭐️\(presentationStringsFormattedNumber(abs(Int32(stars)), dateTimeFormat.groupingSeparator))" + var finalStars = stars + if let upgradeStars, upgradeStars > 0 { + finalStars += upgradeStars + } + let valueString = "⭐️\(presentationStringsFormattedNumber(abs(Int32(finalStars)), dateTimeFormat.groupingSeparator))" let valueAttributedString = NSMutableAttributedString(string: valueString, font: tableFont, textColor: tableTextColor) let range = (valueAttributedString.string as NSString).range(of: "⭐️") if range.location != NSNotFound { @@ -1115,7 +1211,7 @@ private final class GiftViewSheetContent: CombinedComponent { )) } - if !soldOut && upgradeStars != nil { + if !soldOut && canUpgrade { var items: [AnyComponentWithIdentity] = [] items.append( AnyComponentWithIdentity( @@ -1190,7 +1286,14 @@ private final class GiftViewSheetContent: CombinedComponent { if state.cachedSmallChevronImage == nil || state.cachedSmallChevronImage?.1 !== environment.theme { state.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: linkColor)!, theme) } - let descriptionText = savedToProfile ? strings.Gift_View_DisplayedInfoHide : strings.Gift_View_HiddenInfo + 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 @@ -1219,7 +1322,7 @@ private final class GiftViewSheetContent: CombinedComponent { } }, tapAction: { _, _ in - component.updateSavedToProfile(false) + component.updateSavedToProfile(!savedToProfile) Queue.mainQueue().after(1.0, { component.cancel(false) }) @@ -1239,11 +1342,15 @@ private final class GiftViewSheetContent: CombinedComponent { } let buttonChild: _UpdatedChildComponent - if state.inUpgradePreview, let upgradeStars = subject.arguments?.upgradeStars { + if state.inUpgradePreview { if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme { state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme) } - let buttonTitle = upgradeStars > 0 ? "\(strings.Gift_Upgrade_Upgrade) # \(upgradeStars)" : strings.Gift_Upgrade_Confirm + var upgradeString = strings.Gift_Upgrade_Upgrade + if let upgradeForm = state.upgradeForm, let price = upgradeForm.invoice.prices.first?.amount { + upgradeString += " # \(price)" + } + let buttonTitle = subject.arguments?.upgradeStars != nil ? strings.Gift_Upgrade_Confirm : upgradeString let buttonAttributedString = NSMutableAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) if let range = buttonAttributedString.string.range(of: "#"), let starImage = state.cachedStarImage?.0 { buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string)) @@ -1259,7 +1366,7 @@ private final class GiftViewSheetContent: CombinedComponent { cornerRadius: 10.0 ), content: AnyComponentWithIdentity( - id: AnyHashable(0), + id: AnyHashable("upgrade"), component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString))) ), isEnabled: true, @@ -1270,7 +1377,8 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), transition: context.transition ) - } else if incoming && !converted && !savedToProfile { + } else if incoming && !converted && !upgraded, let upgradeStars, upgradeStars > 0 { + let buttonTitle = strings.Gift_View_UpgradeForFree buttonChild = button.update( component: ButtonComponent( background: ButtonComponent.Background( @@ -1280,8 +1388,36 @@ private final class GiftViewSheetContent: CombinedComponent { cornerRadius: 10.0 ), content: AnyComponentWithIdentity( - id: AnyHashable(0), - component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) + id: AnyHashable("freeUpgrade"), + component: AnyComponent(HStack([ + AnyComponentWithIdentity(id: 0, component: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)) + ))), + AnyComponentWithIdentity(id: 1, component: AnyComponent(BundleIconComponent(name: "Premium/GiftUpgrade", tintColor: theme.list.itemCheckColors.foregroundColor))) + ], spacing: 6.0)) + ), + isEnabled: true, + displaysProgress: state.inProgress, + action: { [weak state] in + state?.requestUpgradePreview() + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + } else if incoming && !converted && !savedToProfile { + let buttonTitle = savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display + buttonChild = button.update( + component: ButtonComponent( + background: ButtonComponent.Background( + color: theme.list.itemCheckColors.fillColor, + foreground: theme.list.itemCheckColors.foregroundColor, + pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9), + cornerRadius: 10.0 + ), + content: AnyComponentWithIdentity( + id: AnyHashable("button"), + component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: buttonTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) ), isEnabled: true, displaysProgress: state.inProgress, @@ -1301,7 +1437,7 @@ private final class GiftViewSheetContent: CombinedComponent { cornerRadius: 10.0 ), content: AnyComponentWithIdentity( - id: AnyHashable(0), + id: AnyHashable("ok"), component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Common_OK, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)))) ), isEnabled: true, @@ -1486,24 +1622,31 @@ public class GiftViewScreen: ViewControllerComponentContainer { case message(EngineMessage) case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift) case soldOutGift(StarGift.Gift) + case upgradePreview([StarGift.UniqueGift.Attribute], String) - var arguments: (peerId: EnginePeer.Id, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?)? { + var arguments: (peerId: EnginePeer.Id, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, canExportDate: Int32?)? { switch self { case let .message(message): if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction { switch action.action { - case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, _, 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, false, upgradeStars, nil, nil) - case let .starGiftUnique(gift, _, _, 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, false, nil, transferStars, canExportDate) + 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) + case let .starGiftUnique(gift, _, _, 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) default: return nil } } case let .profileGift(peerId, gift): - 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, gift.canUpgrade, gift.upgradeStars, gift.transferStars, gift.canExportDate) + var upgraded = false + 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: return nil + case .upgradePreview: + return nil } return nil } @@ -1772,6 +1915,8 @@ public class GiftViewScreen: ViewControllerComponentContainer { guard let self else { return } + self.dismissAllTooltips() + guard let sourceView = self.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: self.view) else { return } @@ -1818,13 +1963,16 @@ public class GiftViewScreen: ViewControllerComponentContainer { fileprivate func dismissAllTooltips() { self.window?.forEachController({ controller in if let controller = controller as? TooltipScreen { - controller.dismiss() + controller.dismiss(inPlace: false) } if let controller = controller as? UndoOverlayController { controller.dismiss() } }) self.forEachController({ controller in + if let controller = controller as? TooltipScreen { + controller.dismiss(inPlace: false) + } if let controller = controller as? UndoOverlayController { controller.dismiss() } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift index 6954d2a3d3..c2870b3538 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift @@ -222,29 +222,24 @@ public final class PeerInfoCoverComponent: Component { } public func animateTransition() { - if let snapshotLayer = self.backgroundView.layer.snapshotContentTree() { - self.layer.insertSublayer(snapshotLayer, above: self.backgroundGradientLayer) - if let gradientSnapshotLayer = self.backgroundGradientLayer.snapshotContentTree() { - self.layer.insertSublayer(gradientSnapshotLayer, above: snapshotLayer) - snapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in - snapshotLayer.removeFromSuperlayer() - }) - gradientSnapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in - gradientSnapshotLayer.removeFromSuperlayer() - }) - } + if let gradientSnapshotLayer = self.backgroundGradientLayer.snapshotContentTree() { + gradientSnapshotLayer.frame = self.backgroundGradientLayer.frame + self.layer.insertSublayer(gradientSnapshotLayer, above: self.backgroundGradientLayer) + gradientSnapshotLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + gradientSnapshotLayer.removeFromSuperlayer() + }) } for layer in self.avatarPatternContentLayers { - if let snapshot = layer.snapshotContentTree() { + if let _ = layer.contents, let snapshot = layer.snapshotContentTree() { layer.superlayer?.addSublayer(snapshot) snapshot.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in snapshot.removeFromSuperlayer() }) - layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } + layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } let values: [NSNumber] = [1.0, 1.08, 1.0] - self.avatarBackgroundPatternContentsLayer.animateKeyframes(values: values, duration: 0.25, keyPath: "transform.scale") + self.avatarBackgroundPatternContentsLayer.animateKeyframes(values: values, duration: 0.25, keyPath: "sublayerTransform.scale") } private func loadPatternFromFile() { @@ -298,7 +293,10 @@ public final class PeerInfoCoverComponent: Component { } func update(component: PeerInfoCoverComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { - if self.component?.subject?.fileId != component.subject?.fileId { + let previousComponent = self.component + self.component = component + + if previousComponent?.subject?.fileId != component.subject?.fileId { if let fileId = component.subject?.fileId, fileId != 0 { if self.patternContentsTarget == nil { self.patternContentsTarget = PatternContentsTarget(imageUpdated: { [weak self] hadContents in @@ -337,8 +335,7 @@ public final class PeerInfoCoverComponent: Component { self.updatePatternLayerImages(animated: false) } } - - self.component = component + self.state = state let backgroundColor: UIColor diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift index 4c4a2aa841..940bc2c5b9 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoPaneContainerNode.swift @@ -1210,9 +1210,15 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat case .gifts: title = presentationData.strings.PeerInfo_PaneGifts icons = data?.profileGiftsContext?.currentState?.gifts.prefix(3).compactMap { gift in - if case let .generic(gift) = gift.gift { + switch gift.gift { + case let .generic(gift): return gift.file - } else { + case let .unique(gift): + for attribute in gift.attributes { + if case let .model(_, file, _) = attribute { + return file + } + } return nil } } ?? [] diff --git a/submodules/TelegramUI/Images.xcassets/Premium/GiftUnpack.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/GiftUnpack.imageset/Contents.json new file mode 100644 index 0000000000..4b14361868 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/GiftUnpack.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "unpack_14.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/GiftUnpack.imageset/unpack_14.pdf b/submodules/TelegramUI/Images.xcassets/Premium/GiftUnpack.imageset/unpack_14.pdf new file mode 100644 index 0000000000..6804cb3d05 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/GiftUnpack.imageset/unpack_14.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Premium/GiftUpgrade.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/GiftUpgrade.imageset/Contents.json new file mode 100644 index 0000000000..dd968c0c20 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/GiftUpgrade.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "upgrade_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/GiftUpgrade.imageset/upgrade_30.pdf b/submodules/TelegramUI/Images.xcassets/Premium/GiftUpgrade.imageset/upgrade_30.pdf new file mode 100644 index 0000000000..9aced86ade Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/GiftUpgrade.imageset/upgrade_30.pdf differ diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 1d59798864..457f6812d8 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2289,18 +2289,13 @@ public final class SharedAccountContextImpl: SharedAccountContext { var currentBirthdays: [EnginePeer.Id: TelegramBirthday]? if case let .starGiftTransfer(birthdays, _, _, _, _) = source { - if let birthdays, !birthdays.isEmpty { - mode = .starsGifting(birthdays: birthdays, hasActions: false, showSelf: true) - currentBirthdays = birthdays - } else { - mode = .starsGifting(birthdays: nil, hasActions: false, showSelf: true) - } - } - if case let .chatList(birthdays) = source, let birthdays, !birthdays.isEmpty { - mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: false) + mode = .starsGifting(birthdays: birthdays, hasActions: false, showSelf: false) currentBirthdays = birthdays - } else if case let .settings(birthdays) = source, let birthdays, !birthdays.isEmpty { - mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: false) + } else if case let .chatList(birthdays) = source { + mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: true) + currentBirthdays = birthdays + } else if case let .settings(birthdays) = source { + mode = .starsGifting(birthdays: birthdays, hasActions: true, showSelf: true) currentBirthdays = birthdays } else { mode = .starsGifting(birthdays: nil, hasActions: true, showSelf: false)