diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c29cb27779..43dd46251f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -14496,7 +14496,8 @@ Sorry for the inconvenience."; "Chat.Todo.ContextMenu.SectionsInfo" = "You're viewing actions for one task.\nYou can switch to actions for the list."; "Chat.Todo.PremiumRequired" = "Only [Telegram Premium]() subscribers can mark tasks as done."; -"Chat.Todo.CompletionLimited" = "%@ has restricted others from editing this to do list."; +"Chat.Todo.CompletionLimited" = "%@ has restricted others from editing this checklist."; +"Chat.Todo.CompletionLimitedForward" = "You can’t make changes to forwarded checklists."; "Forward.ErrorTodoDisabledInChannels" = "Sorry, checklists can’t be forwarded to channels."; @@ -14662,3 +14663,8 @@ Sorry for the inconvenience."; "Chat.Todo.Message.Completed_any" = "%@ of {count} completed"; "Chat.Todo.Message.CompletedBy_1" = "%@ of {count} completed by {name}"; "Chat.Todo.Message.CompletedBy_any" = "%@ of {count} completed by {name}"; + +"Notification.StarGift.ReleasedBy" = "released by %@"; + +"Gift.View.ReleasedBy" = "released by %@"; +"Gift.Unique.CollectibleBy" = "collectible %1$@ by %2$@"; diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 980686598f..86b413f239 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -943,8 +943,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[2109703795] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) } - dict[-970274264] = { return Api.StarGift.parse_starGift($0) } - dict[1678891913] = { return Api.StarGift.parse_starGiftUnique($0) } + dict[2139438098] = { return Api.StarGift.parse_starGift($0) } + dict[-164136786] = { return Api.StarGift.parse_starGiftUnique($0) } dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) } dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) } dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) } @@ -1432,7 +1432,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) } dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) } dict[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) } - dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) } + dict[785918357] = { return Api.payments.StarGifts.parse_starGifts($0) } dict[-1551326360] = { return Api.payments.StarGifts.parse_starGiftsNotModified($0) } dict[961445665] = { return Api.payments.StarsRevenueAdsAccountUrl.parse_starsRevenueAdsAccountUrl($0) } dict[1814066038] = { return Api.payments.StarsRevenueStats.parse_starsRevenueStats($0) } diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index ecb0e13970..50f5d74a4f 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -636,14 +636,14 @@ public extension Api { } public extension Api { enum StarGift: TypeConstructorDescription { - case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?) - case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?) + case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, releasedBy: Api.Peer?) + case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?, releasedBy: Api.Peer?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title): + case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy): if boxed { - buffer.appendInt32(-970274264) + buffer.appendInt32(2139438098) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -658,10 +658,11 @@ public extension Api { if Int(flags) & Int(1 << 3) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellMinStars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 5) != 0 {serializeString(title!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 6) != 0 {releasedBy!.serialize(buffer, true)} break - case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars): + case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy): if boxed { - buffer.appendInt32(1678891913) + buffer.appendInt32(-164136786) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -680,16 +681,17 @@ public extension Api { serializeInt32(availabilityTotal, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 3) != 0 {serializeString(giftAddress!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellStars!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 5) != 0 {releasedBy!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title): - return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any)]) - case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars): - return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any)]) + case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title, let releasedBy): + return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any), ("releasedBy", releasedBy as Any)]) + case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars, let releasedBy): + return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any), ("releasedBy", releasedBy as Any)]) } } @@ -722,6 +724,10 @@ public extension Api { if Int(_1!) & Int(1 << 4) != 0 {_12 = reader.readInt64() } var _13: String? if Int(_1!) & Int(1 << 5) != 0 {_13 = parseString(reader) } + var _14: Api.Peer? + if Int(_1!) & Int(1 << 6) != 0 {if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.Peer + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -735,8 +741,9 @@ public extension Api { let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13) + let _c14 = (Int(_1!) & Int(1 << 6) == 0) || _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13, releasedBy: _14) } else { return nil @@ -773,6 +780,10 @@ public extension Api { if Int(_1!) & Int(1 << 3) != 0 {_12 = parseString(reader) } var _13: Int64? if Int(_1!) & Int(1 << 4) != 0 {_13 = reader.readInt64() } + var _14: Api.Peer? + if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() { + _14 = Api.parse(reader, signature: signature) as? Api.Peer + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -786,8 +797,9 @@ public extension Api { let _c11 = _11 != nil let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 4) == 0) || _13 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 { - return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, ownerAddress: _8, attributes: _9!, availabilityIssued: _10!, availabilityTotal: _11!, giftAddress: _12, resellStars: _13) + let _c14 = (Int(_1!) & Int(1 << 5) == 0) || _14 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { + return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, ownerAddress: _8, attributes: _9!, availabilityIssued: _10!, availabilityTotal: _11!, giftAddress: _12, resellStars: _13, releasedBy: _14) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api36.swift b/submodules/TelegramApi/Sources/Api36.swift index c7e80100b6..b25d7db3c7 100644 --- a/submodules/TelegramApi/Sources/Api36.swift +++ b/submodules/TelegramApi/Sources/Api36.swift @@ -298,14 +298,14 @@ public extension Api.payments { } public extension Api.payments { enum StarGifts: TypeConstructorDescription { - case starGifts(hash: Int32, gifts: [Api.StarGift]) + case starGifts(hash: Int32, gifts: [Api.StarGift], chats: [Api.Chat], users: [Api.User]) case starGiftsNotModified public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starGifts(let hash, let gifts): + case .starGifts(let hash, let gifts, let chats, let users): if boxed { - buffer.appendInt32(-1877571094) + buffer.appendInt32(785918357) } serializeInt32(hash, buffer: buffer, boxed: false) buffer.appendInt32(481674261) @@ -313,6 +313,16 @@ public extension Api.payments { for item in gifts { item.serialize(buffer, true) } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } break case .starGiftsNotModified: if boxed { @@ -325,8 +335,8 @@ public extension Api.payments { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .starGifts(let hash, let gifts): - return ("starGifts", [("hash", hash as Any), ("gifts", gifts as Any)]) + case .starGifts(let hash, let gifts, let chats, let users): + return ("starGifts", [("hash", hash as Any), ("gifts", gifts as Any), ("chats", chats as Any), ("users", users as Any)]) case .starGiftsNotModified: return ("starGiftsNotModified", []) } @@ -339,10 +349,20 @@ public extension Api.payments { if let _ = reader.readInt32() { _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self) } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!) + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.payments.StarGifts.starGifts(hash: _1!, gifts: _2!, chats: _3!, users: _4!) } else { return nil diff --git a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift index 48ff0f9e03..e620c3e0b4 100644 --- a/submodules/TelegramCore/Sources/State/AccountTaskManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountTaskManager.swift @@ -122,7 +122,7 @@ final class AccountTaskManager { tasks.add(managedDisabledChannelStatusIconEmoji(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) tasks.add(_internal_loadedStickerPack(postbox: self.stateManager.postbox, network: self.stateManager.network, reference: .iconTopicEmoji, forceActualized: true).start()) tasks.add(managedPeerColorUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) - tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) + tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start()) self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start()) diff --git a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift index 7ee61d5784..b7af0258ec 100644 --- a/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift +++ b/submodules/TelegramCore/Sources/State/ManagedRecentStickers.swift @@ -354,7 +354,8 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne ], availability: StarGift.UniqueGift.Availability(issued: 0, total: 0), giftAddress: nil, - resellStars: nil + resellStars: nil, + releasedBy: nil ) if let entry = CodableEntry(RecentStarGiftItem(gift)) { items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry)) diff --git a/submodules/TelegramCore/Sources/State/Serialization.swift b/submodules/TelegramCore/Sources/State/Serialization.swift index 95b93fef4c..ab9b2a4f2e 100644 --- a/submodules/TelegramCore/Sources/State/Serialization.swift +++ b/submodules/TelegramCore/Sources/State/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 206 + return 207 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index a079328a77..f935eac984 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -869,7 +869,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { return [peerId] case let .prizeStars(_, _, boostPeerId, _, _): return boostPeerId.flatMap { [$0] } ?? [] - case let .starGift(_, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _): + case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, peerId, senderId, _): var peerIds: [PeerId] = [] if let peerId { peerIds.append(peerId) @@ -877,8 +877,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { if let senderId { peerIds.append(senderId) } + if let releasedBy = gift.releasedBy { + peerIds.append(releasedBy) + } return peerIds - case let .starGiftUnique(_, _, _, _, _, _, _, peerId, senderId, _, _, _, _): + case let .starGiftUnique(gift, _, _, _, _, _, _, peerId, senderId, _, _, _, _): var peerIds: [PeerId] = [] if let peerId { peerIds.append(peerId) @@ -886,6 +889,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { if let senderId { peerIds.append(senderId) } + if let releasedBy = gift.releasedBy { + peerIds.append(releasedBy) + } return peerIds case let .conferenceCall(conferenceCall): return conferenceCall.otherParticipants diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift index 1e93ee353b..02a94cafb6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/StarGifts.swift @@ -54,6 +54,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { case soldOut case flags case upgradeStars + case releasedBy } public struct Availability: Equatable, Codable, PostboxCoding { @@ -149,8 +150,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding { public let soldOut: SoldOut? public let flags: Flags public let upgradeStars: Int64? + public let releasedBy: EnginePeer.Id? - public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?) { + public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?, releasedBy: EnginePeer.Id?) { self.id = id self.title = title self.file = file @@ -160,6 +162,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.soldOut = soldOut self.flags = flags self.upgradeStars = upgradeStars + self.releasedBy = releasedBy } public init(from decoder: Decoder) throws { @@ -179,6 +182,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.soldOut = try container.decodeIfPresent(SoldOut.self, forKey: .soldOut) self.flags = Flags(rawValue: try container .decodeIfPresent(Int32.self, forKey: .flags) ?? 0) self.upgradeStars = try container.decodeIfPresent(Int64.self, forKey: .upgradeStars) + self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy) } public init(decoder: PostboxDecoder) { @@ -191,6 +195,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.soldOut = decoder.decodeObjectForKey(CodingKeys.soldOut.rawValue, decoder: { StarGift.Gift.SoldOut(decoder: $0) }) as? StarGift.Gift.SoldOut self.flags = Flags(rawValue: decoder.decodeInt32ForKey(CodingKeys.flags.rawValue, orElse: 0)) self.upgradeStars = decoder.decodeOptionalInt64ForKey(CodingKeys.upgradeStars.rawValue) + self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) } } public func encode(to encoder: Encoder) throws { @@ -209,6 +214,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { try container.encodeIfPresent(self.soldOut, forKey: .soldOut) try container.encode(self.flags.rawValue, forKey: .flags) try container.encodeIfPresent(self.upgradeStars, forKey: .upgradeStars) + try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy) } public func encode(_ encoder: PostboxEncoder) { @@ -237,6 +243,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding { } else { encoder.encodeNil(forKey: CodingKeys.upgradeStars.rawValue) } + if let releasedBy = self.releasedBy { + encoder.encodeInt64(releasedBy.toInt64(), forKey: CodingKeys.releasedBy.rawValue) + } else { + encoder.encodeNil(forKey: CodingKeys.releasedBy.rawValue) + } } } @@ -253,6 +264,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { case availability case giftAddress case resellStars + case releasedBy } public enum Attribute: Equatable, Codable, PostboxCoding { @@ -506,8 +518,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding { public let availability: Availability public let giftAddress: String? public let resellStars: Int64? + public let releasedBy: EnginePeer.Id? - public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellStars: Int64?) { + public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellStars: Int64?, releasedBy: EnginePeer.Id?) { self.id = id self.title = title self.number = number @@ -517,6 +530,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.availability = availability self.giftAddress = giftAddress self.resellStars = resellStars + self.releasedBy = releasedBy } public init(from decoder: Decoder) throws { @@ -538,6 +552,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.availability = try container.decode(UniqueGift.Availability.self, forKey: .availability) self.giftAddress = try container.decodeIfPresent(String.self, forKey: .giftAddress) self.resellStars = try container.decodeIfPresent(Int64.self, forKey: .resellStars) + self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy) } public init(decoder: PostboxDecoder) { @@ -558,6 +573,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { UniqueGift.Availability(decoder: $0) }) as! UniqueGift.Availability self.giftAddress = decoder.decodeOptionalStringForKey(CodingKeys.giftAddress.rawValue) self.resellStars = decoder.decodeOptionalInt64ForKey(CodingKeys.resellStars.rawValue) + self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) } } public func encode(to encoder: Encoder) throws { @@ -578,6 +594,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding { try container.encode(self.availability, forKey: .availability) try container.encodeIfPresent(self.giftAddress, forKey: .giftAddress) try container.encodeIfPresent(self.resellStars, forKey: .resellStars) + try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy) } public func encode(_ encoder: PostboxEncoder) { @@ -605,6 +622,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding { } else { encoder.encodeNil(forKey: CodingKeys.resellStars.rawValue) } + if let releasedBy = self.releasedBy { + encoder.encodeInt64(releasedBy.toInt64(), forKey: CodingKeys.releasedBy.rawValue) + } else { + encoder.encodeNil(forKey: CodingKeys.releasedBy.rawValue) + } } public func withResellStars(_ resellStars: Int64?) -> UniqueGift { @@ -617,7 +639,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding { attributes: self.attributes, availability: self.availability, giftAddress: self.giftAddress, - resellStars: resellStars + resellStars: resellStars, + releasedBy: self.releasedBy ) } } @@ -683,10 +706,21 @@ public enum StarGift: Equatable, Codable, PostboxCoding { } } +public extension StarGift { + var releasedBy: EnginePeer.Id? { + switch self { + case let .generic(gift): + return gift.releasedBy + case let .unique(gift): + return gift.releasedBy + } + } +} + extension StarGift { init?(apiStarGift: Api.StarGift) { switch apiStarGift { - case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title): + case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title, releasedBy): var flags = StarGift.Gift.Flags() if (apiFlags & (1 << 2)) != 0 { flags.insert(.isBirthdayGift) @@ -708,8 +742,8 @@ extension StarGift { guard let file = telegramMediaFileFromApiDocument(sticker, altDocuments: nil) else { return nil } - self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars)) - case let .starGiftUnique(_, id, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, reselltars): + self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars, releasedBy: releasedBy?.peerId)) + case let .starGiftUnique(_, id, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellStars, releasedBy): let owner: StarGift.UniqueGift.Owner if let ownerAddress { owner = .address(ownerAddress) @@ -720,7 +754,7 @@ extension StarGift { } else { return nil } - self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellStars: reselltars)) + self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellStars: resellStars, releasedBy: releasedBy?.peerId)) } } } @@ -739,7 +773,7 @@ func _internal_cachedStarGifts(postbox: Postbox) -> Signal Signal { +func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id) -> Signal { let updateSignal = _internal_cachedStarGifts(postbox: postbox) |> take(1) |> mapToSignal { list -> Signal in @@ -755,7 +789,10 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) -> return postbox.transaction { transaction in switch result { - case let .starGifts(hash, gifts): + case let .starGifts(hash, gifts, chats, users): + let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) + let starGiftsLists = StarGiftsList(items: gifts.compactMap { StarGift(apiStarGift: $0) }, hashValue: hash) transaction.setPreferencesEntry(key: PreferencesKeys.starGifts(), value: PreferencesEntry(starGiftsLists)) case .starGiftsNotModified: @@ -769,8 +806,8 @@ func _internal_keepCachedStarGiftsUpdated(postbox: Postbox, network: Network) -> return updateSignal } -func managedStarGiftsUpdates(postbox: Postbox, network: Network) -> Signal { - let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network) +func managedStarGiftsUpdates(postbox: Postbox, network: Network, accountPeerId: EnginePeer.Id) -> Signal { + let poll = _internal_keepCachedStarGiftsUpdated(postbox: postbox, network: network, accountPeerId: accountPeerId) return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index 80b066523a..2ab196f326 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -114,7 +114,7 @@ public extension TelegramEngine { } public func keepStarGiftsUpdated() -> Signal { - return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network) + return _internal_keepCachedStarGiftsUpdated(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId) } public func convertStarGift(reference: StarGiftReference) -> Signal { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift index 08856d1830..296bab013a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiftBubbleContentNode/Sources/ChatMessageGiftBubbleContentNode.swift @@ -56,6 +56,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { private let ribbonBackgroundNode: ASImageNode private let ribbonTextNode: TextNode + private let creatorButtonNode: HighlightTrackingButtonNode + private let creatorButtonTitleNode: TextNode + private var shimmerEffectNode: ShimmerEffectForegroundNode? private let buttonNode: HighlightTrackingButtonNode private let buttonStarsNode: PremiumStarsNode @@ -152,17 +155,17 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.symbolValueTextNode = TextNode() self.symbolValueTextNode.isUserInteractionEnabled = false self.symbolValueTextNode.displaysAsynchronously = false - - self.buttonNode = HighlightTrackingButtonNode() - self.buttonNode.clipsToBounds = true - self.buttonNode.cornerRadius = 17.0 - + self.placeholderNode = StickerShimmerEffectNode() self.placeholderNode.isUserInteractionEnabled = false self.placeholderNode.alpha = 0.75 self.animationNode = DefaultAnimatedStickerNodeImpl() + self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode.clipsToBounds = true + self.buttonNode.cornerRadius = 17.0 + self.buttonStarsNode = PremiumStarsNode() self.buttonContentNode = ASDisplayNode() @@ -171,6 +174,13 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.buttonTitleNode = TextNode() self.buttonTitleNode.displaysAsynchronously = false + self.creatorButtonNode = HighlightTrackingButtonNode() + self.creatorButtonNode.clipsToBounds = true + self.creatorButtonNode.cornerRadius = 9.0 + + self.creatorButtonTitleNode = TextNode() + self.creatorButtonTitleNode.displaysAsynchronously = false + self.ribbonBackgroundNode = ASImageNode() self.ribbonBackgroundNode.displaysAsynchronously = false @@ -196,9 +206,12 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.buttonNode) self.buttonNode.addSubnode(self.buttonStarsNode) self.buttonNode.addSubnode(self.buttonContentNode) - + self.buttonContentNode.addSubnode(self.buttonTitleNode) + self.addSubnode(self.creatorButtonNode) + self.creatorButtonNode.addSubnode(self.creatorButtonTitleNode) + self.addSubnode(self.ribbonBackgroundNode) self.addSubnode(self.ribbonTextNode) @@ -213,8 +226,20 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } } } - self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + self.creatorButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.creatorButtonNode.layer.removeAnimation(forKey: "opacity") + strongSelf.creatorButtonNode.alpha = 0.4 + } else { + strongSelf.creatorButtonNode.alpha = 1.0 + strongSelf.creatorButtonNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.creatorButtonNode.addTarget(self, action: #selector(self.creatorButtonPressed), forControlEvents: .touchUpInside) } required public init?(coder aDecoder: NSCoder) { @@ -239,6 +264,31 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { self.maskView?.addSubview(maskOverlayView) } + @objc private func creatorButtonPressed() { + guard let item = self.item else { + return + } + + var releasedBy: EnginePeer.Id? + for media in item.message.media { + if let action = media as? TelegramMediaAction { + switch action.action { + case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + releasedBy = gift.releasedBy + case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _): + releasedBy = gift.releasedBy + default: + break + } + break + } + } + guard let releasedBy, let peer = item.message.peers[releasedBy] else { + return + } + item.controllerInteraction.openPeer(EnginePeer(peer), .chat(textInputState: nil, subject: nil, peekData: nil), nil, .default) + } + @objc private func buttonPressed() { guard let item = self.item else { return @@ -330,6 +380,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let makeMeasureTextLayout = TextNode.asyncLayout(nil) let makeMoreTextLayout = TextNode.asyncLayout(self.moreTextNode) + let makeCreatorButtonTitleLayout = TextNode.asyncLayout(self.creatorButtonTitleNode) + let makeModelTitleLayout = TextNode.asyncLayout(self.modelTitleTextNode) let makeModelValueLayout = TextNode.asyncLayout(self.modelValueTextNode) let makeBackdropTitleLayout = TextNode.asyncLayout(self.backdropTitleTextNode) @@ -373,6 +425,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { var textSpacing: CGFloat = 0.0 var isStarGift = false + var creatorButtonTitle = "" + var modelTitle: String? var modelValue: String? var backdropTitle: String? @@ -494,6 +548,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { } case let .starGift(gift, convertStars, giftText, giftEntities, _, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, channelPeerId, senderPeerId, _): if case let .generic(gift) = gift { + if let releasedBy = gift.releasedBy, let peer = item.message.peers[releasedBy], let addressName = peer.addressName { + creatorButtonTitle = item.presentationData.strings.Notification_StarGift_ReleasedBy("**@\(addressName)**").string + } + isStarGift = true var authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? "" @@ -763,7 +821,22 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { 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())) + + let creatorButtonAttributedString = parseMarkdownIntoAttributedString(creatorButtonTitle, attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: Font.regular(12.0), textColor: primaryTextColor), + bold: MarkdownAttributeSet(font: Font.semibold(12.0), textColor: primaryTextColor), + link: MarkdownAttributeSet(font: Font.regular(12.0), textColor: primaryTextColor), + linkAttribute: { url in + return ("URL", url) + } + ), textAlignment: .center) + + let (creatorButtonTitleLayout, creatorButtonTitleApply) = makeCreatorButtonTitleLayout(TextNodeLayoutArguments(attributedString: creatorButtonAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + if !creatorButtonTitle.isEmpty { + textSpacing += 28.0 + } + giftSize.height = titleLayout.size.height + textSpacing + clippedTextHeight + 164.0 if let _ = modelTitle { @@ -848,6 +921,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.buttonNode.isHidden = buttonTitle.isEmpty strongSelf.buttonTitleNode.isHidden = buttonTitle.isEmpty + + strongSelf.creatorButtonNode.isHidden = creatorButtonTitle.isEmpty + strongSelf.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty if strongSelf.item == nil && !isStoryEntity { strongSelf.animationNode.started = { [weak self] in @@ -888,6 +964,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.labelNode.isHidden = !hasServiceMessage strongSelf.buttonNode.backgroundColor = overlayColor + strongSelf.creatorButtonNode.backgroundColor = overlayColor strongSelf.animationNode.updateLayout(size: iconSize) strongSelf.placeholderNode.frame = animationFrame @@ -906,13 +983,23 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { let _ = buttonTitleApply() let _ = ribbonTextApply() let _ = moreApply() - + let _ = creatorButtonTitleApply() + + + + let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((boundingWidth - labelLayout.size.width) / 2.0), y: 2.0), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame 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 creatorButtonSize = CGSize(width: creatorButtonTitleLayout.size.width + 18.0, height: 18.0) + let creatorButtonOriginY = titleFrame.maxY + 4.0 + strongSelf.creatorButtonTitleNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 1.0), size: creatorButtonTitleLayout.size) + + animation.animator.updateFrame(layer: strongSelf.creatorButtonNode.layer, frame: CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - creatorButtonSize.width) / 2.0), y: creatorButtonOriginY), size: creatorButtonSize), completion: nil) + 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) @@ -1355,8 +1442,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode { return ChatMessageBubbleContentTapAction(content: .none) } } - - if self.buttonNode.frame.contains(point) { + + if self.buttonNode.frame.contains(point) || self.creatorButtonNode.frame.contains(point) { return ChatMessageBubbleContentTapAction(content: .ignore) } else if self.textClippingNode.frame.contains(point) && !self.isExpanded && !self.moreTextNode.alpha.isZero { return ChatMessageBubbleContentTapAction(content: .custom({ [weak self] in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift index 781a04f358..b3693be49f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTodoBubbleContentNode/Sources/ChatMessageTodoBubbleContentNode.swift @@ -372,6 +372,19 @@ private func generatePercentageAnimationImages(presentationData: ChatPresentatio return images } +private class TodoTrackingButtonNode: HighlightableButtonNode { + private var internalHighlighted = false + + public var shouldHighlightAtPoint: (CGPoint) -> Bool = { _ in return true } + + open override func beginTracking(with touch: UITouch, with event: UIEvent?) -> Bool { + if self.shouldHighlightAtPoint(touch.location(in: self.view)) { + return super.beginTracking(with: touch, with: event) + } + return false + } +} + private final class ChatMessageTodoItemNode: ASDisplayNode { private var backgroundWallpaperNode: ChatMessageBubbleBackdrop? private var backgroundNode: ChatMessageBackground? @@ -390,7 +403,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { fileprivate var titleNode: TextNodeWithEntities? fileprivate var nameNode: TextNode? - private let buttonNode: HighlightTrackingButtonNode + private let buttonNode: TodoTrackingButtonNode let separatorNode: ASDisplayNode var context: AccountContext? @@ -433,7 +446,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { self.highlightedBackgroundNode.alpha = 0.0 self.highlightedBackgroundNode.isUserInteractionEnabled = false - self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode = TodoTrackingButtonNode() self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -447,6 +460,15 @@ private final class ChatMessageTodoItemNode: ASDisplayNode { self.addSubnode(self.buttonNode) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.buttonNode.shouldHighlightAtPoint = { [weak self] location in + guard let self else { + return true + } + if case .none = self.tapActionAtPoint(location, gesture: .tap, isEstimating: true).content { + return true + } + return false + } self.buttonNode.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index 60d94351d5..c070935f64 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -160,6 +160,9 @@ private final class GiftViewSheetContent: CombinedComponent { } if case let .unique(gift) = arguments.gift { + if let releasedBy = gift.releasedBy { + peerIds.append(releasedBy) + } if case let .peerId(peerId) = gift.owner { peerIds.append(peerId) } @@ -192,6 +195,9 @@ private final class GiftViewSheetContent: CombinedComponent { }) } } else if case let .generic(gift) = arguments.gift { + if let releasedBy = gift.releasedBy { + peerIds.append(releasedBy) + } if arguments.canUpgrade || arguments.upgradeStars != nil { self.sampleDisposable.add((context.engine.payments.starGiftUpgradePreview(giftId: gift.id) |> deliverOnMainQueue).start(next: { [weak self] attributes in @@ -1527,6 +1533,9 @@ private final class GiftViewSheetContent: CombinedComponent { let buttons = Child(ButtonsComponent.self) let animation = Child(GiftCompositionComponent.self) let title = Child(MultilineTextComponent.self) + let subtitle = Child(MultilineTextComponent.self) + + let descriptionButton = Child(RoundedRectangle.self) let description = Child(MultilineTextComponent.self) let transferButton = Child(PlainButtonComponent.self) @@ -1570,6 +1579,7 @@ private final class GiftViewSheetContent: CombinedComponent { let sideInset: CGFloat = 16.0 + environment.safeInsets.left var titleString: String + var subtitleString: String? var animationFile: TelegramMediaFile? let stars: Int64 let convertStars: Int64? @@ -1606,6 +1616,10 @@ private final class GiftViewSheetContent: CombinedComponent { } else if let arguments = subject.arguments { switch arguments.gift { case let .generic(gift): + if let releasedBy = gift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName { + subtitleString = strings.Gift_View_ReleasedBy("[@\(addressName)]()").string + } + animationFile = gift.file stars = gift.price text = arguments.text @@ -2145,9 +2159,15 @@ private final class GiftViewSheetContent: CombinedComponent { } } else { var descriptionText: String + var hasDescriptionButton = false if let uniqueGift { titleString = uniqueGift.title descriptionText = "\(strings.Gift_Unique_Collectible) #\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))" + + if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName { + descriptionText = strings.Gift_Unique_CollectibleBy("#\(presentationStringsFormattedNumber(uniqueGift.number, environment.dateTimeFormat.groupingSeparator))", "[@\(addressName)]()").string + hasDescriptionButton = true + } } else if soldOut { descriptionText = strings.Gift_View_UnavailableDescription } else if upgraded { @@ -2197,7 +2217,7 @@ private final class GiftViewSheetContent: CombinedComponent { component: MultilineTextComponent( text: .plain(NSAttributedString( string: titleString, - font: uniqueGift != nil ? Font.bold(20.0) : Font.bold(25.0), + font: Font.bold(20.0), textColor: uniqueGift != nil ? .white : theme.actionSheet.primaryTextColor, paragraphAlignment: .center )), @@ -2208,13 +2228,41 @@ private final class GiftViewSheetContent: CombinedComponent { transition: .immediate ) context.add(title - .position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 190.0 : 177.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 190.0 : 173.0)) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) ) + var descriptionOffset: CGFloat = 0.0 + if let subtitleString { + let subtitle = subtitle.update( + component: MultilineTextComponent( + text: .plain(NSAttributedString( + string: subtitleString, + font: Font.regular(13.0), + textColor: theme.actionSheet.secondaryTextColor, + paragraphAlignment: .center + )), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + context.add(subtitle + .position(CGPoint(x: context.availableSize.width / 2.0, y: uniqueGift != nil ? 210.0 : 196.0)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + descriptionOffset += subtitle.size.height + } + if !descriptionText.isEmpty { - let linkColor = theme.actionSheet.controlAccentColor + var linkColor = theme.actionSheet.controlAccentColor + if hasDescriptionButton { + linkColor = UIColor.white + } + if state.cachedSmallStarImage == nil || state.cachedSmallStarImage?.1 !== environment.theme { state.cachedSmallStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/ButtonStar"), color: .white)!, theme) } @@ -2226,7 +2274,11 @@ private final class GiftViewSheetContent: CombinedComponent { let textColor: UIColor if let _ = uniqueGift { textFont = Font.regular(13.0) - textColor = vibrantColor + if hasDescriptionButton { + textColor = vibrantColor.mixedWith(UIColor.white, alpha: 0.5) + } else { + textColor = vibrantColor + } } else { textFont = soldOut ? Font.medium(15.0) : Font.regular(15.0) textColor = soldOut ? theme.list.itemDestructiveColor : theme.list.itemPrimaryTextColor @@ -2245,6 +2297,7 @@ private final class GiftViewSheetContent: CombinedComponent { if let range = attributedString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 { attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string)) } + let description = description.update( component: MultilineTextComponent( text: .plain(attributedString), @@ -2269,12 +2322,29 @@ private final class GiftViewSheetContent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude), transition: .immediate ) + + if hasDescriptionButton { + let descriptionButton = descriptionButton.update( + component: RoundedRectangle(color: UIColor.white.withAlphaComponent(0.15), cornerRadius: 9.5), + environment: {}, + availableSize: CGSize(width: description.size.width + 18.0, height: 19.0), + transition: .immediate + ) + context.add(descriptionButton + .position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0 - UIScreenPixel)) + .appear(.default(alpha: true)) + .disappear(.default(alpha: true)) + ) + } + context.add(description - .position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + description.size.height / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + description.size.height / 2.0)) .appear(.default(alpha: true)) .disappear(.default(alpha: true)) ) + originY += descriptionOffset + if uniqueGift != nil { originY += 16.0 } else { diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift index 1eba83a146..a627e17bb6 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/UserApperanceScreen.swift @@ -460,7 +460,8 @@ final class UserAppearanceScreenComponent: Component { ], availability: StarGift.UniqueGift.Availability(issued: 0, total: 0), giftAddress: nil, - resellStars: nil + resellStars: nil, + releasedBy: nil ) signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate) } else { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f0f4ffbfcb..654c5c5828 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4991,6 +4991,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } self.dismissAllTooltips() + if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let _ = message.forwardInfo { + let controller = UndoOverlayController( + presentationData: self.presentationData, + content: .universalImage(image: generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: .white)!, size: nil, title: nil, text: self.presentationData.strings.Chat_Todo_CompletionLimitedForward, customUndoText: nil, timeout: nil), + action: { _ in + return false + } + ) + self.present(controller, in: .current) + return + } + guard self.presentationInterfaceState.subject != .scheduledMessages else { self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.ScheduledMessages_TodoUnavailable, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) return diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 26c463dc18..af650fba24 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -2116,6 +2116,20 @@ extension ChatControllerImpl { return } + guard self.context.isPremium else { + let context = self.context + var replaceImpl: ((ViewController) -> Void)? + let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .todo, forceDark: false, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .todo, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }, dismissed: nil) + replaceImpl = { [weak demoController] c in + demoController?.replace(with: c) + } + self.push(demoController) + return + } + let canEdit = canEditMessage(context: self.context, limitsConfiguration: self.context.currentLimitsConfiguration.with { EngineConfiguration.Limits($0) }, message: message) let controller = ComposeTodoScreen(