Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2025-08-27 11:17:34 +01:00
commit 23dd2695b8
35 changed files with 1472 additions and 649 deletions

View File

@ -14924,3 +14924,11 @@ Sorry for the inconvenience.";
"Gift.Upgrade.GiftUpgrade" = "Pay %@ for Upgrade"; "Gift.Upgrade.GiftUpgrade" = "Pay %@ for Upgrade";
"Gift.View.GiftUpgrade" = "Gift an Upgrade"; "Gift.View.GiftUpgrade" = "Gift an Upgrade";
"Gift.View.OpenChatTheme" = "This gift is the chat's theme. [Change Theme >]()";
"Notification.ChatTheme.Text" = "%1$@ set **%2$@** as a new theme for this chat.";
"Notification.ChatTheme.TextYou" = "You set **%@** as a new theme for this chat.";
"Notification.ChangedThemeGift" = "%1$@ changed chat theme to %2$@";
"Notification.YouChangedThemeGift" = "You changed chat theme to %@";

View File

@ -1326,7 +1326,7 @@ public protocol SharedAccountContext: AnyObject {
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
func makeStarsIntroScreen(context: AccountContext) -> ViewController func makeStarsIntroScreen(context: AccountContext) -> ViewController
func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: ((StarGift.UniqueGift) -> Void)?) -> ViewController func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: ((StarGift.UniqueGift) -> Void)?) -> ViewController
func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, dismissed: (() -> Void)?) -> ViewController func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, openChatTheme: (() -> Void)?, dismissed: (() -> Void)?) -> ViewController
func makeGiftWearPreviewScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController func makeGiftWearPreviewScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController

View File

@ -4,8 +4,28 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface GiftPatternRect : NSObject
@property (nonatomic) CGPoint center;
@property (nonatomic) CGFloat side;
@property (nonatomic) CGFloat rotation;
@property (nonatomic) CGFloat scale;
@end
@interface GiftPatternData : NSObject
@property (nonatomic) CGSize size;
@property (nonatomic, strong) NSArray<GiftPatternRect *> * _Nonnull rects;
@end
NSData * _Nullable prepareSvgImage(NSData * _Nonnull data, bool pattern); NSData * _Nullable prepareSvgImage(NSData * _Nonnull data, bool pattern);
GiftPatternData * _Nullable getGiftPatternData(NSData * _Nonnull data);
UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale, bool fit); UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale, bool fit);
UIImage * _Nullable renderPreparedImageWithSymbol(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale, bool fit, UIImage * _Nullable symbolImage, int32_t modelRectIndex);
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, CGFloat scale, bool opaque); UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, CGFloat scale, bool opaque);

File diff suppressed because it is too large Load Diff

View File

@ -959,7 +959,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) } dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) } dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
dict[-2136190013] = { return Api.StarGift.parse_starGift($0) } dict[-2136190013] = { return Api.StarGift.parse_starGift($0) }
dict[648369470] = { return Api.StarGift.parse_starGiftUnique($0) } dict[468707429] = { return Api.StarGift.parse_starGiftUnique($0) }
dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) } dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) } dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) } dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) }
@ -1230,7 +1230,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1674235686] = { return Api.account.AutoDownloadSettings.parse_autoDownloadSettings($0) } dict[1674235686] = { return Api.account.AutoDownloadSettings.parse_autoDownloadSettings($0) }
dict[1279133341] = { return Api.account.AutoSaveSettings.parse_autoSaveSettings($0) } dict[1279133341] = { return Api.account.AutoSaveSettings.parse_autoSaveSettings($0) }
dict[-331111727] = { return Api.account.BusinessChatLinks.parse_businessChatLinks($0) } dict[-331111727] = { return Api.account.BusinessChatLinks.parse_businessChatLinks($0) }
dict[1271855483] = { return Api.account.ChatThemes.parse_chatThemes($0) } dict[373835863] = { return Api.account.ChatThemes.parse_chatThemes($0) }
dict[-535699004] = { return Api.account.ChatThemes.parse_chatThemesNotModified($0) } dict[-535699004] = { return Api.account.ChatThemes.parse_chatThemesNotModified($0) }
dict[400029819] = { return Api.account.ConnectedBots.parse_connectedBots($0) } dict[400029819] = { return Api.account.ConnectedBots.parse_connectedBots($0) }
dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) } dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) }

View File

@ -289,7 +289,7 @@ public extension Api {
public extension Api { public extension Api {
enum StarGift: TypeConstructorDescription { 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?, releasedBy: Api.Peer?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?) 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?, perUserTotal: Int32?, perUserRemains: Int32?, lockedUntilDate: Int32?)
case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?) case starGiftUnique(flags: Int32, id: Int64, giftId: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellAmount: [Api.StarsAmount]?, releasedBy: Api.Peer?, valueAmount: Int64?, valueCurrency: String?, themePeer: Api.Peer?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -315,9 +315,9 @@ public extension Api {
if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserRemains!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeInt32(perUserRemains!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 9) != 0 {serializeInt32(lockedUntilDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(lockedUntilDate!, buffer: buffer, boxed: false)}
break break
case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency): case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer):
if boxed { if boxed {
buffer.appendInt32(648369470) buffer.appendInt32(468707429)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -344,6 +344,7 @@ public extension Api {
if Int(flags) & Int(1 << 5) != 0 {releasedBy!.serialize(buffer, true)} if Int(flags) & Int(1 << 5) != 0 {releasedBy!.serialize(buffer, true)}
if Int(flags) & Int(1 << 8) != 0 {serializeInt64(valueAmount!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeInt64(valueAmount!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 8) != 0 {serializeString(valueCurrency!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 8) != 0 {serializeString(valueCurrency!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 10) != 0 {themePeer!.serialize(buffer, true)}
break break
} }
} }
@ -352,8 +353,8 @@ public extension Api {
switch self { 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, let releasedBy, let perUserTotal, let perUserRemains, let lockedUntilDate): 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, let perUserTotal, let perUserRemains, let lockedUntilDate):
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), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any), ("lockedUntilDate", lockedUntilDate as Any)]) 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), ("perUserTotal", perUserTotal as Any), ("perUserRemains", perUserRemains as Any), ("lockedUntilDate", lockedUntilDate as Any)])
case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency): case .starGiftUnique(let flags, let id, let giftId, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellAmount, let releasedBy, let valueAmount, let valueCurrency, let themePeer):
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId 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), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any)]) return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("giftId", giftId 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), ("resellAmount", resellAmount as Any), ("releasedBy", releasedBy as Any), ("valueAmount", valueAmount as Any), ("valueCurrency", valueCurrency as Any), ("themePeer", themePeer as Any)])
} }
} }
@ -463,6 +464,10 @@ public extension Api {
if Int(_1!) & Int(1 << 8) != 0 {_16 = reader.readInt64() } if Int(_1!) & Int(1 << 8) != 0 {_16 = reader.readInt64() }
var _17: String? var _17: String?
if Int(_1!) & Int(1 << 8) != 0 {_17 = parseString(reader) } if Int(_1!) & Int(1 << 8) != 0 {_17 = parseString(reader) }
var _18: Api.Peer?
if Int(_1!) & Int(1 << 10) != 0 {if let signature = reader.readInt32() {
_18 = Api.parse(reader, signature: signature) as? Api.Peer
} }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -480,8 +485,9 @@ public extension Api {
let _c15 = (Int(_1!) & Int(1 << 5) == 0) || _15 != nil let _c15 = (Int(_1!) & Int(1 << 5) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 8) == 0) || _17 != nil let _c17 = (Int(_1!) & Int(1 << 8) == 0) || _17 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 {
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, giftId: _3!, title: _4!, slug: _5!, num: _6!, ownerId: _7, ownerName: _8, ownerAddress: _9, attributes: _10!, availabilityIssued: _11!, availabilityTotal: _12!, giftAddress: _13, resellAmount: _14, releasedBy: _15, valueAmount: _16, valueCurrency: _17, themePeer: _18)
} }
else { else {
return nil return nil

View File

@ -570,14 +570,14 @@ public extension Api.account {
} }
public extension Api.account { public extension Api.account {
enum ChatThemes: TypeConstructorDescription { enum ChatThemes: TypeConstructorDescription {
case chatThemes(flags: Int32, hash: Int64, themes: [Api.ChatTheme], nextOffset: Int32?) case chatThemes(flags: Int32, hash: Int64, themes: [Api.ChatTheme], chats: [Api.Chat], users: [Api.User], nextOffset: Int32?)
case chatThemesNotModified case chatThemesNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .chatThemes(let flags, let hash, let themes, let nextOffset): case .chatThemes(let flags, let hash, let themes, let chats, let users, let nextOffset):
if boxed { if boxed {
buffer.appendInt32(1271855483) buffer.appendInt32(373835863)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(hash, buffer: buffer, boxed: false) serializeInt64(hash, buffer: buffer, boxed: false)
@ -586,6 +586,16 @@ public extension Api.account {
for item in themes { for item in themes {
item.serialize(buffer, true) 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)
}
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextOffset!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt32(nextOffset!, buffer: buffer, boxed: false)}
break break
case .chatThemesNotModified: case .chatThemesNotModified:
@ -599,8 +609,8 @@ public extension Api.account {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .chatThemes(let flags, let hash, let themes, let nextOffset): case .chatThemes(let flags, let hash, let themes, let chats, let users, let nextOffset):
return ("chatThemes", [("flags", flags as Any), ("hash", hash as Any), ("themes", themes as Any), ("nextOffset", nextOffset as Any)]) return ("chatThemes", [("flags", flags as Any), ("hash", hash as Any), ("themes", themes as Any), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)])
case .chatThemesNotModified: case .chatThemesNotModified:
return ("chatThemesNotModified", []) return ("chatThemesNotModified", [])
} }
@ -615,14 +625,24 @@ public extension Api.account {
if let _ = reader.readInt32() { if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatTheme.self) _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatTheme.self)
} }
var _4: Int32? var _4: [Api.Chat]?
if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _6: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 { let _c5 = _5 != nil
return Api.account.ChatThemes.chatThemes(flags: _1!, hash: _2!, themes: _3!, nextOffset: _4) let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.account.ChatThemes.chatThemes(flags: _1!, hash: _2!, themes: _3!, chats: _4!, users: _5!, nextOffset: _6)
} }
else { else {
return nil return nil

View File

@ -359,7 +359,9 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne
resellForTonOnly: false, resellForTonOnly: false,
releasedBy: nil, releasedBy: nil,
valueAmount: nil, valueAmount: nil,
valueCurrency: nil valueCurrency: nil,
flags: [],
themePeerId: nil
) )
if let entry = CodableEntry(RecentStarGiftItem(gift)) { if let entry = CodableEntry(RecentStarGiftItem(gift)) {
items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry)) items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry))

View File

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization { public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt { public func currentLayer() -> UInt {
return 213 return 214
} }
public func parseMessage(_ data: Data!) -> Any! { public func parseMessage(_ data: Data!) -> Any! {

View File

@ -230,6 +230,37 @@ public enum TelegramWallpaper: Equatable {
self.file = file self.file = file
self.settings = settings self.settings = settings
} }
public static func ==(lhs: File, rhs: File) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.accessHash != rhs.accessHash {
return false
}
if lhs.isCreator != rhs.isCreator {
return false
}
if lhs.isDefault != rhs.isDefault {
return false
}
if lhs.isPattern != rhs.isPattern {
return false
}
if lhs.isDark != rhs.isDark {
return false
}
if lhs.slug != rhs.slug {
return false
}
if lhs.file.fileId != rhs.file.fileId {
return false
}
if lhs.settings != rhs.settings {
return false
}
return true
}
} }
case builtin(WallpaperSettings) case builtin(WallpaperSettings)

View File

@ -2303,6 +2303,39 @@ public extension TelegramEngine.EngineData.Item {
} }
} }
public struct CopyProtectionEnabled: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Bool
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .basicPeer(self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? BasicPeerView else {
preconditionFailure()
}
guard let peer = view.peer else {
return false
}
if let group = peer as? TelegramGroup {
return group.flags.contains(.copyProtectionEnabled)
} else if let channel = peer as? TelegramChannel {
return channel.flags.contains(.copyProtectionEnabled)
} else {
return false
}
}
}
public struct BotPreview: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public struct BotPreview: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = CachedUserData.BotPreview? public typealias Result = CachedUserData.BotPreview?

View File

@ -318,6 +318,18 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
case releasedBy case releasedBy
case valueAmount case valueAmount
case valueCurrency case valueCurrency
case flags
case themePeerId
}
public struct Flags: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public static let isThemeAvailable = Flags(rawValue: 1 << 0)
} }
public enum Attribute: Equatable, Codable, PostboxCoding { public enum Attribute: Equatable, Codable, PostboxCoding {
@ -593,8 +605,10 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
public let releasedBy: EnginePeer.Id? public let releasedBy: EnginePeer.Id?
public let valueAmount: Int64? public let valueAmount: Int64?
public let valueCurrency: String? public let valueCurrency: String?
public let flags: Flags
public let themePeerId: EnginePeer.Id?
public init(id: Int64, giftId: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellAmounts: [CurrencyAmount]?, resellForTonOnly: Bool, releasedBy: EnginePeer.Id?, valueAmount: Int64?, valueCurrency: String?) { public init(id: Int64, giftId: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellAmounts: [CurrencyAmount]?, resellForTonOnly: Bool, releasedBy: EnginePeer.Id?, valueAmount: Int64?, valueCurrency: String?, flags: Flags, themePeerId: EnginePeer.Id?) {
self.id = id self.id = id
self.giftId = giftId self.giftId = giftId
self.title = title self.title = title
@ -609,6 +623,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.releasedBy = releasedBy self.releasedBy = releasedBy
self.valueAmount = valueAmount self.valueAmount = valueAmount
self.valueCurrency = valueCurrency self.valueCurrency = valueCurrency
self.flags = flags
self.themePeerId = themePeerId
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -641,6 +657,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy) self.releasedBy = try container.decodeIfPresent(EnginePeer.Id.self, forKey: .releasedBy)
self.valueAmount = try container.decodeIfPresent(Int64.self, forKey: .valueAmount) self.valueAmount = try container.decodeIfPresent(Int64.self, forKey: .valueAmount)
self.valueCurrency = try container.decodeIfPresent(String.self, forKey: .valueCurrency) self.valueCurrency = try container.decodeIfPresent(String.self, forKey: .valueCurrency)
self.flags = try container.decodeIfPresent(Int32.self, forKey: .flags).flatMap { Flags(rawValue: $0) } ?? []
self.themePeerId = try container.decodeIfPresent(Int64.self, forKey: .themePeerId).flatMap { EnginePeer.Id($0) }
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -672,6 +690,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) } self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) }
self.valueAmount = decoder.decodeOptionalInt64ForKey(CodingKeys.valueAmount.rawValue) self.valueAmount = decoder.decodeOptionalInt64ForKey(CodingKeys.valueAmount.rawValue)
self.valueCurrency = decoder.decodeOptionalStringForKey(CodingKeys.valueCurrency.rawValue) self.valueCurrency = decoder.decodeOptionalStringForKey(CodingKeys.valueCurrency.rawValue)
self.flags = decoder.decodeOptionalInt32ForKey(CodingKeys.flags.rawValue).flatMap { Flags(rawValue: $0) } ?? []
self.themePeerId = decoder.decodeOptionalInt64ForKey(CodingKeys.themePeerId.rawValue).flatMap { EnginePeer.Id($0) }
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -697,6 +717,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy) try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy)
try container.encodeIfPresent(self.valueAmount, forKey: .valueAmount) try container.encodeIfPresent(self.valueAmount, forKey: .valueAmount)
try container.encodeIfPresent(self.valueCurrency, forKey: .valueCurrency) try container.encodeIfPresent(self.valueCurrency, forKey: .valueCurrency)
try container.encode(self.flags.rawValue, forKey: .flags)
try container.encodeIfPresent(self.themePeerId?.toInt64(), forKey: .themePeerId)
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -738,6 +760,12 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
encoder.encodeNil(forKey: CodingKeys.valueAmount.rawValue) encoder.encodeNil(forKey: CodingKeys.valueAmount.rawValue)
encoder.encodeNil(forKey: CodingKeys.valueCurrency.rawValue) encoder.encodeNil(forKey: CodingKeys.valueCurrency.rawValue)
} }
encoder.encodeInt32(self.flags.rawValue, forKey: CodingKeys.flags.rawValue)
if let themePeerId = self.themePeerId {
encoder.encodeInt64(themePeerId.toInt64(), forKey: CodingKeys.themePeerId.rawValue)
} else {
encoder.encodeNil(forKey: CodingKeys.themePeerId.rawValue)
}
} }
public func withResellAmounts(_ resellAmounts: [CurrencyAmount]?) -> UniqueGift { public func withResellAmounts(_ resellAmounts: [CurrencyAmount]?) -> UniqueGift {
@ -755,7 +783,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
resellForTonOnly: self.resellForTonOnly, resellForTonOnly: self.resellForTonOnly,
releasedBy: self.releasedBy, releasedBy: self.releasedBy,
valueAmount: self.valueAmount, valueAmount: self.valueAmount,
valueCurrency: self.valueCurrency valueCurrency: self.valueCurrency,
flags: self.flags,
themePeerId: self.themePeerId
) )
} }
@ -774,7 +804,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
resellForTonOnly: resellForTonOnly, resellForTonOnly: resellForTonOnly,
releasedBy: self.releasedBy, releasedBy: self.releasedBy,
valueAmount: self.valueAmount, valueAmount: self.valueAmount,
valueCurrency: self.valueCurrency valueCurrency: self.valueCurrency,
flags: self.flags,
themePeerId: self.themePeerId
) )
} }
} }
@ -884,7 +916,7 @@ extension StarGift {
return nil return nil
} }
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, perUserLimit: perUserLimit, lockedUntilDate: lockedUntilDate)) 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, perUserLimit: perUserLimit, lockedUntilDate: lockedUntilDate))
case let .starGiftUnique(flags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency): case let .starGiftUnique(apiFlags, id, giftId, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, resellAmounts, releasedBy, valueAmount, valueCurrency, themePeer):
let owner: StarGift.UniqueGift.Owner let owner: StarGift.UniqueGift.Owner
if let ownerAddress { if let ownerAddress {
owner = .address(ownerAddress) owner = .address(ownerAddress)
@ -896,7 +928,11 @@ extension StarGift {
return nil return nil
} }
let resellAmounts = resellAmounts?.compactMap { CurrencyAmount(apiAmount: $0) } let resellAmounts = resellAmounts?.compactMap { CurrencyAmount(apiAmount: $0) }
self = .unique(StarGift.UniqueGift(id: id, giftId: giftId, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellAmounts: resellAmounts, resellForTonOnly: (flags & (1 << 7)) != 0, releasedBy: releasedBy?.peerId, valueAmount: valueAmount, valueCurrency: valueCurrency)) var flags = StarGift.UniqueGift.Flags()
if (apiFlags & (1 << 9)) != 0 {
flags.insert(.isThemeAvailable)
}
self = .unique(StarGift.UniqueGift(id: id, giftId: giftId, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellAmounts: resellAmounts, resellForTonOnly: (apiFlags & (1 << 7)) != 0, releasedBy: releasedBy?.peerId, valueAmount: valueAmount, valueCurrency: valueCurrency, flags: flags, themePeerId: themePeer?.peerId))
} }
} }
} }

View File

@ -495,6 +495,7 @@ public final class UniqueGiftChatThemesContext {
public func loadMore(reload: Bool = false) { public func loadMore(reload: Bool = false) {
let network = self.account.network let network = self.account.network
let postbox = self.account.postbox let postbox = self.account.postbox
let accountPeerId = self.account.peerId
let dataState = self.dataState let dataState = self.dataState
let offset = self.nextOffset let offset = self.nextOffset
@ -521,12 +522,23 @@ public final class UniqueGiftChatThemesContext {
} }
let signal = network.request(Api.functions.account.getUniqueGiftChatThemes(offset: offset ?? 0, limit: 32, hash: 0)) let signal = network.request(Api.functions.account.getUniqueGiftChatThemes(offset: offset ?? 0, limit: 32, hash: 0))
|> map { result -> ([ChatTheme], Int32?) in |> map(Optional.init)
switch result { |> `catch` { error in
case let .chatThemes(_, _, themes, nextOffset): return .single(nil)
return (themes.compactMap { ChatTheme(apiChatTheme: $0) }, nextOffset) }
case .chatThemesNotModified: |> mapToSignal { result -> Signal<([ChatTheme], Int32?), NoError> in
return ([], nil) guard let result else {
return .single(([], nil))
}
return postbox.transaction { transaction -> ([ChatTheme], Int32?) in
switch result {
case let .chatThemes(_, _, themes, chats, users, nextOffset):
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
return (themes.compactMap { ChatTheme(apiChatTheme: $0) }, nextOffset)
case .chatThemesNotModified:
return ([], nil)
}
} }
} }

View File

@ -47,13 +47,36 @@ public func makePresentationTheme(cloudTheme: TelegramTheme, dark: Bool = false)
} else { } else {
settings = nil settings = nil
} }
guard let settings = settings else { guard let settings else {
return nil return nil
} }
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: .cloud(PresentationCloudTheme(theme: cloudTheme, resolvedWallpaper: nil, creatorAccountId: nil)), serviceBackgroundColor: nil, preview: false) let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), extendingThemeReference: .cloud(PresentationCloudTheme(theme: cloudTheme, resolvedWallpaper: nil, creatorAccountId: nil)), serviceBackgroundColor: nil, preview: false)
return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper) return customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(argb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(argb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
} }
public func makePresentationTheme(chatTheme: ChatTheme, dark: Bool = false) -> PresentationTheme? {
guard case let .gift(_, themeSettings) = chatTheme else {
return nil
}
let settings: TelegramThemeSettings?
if let exactSettings = themeSettings.first(where: { dark ? ($0.baseTheme == .night || $0.baseTheme == .tinted) : ($0.baseTheme == .classic || $0.baseTheme == .day) }) {
settings = exactSettings
} else if let firstSettings = themeSettings.first {
settings = firstSettings
} else {
settings = nil
}
guard let settings else {
return nil
}
let defaultTheme = makeDefaultPresentationTheme(reference: PresentationBuiltinThemeReference(baseTheme: settings.baseTheme), serviceBackgroundColor: nil, preview: false)
let theme = customizePresentationTheme(defaultTheme, editing: true, accentColor: UIColor(rgb: settings.accentColor), outgoingAccentColor: settings.outgoingAccentColor.flatMap { UIColor(rgb: $0) }, backgroundColors: [], bubbleColors: settings.messageColors, animateBubbleColors: settings.animateMessageColors, wallpaper: settings.wallpaper)
if case let .gift(starGiftValue, _) = chatTheme {
theme.starGift = starGiftValue
}
return theme
}
public func makePresentationTheme(cloudTheme: TelegramTheme, baseTheme: TelegramBaseTheme? = nil) -> PresentationTheme? { public func makePresentationTheme(cloudTheme: TelegramTheme, baseTheme: TelegramBaseTheme? = nil) -> PresentationTheme? {
let settings: TelegramThemeSettings? let settings: TelegramThemeSettings?
if let exactSettings = cloudTheme.settings?.first(where: { $0.baseTheme == baseTheme }) { if let exactSettings = cloudTheme.settings?.first(where: { $0.baseTheme == baseTheme }) {

View File

@ -1574,6 +1574,7 @@ public final class PresentationTheme: Equatable {
public let chart: PresentationThemeChart public let chart: PresentationThemeChart
public let preview: Bool public let preview: Bool
public var forceSync: Bool = false public var forceSync: Bool = false
public var starGift: StarGift?
public let resourceCache: PresentationsResourceCache = PresentationsResourceCache() public let resourceCache: PresentationsResourceCache = PresentationsResourceCache()

View File

@ -781,12 +781,15 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} else { } else {
var emoji = "" var emoji = ""
var additionalAttributes: [String: Any] = [:] var additionalAttributes: [String: Any] = [:]
var giftTitle: String?
switch chatTheme { switch chatTheme {
case let .emoticon(emoticon): case let .emoticon(emoticon):
emoji = emoticon emoji = emoticon
case let .gift(starGift, _): case let .gift(starGift, _):
var file: TelegramMediaFile? var file: TelegramMediaFile?
if case let .unique(uniqueGift) = starGift { if case let .unique(uniqueGift) = starGift {
giftTitle = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: dateTimeFormat))"
for attribute in uniqueGift.attributes { for attribute in uniqueGift.attributes {
if case let .model(_, fileValue, _) = attribute { if case let .model(_, fileValue, _) = attribute {
file = fileValue file = fileValue
@ -802,11 +805,21 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
if message.author?.id.namespace == Namespaces.Peer.CloudChannel { if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
attributedString = NSAttributedString(string: strings.Notification_ChannelChangedTheme(emoji).string, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_ChannelChangedTheme(emoji).string, font: titleFont, textColor: primaryTextColor)
} else if message.author?.id == accountPeerId { } else if message.author?.id == accountPeerId {
let resultTitleString = strings.Notification_YouChangedTheme(emoji) if let giftTitle {
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: emojiAttributes]) let resultTitleString = strings.Notification_YouChangedThemeGift(giftTitle)
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [:])
} else {
let resultTitleString = strings.Notification_YouChangedTheme(emoji)
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: emojiAttributes])
}
} else { } else {
let resultTitleString = strings.Notification_ChangedTheme(compactAuthorName, emoji) if let giftTitle {
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: emojiAttributes]) let resultTitleString = strings.Notification_ChangedThemeGift(compactAuthorName, giftTitle)
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: boldAttributes])
} else {
let resultTitleString = strings.Notification_ChangedTheme(compactAuthorName, emoji)
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: emojiAttributes])
}
} }
} }
case let .webViewData(text): case let .webViewData(text):

View File

@ -241,6 +241,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
} else if case .joinedChannel = action.action { } else if case .joinedChannel = action.action {
result.append((message, ChatMessageJoinedChannelBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default))) result.append((message, ChatMessageJoinedChannelBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
needReactions = false needReactions = false
} else if case let .setChatTheme(chatTheme) = action.action, case .gift = chatTheme {
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
} else { } else {
if !canAddMessageReactions(message: message) { if !canAddMessageReactions(message: message) {
needReactions = false needReactions = false

View File

@ -33,6 +33,7 @@ swift_library(
"//submodules/TelegramUI/Components/TextNodeWithEntities", "//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/InvisibleInkDustNode", "//submodules/InvisibleInkDustNode",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent", "//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -24,6 +24,7 @@ import ChatMessageItemCommon
import TextNodeWithEntities import TextNodeWithEntities
import InvisibleInkDustNode import InvisibleInkDustNode
import PeerInfoCoverComponent import PeerInfoCoverComponent
import GiftItemComponent
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, message: EngineMessage, accountPeerId: EnginePeer.Id) -> NSAttributedString? { 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) return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, message: message, accountPeerId: accountPeerId, forChatList: false, forForumOverview: false, forAdditionalServiceMessage: true)
@ -45,6 +46,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
private var dustNode: InvisibleInkDustNode? private var dustNode: InvisibleInkDustNode?
private let placeholderNode: StickerShimmerEffectNode private let placeholderNode: StickerShimmerEffectNode
private let animationNode: AnimatedStickerNode private let animationNode: AnimatedStickerNode
private let giftIcon = ComponentView<Empty>()
private let modelTitleTextNode: TextNode private let modelTitleTextNode: TextNode
private let modelValueTextNode: TextNode private let modelValueTextNode: TextNode
@ -416,6 +418,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
var months: Int32 = 3 var months: Int32 = 3
var animationName: String = "" var animationName: String = ""
var animationFile: TelegramMediaFile? var animationFile: TelegramMediaFile?
var uniqueGift: StarGift.UniqueGift?
var title = item.presentationData.strings.Notification_PremiumGift_Title var title = item.presentationData.strings.Notification_PremiumGift_Title
var text = "" var text = ""
var subtitleColor = primaryTextColor var subtitleColor = primaryTextColor
@ -708,6 +711,20 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
text = item.presentationData.strings.Notification_StarGift_Subtitle_Refunded text = item.presentationData.strings.Notification_StarGift_Subtitle_Refunded
animationFile = gift.file animationFile = gift.file
} }
case let .setChatTheme(chatTheme):
title = ""
var giftTitle = ""
if case let .gift(gift, _) = chatTheme, case let .unique(uniqueGiftValue) = gift {
giftTitle = "\(uniqueGiftValue.title) #\(formatCollectibleNumber(uniqueGiftValue.number, dateTimeFormat: item.presentationData.dateTimeFormat))"
uniqueGift = uniqueGiftValue
}
if incoming {
let authorName = item.message.author.flatMap { EnginePeer($0) }?.compactDisplayTitle ?? ""
text = item.presentationData.strings.Notification_ChatTheme_Text(authorName, giftTitle).string
} else {
text = item.presentationData.strings.Notification_ChatTheme_TextYou(giftTitle).string
}
hasServiceMessage = false
default: default:
break break
} }
@ -866,6 +883,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
giftSize.height += 12.0 giftSize.height += 12.0
} }
if let _ = uniqueGift {
giftSize.height -= 31.0
}
var labelRects = labelLayout.linesRects() var labelRects = labelLayout.linesRects()
if labelRects.count > 1 { if labelRects.count > 1 {
let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width }) let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })
@ -944,7 +965,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.creatorButtonNode.isUserInteractionEnabled = !item.presentationData.isPreview strongSelf.creatorButtonNode.isUserInteractionEnabled = !item.presentationData.isPreview
strongSelf.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty strongSelf.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty
if strongSelf.item == nil && !isStoryEntity { if strongSelf.item == nil && !isStoryEntity && uniqueGift == nil {
strongSelf.animationNode.started = { [weak self] in strongSelf.animationNode.started = { [weak self] in
if let strongSelf = self { if let strongSelf = self {
let current = CACurrentMediaTime() let current = CACurrentMediaTime()
@ -1009,7 +1030,10 @@ 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) 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 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)) var 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))
if let _ = uniqueGift {
clippingTextFrame.origin.y -= 23.0
}
var attributesOffsetY: CGFloat = 0.0 var attributesOffsetY: CGFloat = 0.0
@ -1315,6 +1339,31 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
if let uniqueGift {
let iconSize = CGSize(width: 94.0, height: 94.0)
let _ = strongSelf.giftIcon.update(
transition: .immediate,
component: AnyComponent(GiftItemComponent(
context: item.context,
theme: item.presentationData.theme.theme,
strings: item.presentationData.strings,
peer: nil,
subject: .uniqueGift(gift: uniqueGift, price: nil),
mode: .thumbnail
)),
environment: {},
containerSize: iconSize
)
if let giftIconView = strongSelf.giftIcon.view {
if giftIconView.superview == nil {
// backgroundView.layer.cornerRadius = 20.0
//backgroundView.clipsToBounds = true
strongSelf.view.addSubview(giftIconView)
}
giftIconView.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - iconSize.width) / 2.0), y: mediaBackgroundFrame.minY + 17.0), size: iconSize)
}
}
let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0)
if let (offset, image) = backgroundMaskImage { if let (offset, image) = backgroundMaskImage {
if strongSelf.backgroundNode == nil { if strongSelf.backgroundNode == nil {

View File

@ -1656,7 +1656,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
return patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: representations, mode: .screen) return patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: representations, mode: .screen)
|> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in |> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let value = value { if let value = value {
return .single(value) return .single(value.generator)
} else { } else {
return .complete() return .complete()
} }

View File

@ -387,7 +387,7 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode
updateImageSignal = patternWallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, representations: representations, mode: .thumbnail) updateImageSignal = patternWallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, representations: representations, mode: .thumbnail)
|> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in |> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let value { if let value {
return .single(value) return .single(value.generator)
} else { } else {
return .complete() return .complete()
} }

View File

@ -3658,7 +3658,12 @@ private final class GiftViewSheetContent: CombinedComponent {
} }
} }
if ((incoming && !converted && !upgraded) || exported || selling) && (!showUpgradePreview && !showWearPreview) {
var isChatTheme = false
if let controller = controller() as? GiftViewScreen, controller.openChatTheme != nil {
isChatTheme = true
}
if ((incoming && !converted && !upgraded) || exported || selling || isChatTheme) && (!showUpgradePreview && !showWearPreview) {
let textFont = Font.regular(13.0) let textFont = Font.regular(13.0)
let textColor = theme.list.itemSecondaryTextColor let textColor = theme.list.itemSecondaryTextColor
let linkColor = theme.actionSheet.controlAccentColor let linkColor = theme.actionSheet.controlAccentColor
@ -3672,7 +3677,9 @@ private final class GiftViewSheetContent: CombinedComponent {
var addressToOpen: String? var addressToOpen: String?
var descriptionText: String var descriptionText: String
if let uniqueGift, selling { if isChatTheme {
descriptionText = strings.Gift_View_OpenChatTheme
} else if let uniqueGift, selling {
let ownerName: String let ownerName: String
if case let .peerId(peerId) = uniqueGift.owner { if case let .peerId(peerId) = uniqueGift.owner {
ownerName = state.peerMap[peerId]?.compactDisplayTitle ?? "" ownerName = state.peerMap[peerId]?.compactDisplayTitle ?? ""
@ -3731,7 +3738,10 @@ private final class GiftViewSheetContent: CombinedComponent {
}, },
tapAction: { [weak state] attributes, _ in tapAction: { [weak state] attributes, _ in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
if let addressToOpen { if isChatTheme, let controller = controller() as? GiftViewScreen {
state?.dismiss(animated: true)
controller.openChatTheme?()
} else if let addressToOpen {
state?.openAddress(addressToOpen) state?.openAddress(addressToOpen)
} else { } else {
state?.updateSavedToProfile(!savedToProfile) state?.updateSavedToProfile(!savedToProfile)
@ -4381,6 +4391,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
fileprivate let updateResellStars: ((CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)? fileprivate let updateResellStars: ((CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)?
fileprivate let togglePinnedToTop: ((Bool) -> Bool)? fileprivate let togglePinnedToTop: ((Bool) -> Bool)?
fileprivate let shareStory: ((StarGift.UniqueGift) -> Void)? fileprivate let shareStory: ((StarGift.UniqueGift) -> Void)?
fileprivate let openChatTheme: (() -> Void)?
public var disposed: () -> Void = {} public var disposed: () -> Void = {}
@ -4397,7 +4408,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
buyGift: ((String, EnginePeer.Id, CurrencyAmount?) -> Signal<Never, BuyStarGiftError>)? = nil, buyGift: ((String, EnginePeer.Id, CurrencyAmount?) -> Signal<Never, BuyStarGiftError>)? = nil,
updateResellStars: ((CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)? = nil, updateResellStars: ((CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)? = nil,
togglePinnedToTop: ((Bool) -> Bool)? = nil, togglePinnedToTop: ((Bool) -> Bool)? = nil,
shareStory: ((StarGift.UniqueGift) -> Void)? = nil shareStory: ((StarGift.UniqueGift) -> Void)? = nil,
openChatTheme: (() -> Void)? = nil
) { ) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -4410,6 +4422,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
self.updateResellStars = updateResellStars self.updateResellStars = updateResellStars
self.togglePinnedToTop = togglePinnedToTop self.togglePinnedToTop = togglePinnedToTop
self.shareStory = shareStory self.shareStory = shareStory
self.openChatTheme = openChatTheme
if case let .unique(gift) = subject.arguments?.gift, gift.resellForTonOnly { if case let .unique(gift) = subject.arguments?.gift, gift.resellForTonOnly {
self.balanceCurrency = .ton self.balanceCurrency = .ton

View File

@ -465,7 +465,9 @@ final class UserAppearanceScreenComponent: Component {
resellForTonOnly: false, resellForTonOnly: false,
releasedBy: nil, releasedBy: nil,
valueAmount: nil, valueAmount: nil,
valueCurrency: nil valueCurrency: nil,
flags: [],
themePeerId: nil
) )
signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate) signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate)
} else { } else {

View File

@ -273,9 +273,9 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode {
self.arguments = PatternWallpaperArguments(colors: [.clear], rotation: nil, customPatternColor: isLight ? .black : .white) self.arguments = PatternWallpaperArguments(colors: [.clear], rotation: nil, customPatternColor: isLight ? .black : .white)
} }
imageSignal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: .thumbnail, autoFetchFullSize: true) imageSignal = patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: convertedRepresentations, mode: .thumbnail, autoFetchFullSize: true)
|> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in |> mapToSignal { generatorAndRects -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let value = value { if let (generator, _) = generatorAndRects {
return .single(value) return .single(generator)
} else { } else {
return .complete() return .complete()
} }

View File

@ -101,9 +101,22 @@ func openWebAppImpl(
} }
} }
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotAppSettings(id: botPeer.id)) var botPeer = botPeer
|> deliverOnMainQueue).start(next: { appSettings in if case let .inline(bot) = source {
let openWebView = { [weak parentController] in botPeer = bot
}
let _ = combineLatest(queue: Queue.mainQueue(),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotAppSettings(id: botPeer.id)),
ApplicationSpecificNotice.getBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id),
context.engine.messages.attachMenuBots(),
context.engine.messages.getAttachMenuBot(botId: botPeer.id, cached: true)
|> map(Optional.init)
|> `catch` { _ -> Signal<AttachMenuBot?, NoError> in
return .single(nil)
}
).start(next: { appSettings, noticed, attachMenuBots, attachMenuBot in
let openWebView: (Bool) -> Void = { [weak parentController] justInstalled in
guard let parentController else { guard let parentController else {
return return
} }
@ -305,6 +318,11 @@ func openWebAppImpl(
presentImpl = { [weak controller] c, a in presentImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a) controller?.present(c, in: .window(.root), with: a)
} }
if justInstalled {
let content: UndoOverlayContent = .succeed(text: presentationData.strings.WebApp_ShortcutsSettingsAdded(botPeer.compactDisplayTitle).string, timeout: 5.0, customUndoText: nil)
controller.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, position: .top, action: { _ in return false }), in: .current)
}
}, error: { [weak parentController] error in }, error: { [weak parentController] error in
if let parentController { if let parentController {
parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: { parentController.present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
@ -314,25 +332,37 @@ func openWebAppImpl(
} }
} }
if skipTermsOfService { var isAttachMenuBotInstalled: Bool?
openWebView() if let _ = attachMenuBot {
} else { if let _ = attachMenuBots.first(where: { $0.peer.id == botPeer.id && !$0.flags.contains(.notActivated) }) {
var botPeer = botPeer isAttachMenuBotInstalled = true
if case let .inline(bot) = source { } else {
botPeer = bot isAttachMenuBotInstalled = false
} }
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id) }
|> deliverOnMainQueue).startStandalone(next: { [weak parentController] value in
guard let parentController else {
return
}
if value { if !noticed || attachMenuBot?.flags.contains(.notActivated) == true || isAttachMenuBotInstalled == false {
openWebView() if let isAttachMenuBotInstalled, let attachMenuBot {
if !isAttachMenuBotInstalled {
let controller = webAppTermsAlertController(context: context, updatedPresentationData: updatedPresentationData, bot: attachMenuBot, completion: { allowWrite in
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
let _ = (context.engine.messages.addBotToAttachMenu(botId: botPeer.id, allowWrite: allowWrite)
|> deliverOnMainQueue).startStandalone(error: { _ in
}, completed: {
openWebView(true)
})
})
parentController.present(controller, in: .window(.root))
} else {
openWebView(false)
}
} else {
if skipTermsOfService {
openWebView(false)
} else { } else {
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, completion: { _ in let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, completion: { _ in
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone() let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
openWebView() openWebView(false)
}, showMore: nil, openTerms: { }, showMore: nil, openTerms: {
if let navigationController = parentController.navigationController as? NavigationController { if let navigationController = parentController.navigationController as? NavigationController {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {}) context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: presentationData.strings.WebApp_LaunchTermsConfirmation_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
@ -340,7 +370,9 @@ func openWebAppImpl(
}) })
parentController.present(controller, in: .window(.root)) parentController.present(controller, in: .window(.root))
} }
}) }
} else {
openWebView(false)
} }
}) })
} }

View File

@ -182,7 +182,7 @@ extension ChatControllerImpl {
previewTheme: { [weak self] chatTheme, dark in previewTheme: { [weak self] chatTheme, dark in
if let strongSelf = self { if let strongSelf = self {
strongSelf.presentCrossfadeSnapshot() strongSelf.presentCrossfadeSnapshot()
strongSelf.chatThemeAndDarkAppearancePreviewPromise.set(.single((chatTheme ?? .emoticon(""), dark))) strongSelf.chatThemeAndDarkAppearancePreviewPromise.set(.single((chatTheme, dark)))
} }
}, },
changeWallpaper: { [weak self] in changeWallpaper: { [weak self] in

View File

@ -1110,8 +1110,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.present(BotReceiptController(context: self.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) self.present(BotReceiptController(context: self.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
} }
return true return true
case .setChatTheme: case let .setChatTheme(chatTheme):
self.presentThemeSelection() switch chatTheme {
case .emoticon:
self.presentThemeSelection()
case let .gift(gift, _):
if case let .unique(uniqueGift) = gift {
let controller = self.context.sharedContext.makeGiftViewScreen(context: self.context, gift: uniqueGift, shareStory: { [weak self] uniqueGift in
Queue.mainQueue().after(0.15) {
if let self {
let controller = self.context.sharedContext.makeStorySharingScreen(context: self.context, subject: .gift(uniqueGift), parentController: self)
self.push(controller)
}
}
}, openChatTheme: { [weak self] in
if let self {
Queue.mainQueue().after(0.15) {
self.presentThemeSelection()
}
}
}, dismissed: nil)
self.push(controller)
}
}
return true return true
case let .setChatWallpaper(wallpaper, _): case let .setChatWallpaper(wallpaper, _):
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
@ -5808,10 +5829,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
} }
} }
case let .gift(gift, wallpaper): case .gift:
let _ = gift if let theme = makePresentationTheme(chatTheme: chatTheme, dark: useDarkAppearance) {
let _ = wallpaper theme.forceSync = true
//TODO:release presentationData = presentationData.withUpdated(theme: theme).withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
Queue.mainQueue().after(1.0, {
theme.forceSync = false
})
}
} }
} else if let darkAppearancePreview = darkAppearancePreview { } else if let darkAppearancePreview = darkAppearancePreview {
useDarkAppearance = darkAppearancePreview useDarkAppearance = darkAppearancePreview

View File

@ -3582,7 +3582,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
let themeUpdated = presentationReadyUpdated || (self.chatPresentationInterfaceState.theme !== chatPresentationInterfaceState.theme) let themeUpdated = presentationReadyUpdated || (self.chatPresentationInterfaceState.theme !== chatPresentationInterfaceState.theme)
self.backgroundNode.update(wallpaper: chatPresentationInterfaceState.chatWallpaper, animated: true) self.backgroundNode.update(wallpaper: chatPresentationInterfaceState.chatWallpaper, starGift: chatPresentationInterfaceState.theme.starGift, animated: true)
self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8) self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8)
if self.pendingSwitchToChatLocation == nil { if self.pendingSwitchToChatLocation == nil {

View File

@ -743,6 +743,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
private var initialized = false private var initialized = false
private let uniqueGiftChatThemesContext: UniqueGiftChatThemesContext private let uniqueGiftChatThemesContext: UniqueGiftChatThemesContext
private var currentUniqueGiftChatThemesState: UniqueGiftChatThemesContext.State?
private let peerName: String private let peerName: String
@ -891,11 +892,13 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
self.uniqueGiftChatThemesContext.state, self.uniqueGiftChatThemesContext.state,
self.selectedThemePromise.get(), self.selectedThemePromise.get(),
self.isDarkAppearancePromise.get() self.isDarkAppearancePromise.get()
).startStrict(next: { [weak self] themes, uniqueGiftChatThemes, selectedTheme, isDarkAppearance in ).startStrict(next: { [weak self] themes, uniqueGiftChatThemesState, selectedTheme, isDarkAppearance in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.currentUniqueGiftChatThemesState = uniqueGiftChatThemesState
let isFirstTime = strongSelf.entries == nil let isFirstTime = strongSelf.entries == nil
let presentationData = strongSelf.presentationData let presentationData = strongSelf.presentationData
@ -927,8 +930,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
wallpaper: nil wallpaper: nil
)) ))
} }
for theme in uniqueGiftChatThemes.themes { for theme in uniqueGiftChatThemesState.themes {
guard case let .gift(gift, wallpaperFile) = theme else { guard case let .gift(gift, themeSettings) = theme else {
continue continue
} }
var emojiFile: TelegramMediaFile? var emojiFile: TelegramMediaFile?
@ -939,16 +942,23 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
} }
} }
} }
var wallpaper: TelegramWallpaper?
if isDarkAppearance {
wallpaper = themeSettings.first(where: { $0.baseTheme == .night || $0.baseTheme == .tinted })?.wallpaper
} else {
wallpaper = themeSettings.first(where: { $0.baseTheme == .classic || $0.baseTheme == .day })?.wallpaper
}
entries.append(ThemeSettingsThemeEntry( entries.append(ThemeSettingsThemeEntry(
index: entries.count, index: entries.count,
chatTheme: theme, chatTheme: theme,
emojiFile: emojiFile, emojiFile: emojiFile,
themeReference: nil, themeReference: .builtin(.dayClassic),
nightMode: isDarkAppearance, nightMode: isDarkAppearance,
selected: selectedTheme?.id == theme.id, selected: selectedTheme?.id == theme.id,
theme: presentationData.theme, theme: presentationData.theme,
strings: presentationData.strings, strings: presentationData.strings,
wallpaper: .file(TelegramWallpaper.File(id: wallpaperFile.fileId.id, accessHash: 0, isCreator: false, isDefault: false, isPattern: true, isDark: false, slug: "", file: wallpaperFile, settings: WallpaperSettings(blur: false, motion: false, colors: [], intensity: 100, rotation: 0))) wallpaper: wallpaper
)) ))
} }
@ -1008,6 +1018,15 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
} }
} }
self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in
guard let self, let state = self.currentUniqueGiftChatThemesState, case .ready(true) = state.dataState else {
return
}
if case let .known(value) = offset, value < 100.0 {
self.uniqueGiftChatThemesContext.loadMore()
}
}
self.updateCancelButton() self.updateCancelButton()
} }

View File

@ -1481,7 +1481,7 @@ func openResolvedUrlImpl(
navigationController?.pushViewController(controller) navigationController?.pushViewController(controller)
} }
} }
}, dismissed: { }, openChatTheme: nil, dismissed: {
dismissedImpl?() dismissedImpl?()
}) })
navigationController?.pushViewController(controller) navigationController?.pushViewController(controller)

View File

@ -57,6 +57,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
private var savedIdsPromise = Promise<Set<Int64>?>() private var savedIdsPromise = Promise<Set<Int64>?>()
private var savedIds: Set<Int64>? private var savedIds: Set<Int64>?
private var copyProtectionEnabled = false
init( init(
context: AccountContext, context: AccountContext,
chatLocation: ChatLocation, chatLocation: ChatLocation,
@ -388,14 +390,25 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
} }
}) })
self.savedIdsDisposable = (context.engine.peers.savedMusicIds() let copyProtectionEnabled: Signal<Bool, NoError>
|> deliverOnMainQueue).start(next: { [weak self] savedIds in if case let .peer(peerId) = self.chatLocation {
copyProtectionEnabled = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CopyProtectionEnabled(id: peerId))
} else {
copyProtectionEnabled = .single(false)
}
self.savedIdsDisposable = combineLatest(
queue: Queue.mainQueue(),
context.engine.peers.savedMusicIds(),
copyProtectionEnabled
).start(next: { [weak self] savedIds, copyProtectionEnabled in
guard let self else { guard let self else {
return return
} }
let isFirstTime = self.savedIds == nil let isFirstTime = self.savedIds == nil
self.savedIds = savedIds self.savedIds = savedIds
self.savedIdsPromise.set(.single(savedIds)) self.savedIdsPromise.set(.single(savedIds))
self.copyProtectionEnabled = copyProtectionEnabled
let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : .animated(duration: 0.5, curve: .spring) let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : .animated(duration: 0.5, curve: .spring)
self.updateFloatingHeaderOffset(offset: self.floatingHeaderOffset ?? 0.0, transition: transition) self.updateFloatingHeaderOffset(offset: self.floatingHeaderOffset ?? 0.0, transition: transition)
@ -638,6 +651,12 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
} }
private var isSaved: Bool? { private var isSaved: Bool? {
if self .copyProtectionEnabled {
return nil
}
if case let .peer(peerId) = self.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
return nil
}
guard let fileReference = self.controlsNode.currentFileReference else { guard let fileReference = self.controlsNode.currentFileReference else {
return nil return nil
} }

View File

@ -3768,8 +3768,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return GiftViewScreen(context: context, subject: .message(message), shareStory: shareStory) return GiftViewScreen(context: context, subject: .message(message), shareStory: shareStory)
} }
public func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, dismissed: (() -> Void)?) -> ViewController { public func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, openChatTheme: (() -> Void)?, dismissed: (() -> Void)?) -> ViewController {
let controller = GiftViewScreen(context: context, subject: .uniqueGift(gift, nil), shareStory: shareStory) let controller = GiftViewScreen(context: context, subject: .uniqueGift(gift, nil), shareStory: shareStory, openChatTheme: openChatTheme)
controller.disposed = { controller.disposed = {
dismissed?() dismissed?()
} }

View File

@ -8,6 +8,7 @@ import TelegramCore
import AccountContext import AccountContext
import SwiftSignalKit import SwiftSignalKit
import WallpaperResources import WallpaperResources
import StickerResources
import FastBlur import FastBlur
import Svg import Svg
import GZip import GZip
@ -85,6 +86,7 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
var rotation: CGFloat { get set } var rotation: CGFloat { get set }
func update(wallpaper: TelegramWallpaper, animated: Bool) func update(wallpaper: TelegramWallpaper, animated: Bool)
func update(wallpaper: TelegramWallpaper, starGift: StarGift?, animated: Bool)
func _internalUpdateIsSettingUpWallpaper() func _internalUpdateIsSettingUpWallpaper()
func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition) func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition)
func updateIsLooping(_ isLooping: Bool) func updateIsLooping(_ isLooping: Bool)
@ -758,11 +760,20 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
private var validLayout: (CGSize, WallpaperDisplayMode)? private var validLayout: (CGSize, WallpaperDisplayMode)?
private var wallpaper: TelegramWallpaper? private var wallpaper: TelegramWallpaper?
private var starGift: StarGift?
private var modelRectIndex: Int32?
private var modelStickerNode: DefaultAnimatedStickerNodeImpl?
private var isSettingUpWallpaper: Bool = false private var isSettingUpWallpaper: Bool = false
private struct CachedValidPatternImage { private struct CachedValidPatternImage {
let generate: (TransformImageArguments) -> DrawingContext? let generate: (TransformImageArguments) -> DrawingContext?
let generated: ValidPatternGeneratedImage let generated: ValidPatternGeneratedImage
let rects: [WallpaperGiftPatternRect]
let starGift: StarGift?
let symbolImage: UIImage?
let modelRectIndex: Int32?
let image: UIImage let image: UIImage
} }
@ -771,6 +782,10 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
private struct ValidPatternImage { private struct ValidPatternImage {
let wallpaper: TelegramWallpaper let wallpaper: TelegramWallpaper
let invertPattern: Bool let invertPattern: Bool
let rects: [WallpaperGiftPatternRect]
let starGift: StarGift?
let symbolImage: UIImage?
let modelRectIndex: Int32?
let generate: (TransformImageArguments) -> DrawingContext? let generate: (TransformImageArguments) -> DrawingContext?
} }
private var validPatternImage: ValidPatternImage? private var validPatternImage: ValidPatternImage?
@ -781,10 +796,38 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
let patternColor: UInt32 let patternColor: UInt32
let backgroundColor: UInt32 let backgroundColor: UInt32
let invertPattern: Bool let invertPattern: Bool
let starGift: StarGift?
let modelRectIndex: Int32?
public static func ==(lhs: ValidPatternGeneratedImage, rhs: ValidPatternGeneratedImage) -> Bool {
if lhs.wallpaper != rhs.wallpaper {
return false
}
if lhs.size != rhs.size {
return false
}
if lhs.patternColor != rhs.patternColor {
return false
}
if lhs.backgroundColor != rhs.backgroundColor {
return false
}
if lhs.invertPattern != rhs.invertPattern {
return false
}
if lhs.starGift?.slug != rhs.starGift?.slug {
return false
}
if lhs.modelRectIndex != rhs.modelRectIndex {
return false
}
return true
}
} }
private var validPatternGeneratedImage: ValidPatternGeneratedImage? private var validPatternGeneratedImage: ValidPatternGeneratedImage?
private let patternImageDisposable = MetaDisposable() private let patternImageDisposable = MetaDisposable()
private let symbolImageDisposable = MetaDisposable()
private var bubbleTheme: PresentationTheme? private var bubbleTheme: PresentationTheme?
private var bubbleCorners: PresentationChatBubbleCorners? private var bubbleCorners: PresentationChatBubbleCorners?
@ -930,11 +973,26 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
} }
public func update(wallpaper: TelegramWallpaper, animated: Bool) { public func update(wallpaper: TelegramWallpaper, animated: Bool) {
if self.wallpaper == wallpaper { self.update(wallpaper: wallpaper, starGift: nil, animated: animated)
}
public func update(wallpaper: TelegramWallpaper, starGift: StarGift?, animated: Bool) {
if self.wallpaper == wallpaper && self.starGift == starGift {
return return
} }
let previousWallpaper = self.wallpaper let previousWallpaper = self.wallpaper
let previousStarGift = self.starGift
self.wallpaper = wallpaper self.wallpaper = wallpaper
self.starGift = starGift
if previousWallpaper != wallpaper || previousStarGift?.slug != starGift?.slug {
if let _ = starGift {
self.modelRectIndex = Int32.random(in: 0 ..< 10)
} else {
self.modelRectIndex = nil
}
}
if let _ = previousWallpaper, animated { if let _ = previousWallpaper, animated {
if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) { if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) {
@ -1132,6 +1190,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
} }
default: default:
self.patternImageDisposable.set(nil) self.patternImageDisposable.set(nil)
self.symbolImageDisposable.set(nil)
self.validPatternImage = nil self.validPatternImage = nil
self.patternImageLayer.isHidden = true self.patternImageLayer.isHidden = true
self.patternImageLayer.fillWithColorUntilLoaded = nil self.patternImageLayer.fillWithColorUntilLoaded = nil
@ -1147,6 +1206,9 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
return return
} }
let starGift = self.starGift
let modelRectIndex = self.modelRectIndex
var invertPattern: Bool = false var invertPattern: Bool = false
var patternIsLight: Bool = false var patternIsLight: Bool = false
@ -1170,12 +1232,19 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
} }
} }
if let previousStarGift = self.validPatternImage?.starGift, !updated {
updated = true
if previousStarGift.slug == starGift?.slug {
updated = false
}
}
if updated { if updated {
self.validPatternGeneratedImage = nil self.validPatternGeneratedImage = nil
self.validPatternImage = nil self.validPatternImage = nil
if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper && cachedValidPatternImage.generated.invertPattern == invertPattern { if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper && cachedValidPatternImage.generated.invertPattern == invertPattern && cachedValidPatternImage.starGift == starGift && cachedValidPatternImage.modelRectIndex == modelRectIndex {
self.validPatternImage = ValidPatternImage(wallpaper: cachedValidPatternImage.generated.wallpaper, invertPattern: invertPattern, generate: cachedValidPatternImage.generate) self.validPatternImage = ValidPatternImage(wallpaper: cachedValidPatternImage.generated.wallpaper, invertPattern: invertPattern, rects: cachedValidPatternImage.rects, starGift: cachedValidPatternImage.starGift, symbolImage: cachedValidPatternImage.symbolImage, modelRectIndex: cachedValidPatternImage.modelRectIndex, generate: cachedValidPatternImage.generate)
} else { } else {
func reference(for resource: EngineMediaResource, media: EngineMedia) -> MediaResourceReference { func reference(for resource: EngineMediaResource, media: EngineMedia) -> MediaResourceReference {
return .wallpaper(wallpaper: .slug(file.slug), resource: resource._asResource()) return .wallpaper(wallpaper: .slug(file.slug), resource: resource._asResource())
@ -1189,37 +1258,33 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: reference(for: EngineMediaResource(file.file.resource), media: EngineMedia(file.file)))) convertedRepresentations.append(ImageRepresentationWithReference(representation: .init(dimensions: dimensions, resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: reference(for: EngineMediaResource(file.file.resource), media: EngineMedia(file.file))))
let signal = patternWallpaperImage(account: self.context.account, accountManager: self.context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true) let signal = patternWallpaperImage(account: self.context.account, accountManager: self.context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true)
self.patternImageDisposable.set((signal var symbolImage: Signal<UIImage?, NoError> = .single(nil)
|> deliverOnMainQueue).start(next: { [weak self] generator in if let starGift = self.starGift, case let .unique(uniqueGift) = starGift {
guard let strongSelf = self else { for attribute in uniqueGift.attributes {
if case let .pattern(_, file, _) = attribute, let dimensions = file.dimensions {
let size = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
symbolImage = chatMessageAnimatedSticker(postbox: self.context.account.postbox, userLocation: .other, file: file, small: false, size: size)
|> map { generator -> UIImage? in
return generator(TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: .zero))?.generateImage()
}
break
}
}
}
self.patternImageDisposable.set(combineLatest(queue: Queue.mainQueue(), signal, symbolImage).start(next: { [weak self] generator, symbolImage in
guard let self else {
return return
} }
if let (generator, rects) = generator {
if let generator = generator { self.validPatternImage = ValidPatternImage(wallpaper: wallpaper, invertPattern: invertPattern, rects: rects, starGift: starGift, symbolImage: symbolImage, modelRectIndex: modelRectIndex, generate: generator)
/*generator = { arguments in self.validPatternGeneratedImage = nil
let scale = arguments.scale ?? UIScreenScale if let (size, displayMode) = self.validLayout {
let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true) self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: .immediate)
context.withFlippedContext { c in
if let path = getAppBundle().path(forResource: "PATTERN_static", ofType: "svg"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
if let image = drawSvgImage(data, CGSize(width: arguments.drawingSize.width * scale, height: arguments.drawingSize.height * scale), .clear, .black, false) {
c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: arguments.drawingSize))
}
}
}
return context
}*/
strongSelf.validPatternImage = ValidPatternImage(wallpaper: wallpaper, invertPattern: invertPattern, generate: generator)
strongSelf.validPatternGeneratedImage = nil
if let (size, displayMode) = strongSelf.validLayout {
strongSelf.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: .immediate)
} else { } else {
strongSelf._isReady.set(true) self._isReady.set(true)
} }
} else { } else {
strongSelf._isReady.set(true) self._isReady.set(true)
} }
})) }))
} }
@ -1244,7 +1309,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
self.patternImageLayer.backgroundColor = nil self.patternImageLayer.backgroundColor = nil
} }
let updatedGeneratedImage = ValidPatternGeneratedImage(wallpaper: validPatternImage.wallpaper, size: size, patternColor: patternColor.rgb, backgroundColor: patternBackgroundColor.rgb, invertPattern: invertPattern) let updatedGeneratedImage = ValidPatternGeneratedImage(wallpaper: validPatternImage.wallpaper, size: size, patternColor: patternColor.rgb, backgroundColor: patternBackgroundColor.rgb, invertPattern: invertPattern, starGift: starGift, modelRectIndex: modelRectIndex)
if self.validPatternGeneratedImage != updatedGeneratedImage { if self.validPatternGeneratedImage != updatedGeneratedImage {
self.validPatternGeneratedImage = updatedGeneratedImage self.validPatternGeneratedImage = updatedGeneratedImage
@ -1256,7 +1321,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
self.patternImageLayer.suspendCompositionUpdates = false self.patternImageLayer.suspendCompositionUpdates = false
self.patternImageLayer.updateCompositionIfNeeded() self.patternImageLayer.updateCompositionIfNeeded()
} else { } else {
let patternArguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: PatternWallpaperArguments(colors: [patternBackgroundColor], rotation: nil, customPatternColor: patternColor, preview: false, displayMode: displayMode.argumentsDisplayMode), scale: min(2.0, UIScreenScale)) let patternArguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: PatternWallpaperArguments(colors: [patternBackgroundColor], rotation: nil, customPatternColor: patternColor, preview: false, displayMode: displayMode.argumentsDisplayMode, symbolImage: generateTintedImage(image: validPatternImage.symbolImage, color: .white), modelRectIndex: self.modelRectIndex), scale: min(2.0, UIScreenScale))
if self.useSharedAnimationPhase || self.patternImageLayer.contents == nil { if self.useSharedAnimationPhase || self.patternImageLayer.contents == nil {
if let drawingContext = validPatternImage.generate(patternArguments) { if let drawingContext = validPatternImage.generate(patternArguments) {
if let image = drawingContext.generateImage() { if let image = drawingContext.generateImage() {
@ -1267,7 +1332,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
self.patternImageLayer.updateCompositionIfNeeded() self.patternImageLayer.updateCompositionIfNeeded()
if self.useSharedAnimationPhase { if self.useSharedAnimationPhase {
WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image) WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, rects: validPatternImage.rects, starGift: validPatternImage.starGift, symbolImage: validPatternImage.symbolImage, modelRectIndex: validPatternImage.modelRectIndex, image: image)
} }
} else { } else {
self.updatePatternPresentation() self.updatePatternPresentation()
@ -1288,7 +1353,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
strongSelf.updatePatternPresentation() strongSelf.updatePatternPresentation()
if let image = image, strongSelf.useSharedAnimationPhase { if let image = image, strongSelf.useSharedAnimationPhase {
WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, image: image) WallpaperBackgroundNodeImpl.cachedValidPatternImage = CachedValidPatternImage(generate: validPatternImage.generate, generated: updatedGeneratedImage, rects: validPatternImage.rects, starGift: validPatternImage.starGift, symbolImage: validPatternImage.symbolImage, modelRectIndex: validPatternImage.modelRectIndex, image: image)
} }
} }
} }
@ -1307,6 +1372,68 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
} }
} }
var modelFile: TelegramMediaFile?
if let validPatternImage = self.validPatternImage, !validPatternImage.rects.isEmpty, let starGift = validPatternImage.starGift {
if case let .unique(uniqueGift) = starGift {
for attribute in uniqueGift.attributes {
if case let .model(_, file, _) = attribute {
modelFile = file
}
}
}
}
if let validPatternImage = self.validPatternImage, !validPatternImage.rects.isEmpty, var modelRectIndex = self.modelRectIndex, let modelFile {
let filteredRects = validPatternImage.rects.filter { $0.center.y > 240.0 }
modelRectIndex = modelRectIndex % Int32(filteredRects.count);
let rect = filteredRects[Int(modelRectIndex)]
let modelStickerNode: DefaultAnimatedStickerNodeImpl
if let current = self.modelStickerNode {
modelStickerNode = current
} else {
modelStickerNode = DefaultAnimatedStickerNodeImpl()
modelStickerNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: modelFile.resource, isVideo: false), width: 96, height: 96, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
modelStickerNode.visibility = true
self.modelStickerNode = modelStickerNode
self.addSubnode(modelStickerNode)
}
let targetSize: CGSize = self.bounds.size
let containerSize: CGSize = rect.containerSize
let useAspectFit: Bool = false
let renderScale: CGFloat = useAspectFit
? min(targetSize.width / containerSize.width, targetSize.height / containerSize.height)
: max(targetSize.width / containerSize.width, targetSize.height / containerSize.height)
let drawingSize = CGSize(width: containerSize.width * renderScale, height: containerSize.height * renderScale)
let offsetX = (targetSize.width - drawingSize.width) * 0.5
let offsetY = (targetSize.height - drawingSize.height) * 0.5
let onScreenCenter = CGPoint(x: offsetX + rect.center.x * renderScale, y: offsetY + rect.center.y * renderScale)
let side = rect.side * rect.scale * renderScale
modelStickerNode.bounds = CGRect(origin: .zero, size: CGSize(width: side, height: side))
modelStickerNode.position = onScreenCenter
modelStickerNode.updateLayout(size: modelStickerNode.bounds.size)
modelStickerNode.alpha = 0.5
modelStickerNode.layer.transform = CATransform3DMakeRotation(rect.rotation, 0, 0, 1)
} else {
if let modelStickerNode = self.modelStickerNode {
self.modelStickerNode = nil
if transition.isAnimated {
modelStickerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak modelStickerNode] _ in
modelStickerNode?.removeFromSupernode()
})
} else {
modelStickerNode.removeFromSupernode()
}
}
}
transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: size)) transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: size))
} }
@ -1559,3 +1686,14 @@ private protocol WallpaperComponentView: AnyObject {
public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false) -> WallpaperBackgroundNode { public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false) -> WallpaperBackgroundNode {
return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase) return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
} }
private extension StarGift {
var slug: String? {
switch self {
case let .unique(uniqueGift):
return uniqueGift.slug
default:
return nil
}
}
}

View File

@ -353,14 +353,18 @@ public struct PatternWallpaperArguments: TransformImageCustomArguments {
let customPatternColor: UIColor? let customPatternColor: UIColor?
let bakePatternAlpha: CGFloat let bakePatternAlpha: CGFloat
let displayMode: DisplayMode let displayMode: DisplayMode
let symbolImage: UIImage?
let modelRectIndex: Int32?
public init(colors: [UIColor], rotation: Int32?, customPatternColor: UIColor? = nil, preview: Bool = false, bakePatternAlpha: CGFloat = 1.0, displayMode: DisplayMode = .aspectFill) { public init(colors: [UIColor], rotation: Int32?, customPatternColor: UIColor? = nil, preview: Bool = false, bakePatternAlpha: CGFloat = 1.0, displayMode: DisplayMode = .aspectFill, symbolImage: UIImage? = nil, modelRectIndex: Int32? = nil) {
self.colors = colors self.colors = colors
self.rotation = rotation self.rotation = rotation
self.customPatternColor = customPatternColor self.customPatternColor = customPatternColor
self.preview = preview self.preview = preview
self.bakePatternAlpha = bakePatternAlpha self.bakePatternAlpha = bakePatternAlpha
self.displayMode = displayMode self.displayMode = displayMode
self.symbolImage = symbolImage
self.modelRectIndex = modelRectIndex
} }
public func serialized() -> NSArray { public func serialized() -> NSArray {
@ -373,6 +377,9 @@ public struct PatternWallpaperArguments: TransformImageCustomArguments {
array.add(NSNumber(value: self.preview)) array.add(NSNumber(value: self.preview))
array.add(NSNumber(value: Double(self.bakePatternAlpha))) array.add(NSNumber(value: Double(self.bakePatternAlpha)))
array.add(NSNumber(value: self.displayMode.rawValue)) array.add(NSNumber(value: self.displayMode.rawValue))
if let symbolImage {
array.add(symbolImage)
}
return array return array
} }
} }
@ -470,18 +477,34 @@ private func patternWallpaperDatas(account: Account, accountManager: AccountMana
} }
} }
public func patternWallpaperImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false) -> Signal<((TransformImageArguments) -> DrawingContext?)?, NoError> { public struct WallpaperGiftPatternRect: Equatable {
public let containerSize: CGSize
public let center: CGPoint
public let side: CGFloat
public let scale: CGFloat
public let rotation: CGFloat
fileprivate init(containerSize: CGSize, rect: GiftPatternRect) {
self.containerSize = containerSize
self.center = rect.center
self.side = rect.side
self.scale = rect.scale
self.rotation = rect.rotation
}
}
public func patternWallpaperImage(account: Account, accountManager: AccountManager<TelegramAccountManagerTypes>, representations: [ImageRepresentationWithReference], mode: PatternWallpaperDrawMode, autoFetchFullSize: Bool = false, forcePrepared: Bool = false) -> Signal<(generator: (TransformImageArguments) -> DrawingContext?, rects: [WallpaperGiftPatternRect])?, NoError> {
return patternWallpaperDatas(account: account, accountManager: accountManager, representations: representations, mode: mode, autoFetchFullSize: autoFetchFullSize) return patternWallpaperDatas(account: account, accountManager: accountManager, representations: representations, mode: mode, autoFetchFullSize: autoFetchFullSize)
|> mapToSignal { fullSizeData, fullSizeComplete in |> mapToSignal { fullSizeData, fullSizeComplete in
if !autoFetchFullSize || fullSizeComplete { if !autoFetchFullSize || fullSizeComplete {
return patternWallpaperImageInternal(fullSizeData: fullSizeData, fullSizeComplete: fullSizeComplete, mode: mode) return patternWallpaperImageInternal(fullSizeData: fullSizeData, fullSizeComplete: fullSizeComplete, mode: mode, forcePrepared: forcePrepared)
} else { } else {
return .single(nil) return .single(nil)
} }
} }
} }
private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete: Bool, mode: PatternWallpaperDrawMode) -> Signal<((TransformImageArguments) -> DrawingContext?)?, NoError> { private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete: Bool, mode: PatternWallpaperDrawMode, forcePrepared: Bool = false) -> Signal<(generator: (TransformImageArguments) -> DrawingContext?, rects: [WallpaperGiftPatternRect])?, NoError> {
var prominent = false var prominent = false
if case .thumbnail = mode { if case .thumbnail = mode {
prominent = true prominent = true
@ -491,7 +514,11 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
return .single((fullSizeData, fullSizeComplete)) return .single((fullSizeData, fullSizeComplete))
|> map { fullSizeData, fullSizeComplete in |> map { fullSizeData, fullSizeComplete in
return { arguments in var rects: [WallpaperGiftPatternRect] = []
if let fullSizeData, let patternData = getGiftPatternData(fullSizeData) {
rects = patternData.rects.map { WallpaperGiftPatternRect(containerSize: patternData.size, rect: $0) }
}
return ({ arguments in
var scale = scale var scale = scale
if scale.isZero { if scale.isZero {
scale = arguments.scale ?? UIScreenScale scale = arguments.scale ?? UIScreenScale
@ -561,7 +588,7 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
var image: UIImage? var image: UIImage?
if let fullSizeData = fullSizeData { if let fullSizeData = fullSizeData {
if mode == .screen { if mode == .screen {
image = renderPreparedImage(fullSizeData, CGSize(width: size.width * context.scale, height: size.height * context.scale), .black, 1.0, displayMode == .aspectFit) image = renderPreparedImageWithSymbol(fullSizeData, CGSize(width: size.width * context.scale, height: size.height * context.scale), .black, 1.0, displayMode == .aspectFit, customArguments.symbolImage, customArguments.modelRectIndex ?? -1)
} else { } else {
image = UIImage(data: fullSizeData) image = UIImage(data: fullSizeData)
} }
@ -674,7 +701,7 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
} else { } else {
return nil return nil
} }
} }, rects)
} }
} }
@ -1452,6 +1479,10 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
wallpaperSignal = .single((backgroundColor, incomingColors, outgoingColors, image, options.blur, false, 1.0, rotation)) wallpaperSignal = .single((backgroundColor, incomingColors, outgoingColors, image, options.blur, false, 1.0, rotation))
} }
case let .file(file): case let .file(file):
if file.settings.intensity == 100 {
print()
}
rotation = file.settings.rotation rotation = file.settings.rotation
if file.isPattern, let intensity = file.settings.intensity, intensity < 0 { if file.isPattern, let intensity = file.settings.intensity, intensity < 0 {
backgroundColor = (.black, nil, []) backgroundColor = (.black, nil, [])
@ -1464,6 +1495,9 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
} else { } else {
backgroundColor = (theme.chatList.backgroundColor, nil, []) backgroundColor = (theme.chatList.backgroundColor, nil, [])
} }
wallpaperSignal = cachedWallpaper(account: account, slug: file.slug, settings: file.settings) wallpaperSignal = cachedWallpaper(account: account, slug: file.slug, settings: file.settings)
|> mapToSignal { wallpaper in |> mapToSignal { wallpaper in
if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper { if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper {
@ -1479,6 +1513,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
let convertedPreviewRepresentations : [ImageRepresentationWithReference] = file.file.previewRepresentations.map { let convertedPreviewRepresentations : [ImageRepresentationWithReference] = file.file.previewRepresentations.map {
ImageRepresentationWithReference(representation: $0, reference: .wallpaper(wallpaper: .slug(file.slug), resource: $0.resource)) ImageRepresentationWithReference(representation: $0, reference: .wallpaper(wallpaper: .slug(file.slug), resource: $0.resource))
} }
let useFallback = convertedPreviewRepresentations.isEmpty
var convertedRepresentations: [ImageRepresentationWithReference] = [] var convertedRepresentations: [ImageRepresentationWithReference] = []
convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource))) convertedRepresentations.append(ImageRepresentationWithReference(representation: TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 100, height: 100), resource: file.file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: .wallpaper(wallpaper: .slug(file.slug), resource: file.file.resource)))
@ -1511,12 +1546,11 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
let isLight = UIColor.average(of: file.settings.colors.map(UIColor.init(rgb:))).hsb.b > 0.3 let isLight = UIColor.average(of: file.settings.colors.map(UIColor.init(rgb:))).hsb.b > 0.3
arguments = PatternWallpaperArguments(colors: [.clear], rotation: nil, customPatternColor: isLight ? .black : .white) arguments = PatternWallpaperArguments(colors: [.clear], rotation: nil, customPatternColor: isLight ? .black : .white)
} }
return patternWallpaperImage(account: account, accountManager: accountManager, representations: useFallback ? convertedRepresentations : convertedPreviewRepresentations, mode: useFallback ? .screen : .thumbnail, autoFetchFullSize: true)
return patternWallpaperImage(account: account, accountManager: accountManager, representations: convertedPreviewRepresentations, mode: .thumbnail, autoFetchFullSize: true) |> mapToSignal { generatorAndRects -> Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError> in
|> mapToSignal { generator -> Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError> in
let imageSize = CGSize(width: 148.0, height: 320.0) let imageSize = CGSize(width: 148.0, height: 320.0)
let imageArguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil, custom: arguments) let imageArguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil, custom: arguments)
let context = generator?(imageArguments) let context = generatorAndRects?.generator(imageArguments)
let image = context?.generateImage() let image = context?.generateImage()
if !file.settings.colors.isEmpty { if !file.settings.colors.isEmpty {