mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-05 05:51:42 +00:00
Merge remote-tracking branch 'refs/remotes/origin/master'
This commit is contained in:
commit
980ced37a8
@ -14924,3 +14924,87 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Gift.Upgrade.GiftUpgrade" = "Pay %@ for 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 %@";
|
||||
|
||||
"Conversation.Theme.GiftTransfer.Text" = "This gift is already your theme in the chat with **%@**. Remove it there and use it here instead?";
|
||||
"Conversation.Theme.GiftTransfer.Proceed" = "Yes";
|
||||
|
||||
"PeerInfo.Tabs.SetMainTab" = "Set as Main Tab";
|
||||
"PeerInfo.Tabs.SetMainTab.Succeed" = "Tab order changed.";
|
||||
|
||||
"MediaPlayer.SavedMusic.AddToProfile" = "Add to Profile";
|
||||
"MediaPlayer.SavedMusic.RemoveFromProfile" = "This audio is visible on your profile. [Remove >]()";
|
||||
|
||||
"MediaPlayer.SavedMusic.AddedToProfile.View" = "View";
|
||||
"MediaPlayer.SavedMusic.AddedToProfile" = "Audio added to your profile.";
|
||||
"MediaPlayer.SavedMusic.RemovedFromProfile" = "Audio removed from your profile.";
|
||||
|
||||
"MediaPlayer.ContextMenu.SaveToFiles" = "Save to Files";
|
||||
"MediaPlayer.ContextMenu.SaveTo" = "Save to...";
|
||||
"MediaPlayer.ContextMenu.SaveTo.Profile" = "Profile";
|
||||
"MediaPlayer.ContextMenu.SaveTo.SavedMessages" = "Saved Messages";
|
||||
"MediaPlayer.ContextMenu.SaveTo.Files" = "Files";
|
||||
"MediaPlayer.ContextMenu.SaveTo.Info" = "Choose where you want this audio to be saved.";
|
||||
"MediaPlayer.ContextMenu.ShowInChat" = "Show in Chat";
|
||||
"MediaPlayer.ContextMenu.Forward" = "Forward";
|
||||
"MediaPlayer.ContextMenu.Delete" = "Delete";
|
||||
"MediaPlayer.ContextMenu.Remove" = "Remove";
|
||||
|
||||
"MediaPlayer.Playlist.ThisChat" = "AUDIO IN THIS CHAT";
|
||||
"MediaPlayer.Playlist.SavedMusic" = "%@'S PLAYLIST";
|
||||
"MediaPlayer.Playlist.SavedMusicYou" = "YOUR PLAYLIST";
|
||||
|
||||
"Notification.PremiumGift.Stars_1" = "%@ Star";
|
||||
"Notification.PremiumGift.Stars_any" = "%@ Stars";
|
||||
|
||||
"Ton.ProceedsOverview" = "PROCEEDS OVERVIEW";
|
||||
"Ton.AvailableBalance" = "Balance Available to Withdraw";
|
||||
"Ton.LifetimeProceeds" = "Total Lifetime Proceeds";
|
||||
|
||||
"Ton.WithdrawViaFragment" = "Withdraw via Fragment";
|
||||
"Ton.WithdrawViaFragment.Info" = "Collect your TON using Fragment. [Learn More >]()";
|
||||
"Ton.WithdrawViaFragment.Info_URL" = "https://telegram.org/tos/bot-developers#6-2-2-tpa-balance";
|
||||
|
||||
"MESSAGE_GIFT_THEME" = "%1$@ changed theme to %2$@";
|
||||
|
||||
"Gift.Upgrade.Skip" = "Skip";
|
||||
"Gift.Upgrade.UpgradeNext" = "Upgrade Next Gift";
|
||||
|
||||
"Gift.Value.DescriptionAveragePrice" = "This is the average sale price of **%@** on Telegram and Fragment over the past month.";
|
||||
"Gift.Value.DescriptionLastPriceFragment" = "This is the last price at which **%@** was last sold on Fragment.";
|
||||
"Gift.Value.DescriptionLastPriceTelegram" = "This is the last price at which **%@** was last sold on Telegram.";
|
||||
|
||||
"Gift.Value.LastPriceInfo" = "**%1$@** is the last price for %2$@ gifts listed on Telegram and Fragment.";
|
||||
"Gift.Value.MinimumPriceInfo" = "**%1$@** is the floor price for %2$@ gifts listed on Telegram and Fragment.";
|
||||
"Gift.Value.AveragePriceInfo" = "**%1$@** is the average sale price for %2$@ gifts listed on Telegram and Fragment over the past month.";
|
||||
|
||||
"Gift.Value.AveragePrice" = "Last Sale";
|
||||
"Gift.Value.InitialSale" = "Initial Sale";
|
||||
"Gift.Value.InitialPrice" = "Initial Price";
|
||||
"Gift.Value.LastSale" = "Last Sale";
|
||||
"Gift.Value.LastPrice" = "Last Price";
|
||||
"Gift.Value.MinimumPrice" = "Minimum Price";
|
||||
"Gift.Value.AveragePrice" = "Average Price";
|
||||
"Gift.Value.ForSaleOnTelegram" = "for sale on Telegram";
|
||||
"Gift.Value.ForSaleOnFragment" = "for sale on Fragment";
|
||||
|
||||
"Gift.View.Context.SetAsTheme" = "Set as Theme in...";
|
||||
|
||||
"Notification.GiftStars" = "Gift for %@";
|
||||
"Notification.GiftStars.Stars_1" = "%@ Star";
|
||||
"Notification.GiftStars.Stars_any" = "%@ Stars";
|
||||
|
||||
"Notification.PrepaidGiftUpgrade" = "Gift Upgrade for %@";
|
||||
"Notification.PrepaidGiftUpgrade.Stars_1" = "%@ Star";
|
||||
"Notification.PrepaidGiftUpgrade.Stars_any" = "%@ Stars";
|
||||
|
||||
"ProfileLevelInfo.RatingTitle" = "Rating";
|
||||
"ProfileLevelInfo.FutureRatingTitle" = "Future Rating";
|
||||
|
||||
|
||||
@ -1326,7 +1326,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
|
||||
func makeStarsIntroScreen(context: AccountContext) -> 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 makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController
|
||||
|
||||
@ -2503,7 +2503,7 @@ public final class ChatListNode: ListView {
|
||||
case let .user(userType):
|
||||
if case let .user(user) = peer {
|
||||
match = true
|
||||
if user.id.isVerificationCodes {
|
||||
if user.id.isVerificationCodes || user.id.isTelegramNotifications {
|
||||
match = false
|
||||
}
|
||||
if let isBot = userType.isBot {
|
||||
|
||||
@ -65,6 +65,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
||||
public let clipsContent: Bool
|
||||
public let isScrollEnabled: Bool
|
||||
public let hasDimView: Bool
|
||||
public let autoAnimateOut: Bool
|
||||
public let externalState: ExternalState?
|
||||
public let animateOut: ActionSlot<Action<()>>
|
||||
public let onPan: () -> Void
|
||||
@ -77,6 +78,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
||||
clipsContent: Bool = false,
|
||||
isScrollEnabled: Bool = true,
|
||||
hasDimView: Bool = true,
|
||||
autoAnimateOut: Bool = true,
|
||||
externalState: ExternalState? = nil,
|
||||
animateOut: ActionSlot<Action<()>>,
|
||||
onPan: @escaping () -> Void = {},
|
||||
@ -88,6 +90,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
||||
self.clipsContent = clipsContent
|
||||
self.isScrollEnabled = isScrollEnabled
|
||||
self.hasDimView = hasDimView
|
||||
self.autoAnimateOut = autoAnimateOut
|
||||
self.externalState = externalState
|
||||
self.animateOut = animateOut
|
||||
self.onPan = onPan
|
||||
@ -110,6 +113,9 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
||||
if lhs.hasDimView != rhs.hasDimView {
|
||||
return false
|
||||
}
|
||||
if lhs.autoAnimateOut != rhs.autoAnimateOut {
|
||||
return false
|
||||
}
|
||||
if lhs.animateOut != rhs.animateOut {
|
||||
return false
|
||||
}
|
||||
@ -430,9 +436,15 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
||||
if environment[SheetComponentEnvironment.self].value.isDisplaying, !self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) {
|
||||
self.animateIn()
|
||||
} else if !environment[SheetComponentEnvironment.self].value.isDisplaying, self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) {
|
||||
self.animateOut(completion: {})
|
||||
if component.autoAnimateOut {
|
||||
self.animateOut(completion: {})
|
||||
}
|
||||
}
|
||||
if !sheetEnvironment.isDisplaying && !component.autoAnimateOut {
|
||||
|
||||
} else {
|
||||
self.previousIsDisplaying = sheetEnvironment.isDisplaying
|
||||
}
|
||||
self.previousIsDisplaying = sheetEnvironment.isDisplaying
|
||||
self.dismiss = sheetEnvironment.dismiss
|
||||
|
||||
return availableSize
|
||||
|
||||
@ -349,6 +349,8 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
||||
public final var beganInteractiveDragging: (CGPoint) -> Void = { _ in }
|
||||
public final var endedInteractiveDragging: (CGPoint) -> Void = { _ in }
|
||||
public final var didEndScrolling: ((Bool) -> Void)?
|
||||
public final var didEndScrollingWithOverscroll: (() -> Void)?
|
||||
|
||||
|
||||
private var currentGeneralScrollDirection: GeneralScrollDirection?
|
||||
public final var generalScrollDirectionUpdated: (GeneralScrollDirection) -> Void = { _ in }
|
||||
@ -891,6 +893,10 @@ open class ListView: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDel
|
||||
self.resetScrollIndicatorFlashTimer(start: false)
|
||||
|
||||
self.isAuxiliaryDisplayLinkEnabled = true
|
||||
|
||||
if scrollView.contentOffset.y < -48.0 {
|
||||
self.didEndScrollingWithOverscroll?()
|
||||
}
|
||||
} else {
|
||||
self.isDeceleratingAfterTracking = false
|
||||
self.resetHeaderItemsFlashTimer(start: true)
|
||||
|
||||
@ -4,8 +4,28 @@
|
||||
#import <Foundation/Foundation.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);
|
||||
|
||||
GiftPatternData * _Nullable getGiftPatternData(NSData * _Nonnull data);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -959,7 +959,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
||||
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($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[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($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[1279133341] = { return Api.account.AutoSaveSettings.parse_autoSaveSettings($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[400029819] = { return Api.account.ConnectedBots.parse_connectedBots($0) }
|
||||
dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) }
|
||||
|
||||
@ -289,7 +289,7 @@ public extension Api {
|
||||
public extension Api {
|
||||
enum StarGift: TypeConstructorDescription {
|
||||
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?, 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) {
|
||||
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 << 9) != 0 {serializeInt32(lockedUntilDate!, buffer: buffer, boxed: false)}
|
||||
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 {
|
||||
buffer.appendInt32(648369470)
|
||||
buffer.appendInt32(468707429)
|
||||
}
|
||||
serializeInt32(flags, 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 << 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 << 10) != 0 {themePeer!.serialize(buffer, true)}
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -352,8 +353,8 @@ public extension Api {
|
||||
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):
|
||||
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):
|
||||
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)])
|
||||
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), ("themePeer", themePeer as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,6 +464,10 @@ public extension Api {
|
||||
if Int(_1!) & Int(1 << 8) != 0 {_16 = reader.readInt64() }
|
||||
var _17: String?
|
||||
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 _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -480,8 +485,9 @@ public extension Api {
|
||||
let _c15 = (Int(_1!) & Int(1 << 5) == 0) || _15 != nil
|
||||
let _c16 = (Int(_1!) & Int(1 << 8) == 0) || _16 != 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 {
|
||||
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)
|
||||
let _c18 = (Int(_1!) & Int(1 << 10) == 0) || _18 != nil
|
||||
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 {
|
||||
return nil
|
||||
|
||||
@ -570,14 +570,14 @@ public extension Api.account {
|
||||
}
|
||||
public extension Api.account {
|
||||
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
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
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 {
|
||||
buffer.appendInt32(1271855483)
|
||||
buffer.appendInt32(373835863)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(hash, buffer: buffer, boxed: false)
|
||||
@ -586,6 +586,16 @@ public extension Api.account {
|
||||
for item in themes {
|
||||
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)}
|
||||
break
|
||||
case .chatThemesNotModified:
|
||||
@ -599,8 +609,8 @@ public extension Api.account {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .chatThemes(let flags, let hash, let themes, let nextOffset):
|
||||
return ("chatThemes", [("flags", flags as Any), ("hash", hash as Any), ("themes", themes as Any), ("nextOffset", nextOffset as Any)])
|
||||
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), ("chats", chats as Any), ("users", users as Any), ("nextOffset", nextOffset as Any)])
|
||||
case .chatThemesNotModified:
|
||||
return ("chatThemesNotModified", [])
|
||||
}
|
||||
@ -615,14 +625,24 @@ public extension Api.account {
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ChatTheme.self)
|
||||
}
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() }
|
||||
var _4: [Api.Chat]?
|
||||
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 _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.account.ChatThemes.chatThemes(flags: _1!, hash: _2!, themes: _3!, nextOffset: _4)
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
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 {
|
||||
return nil
|
||||
|
||||
@ -359,7 +359,9 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne
|
||||
resellForTonOnly: false,
|
||||
releasedBy: nil,
|
||||
valueAmount: nil,
|
||||
valueCurrency: nil
|
||||
valueCurrency: nil,
|
||||
flags: [],
|
||||
themePeerId: nil
|
||||
)
|
||||
if let entry = CodableEntry(RecentStarGiftItem(gift)) {
|
||||
items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry))
|
||||
|
||||
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 213
|
||||
return 214
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
||||
@ -230,6 +230,37 @@ public enum TelegramWallpaper: Equatable {
|
||||
self.file = file
|
||||
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)
|
||||
|
||||
@ -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 typealias Result = CachedUserData.BotPreview?
|
||||
|
||||
|
||||
@ -318,6 +318,18 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
||||
case releasedBy
|
||||
case valueAmount
|
||||
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 {
|
||||
@ -593,8 +605,10 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
||||
public let releasedBy: EnginePeer.Id?
|
||||
public let valueAmount: Int64?
|
||||
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.giftId = giftId
|
||||
self.title = title
|
||||
@ -609,6 +623,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
||||
self.releasedBy = releasedBy
|
||||
self.valueAmount = valueAmount
|
||||
self.valueCurrency = valueCurrency
|
||||
self.flags = flags
|
||||
self.themePeerId = themePeerId
|
||||
}
|
||||
|
||||
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.valueAmount = try container.decodeIfPresent(Int64.self, forKey: .valueAmount)
|
||||
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) {
|
||||
@ -672,6 +690,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
||||
self.releasedBy = decoder.decodeOptionalInt64ForKey(CodingKeys.releasedBy.rawValue).flatMap { EnginePeer.Id($0) }
|
||||
self.valueAmount = decoder.decodeOptionalInt64ForKey(CodingKeys.valueAmount.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 {
|
||||
@ -697,6 +717,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
||||
try container.encodeIfPresent(self.releasedBy, forKey: .releasedBy)
|
||||
try container.encodeIfPresent(self.valueAmount, forKey: .valueAmount)
|
||||
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) {
|
||||
@ -738,6 +760,12 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
||||
encoder.encodeNil(forKey: CodingKeys.valueAmount.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 {
|
||||
@ -755,7 +783,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
||||
resellForTonOnly: self.resellForTonOnly,
|
||||
releasedBy: self.releasedBy,
|
||||
valueAmount: self.valueAmount,
|
||||
valueCurrency: self.valueCurrency
|
||||
valueCurrency: self.valueCurrency,
|
||||
flags: self.flags,
|
||||
themePeerId: self.themePeerId
|
||||
)
|
||||
}
|
||||
|
||||
@ -774,7 +804,30 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
||||
resellForTonOnly: resellForTonOnly,
|
||||
releasedBy: self.releasedBy,
|
||||
valueAmount: self.valueAmount,
|
||||
valueCurrency: self.valueCurrency
|
||||
valueCurrency: self.valueCurrency,
|
||||
flags: self.flags,
|
||||
themePeerId: self.themePeerId
|
||||
)
|
||||
}
|
||||
|
||||
public func withThemePeerId(_ themePeerId: EnginePeer.Id?) -> UniqueGift {
|
||||
return UniqueGift(
|
||||
id: self.id,
|
||||
giftId: self.giftId,
|
||||
title: self.title,
|
||||
number: self.number,
|
||||
slug: self.slug,
|
||||
owner: self.owner,
|
||||
attributes: self.attributes,
|
||||
availability: self.availability,
|
||||
giftAddress: self.giftAddress,
|
||||
resellAmounts: self.resellAmounts,
|
||||
resellForTonOnly: self.resellForTonOnly,
|
||||
releasedBy: self.releasedBy,
|
||||
valueAmount: self.valueAmount,
|
||||
valueCurrency: self.valueCurrency,
|
||||
flags: self.flags,
|
||||
themePeerId: themePeerId
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -884,7 +937,7 @@ extension StarGift {
|
||||
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))
|
||||
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
|
||||
if let ownerAddress {
|
||||
owner = .address(ownerAddress)
|
||||
@ -896,7 +949,11 @@ extension StarGift {
|
||||
return nil
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1322,6 +1379,7 @@ private final class ProfileGiftsContextImpl {
|
||||
|
||||
private var sorting: ProfileGiftsContext.Sorting
|
||||
private var filter: ProfileGiftsContext.Filters
|
||||
private var limit: Int32
|
||||
|
||||
private var gifts: [ProfileGiftsContext.State.StarGift] = []
|
||||
private var count: Int32?
|
||||
@ -1345,7 +1403,8 @@ private final class ProfileGiftsContextImpl {
|
||||
peerId: EnginePeer.Id,
|
||||
collectionId: Int32?,
|
||||
sorting: ProfileGiftsContext.Sorting,
|
||||
filter: ProfileGiftsContext.Filters
|
||||
filter: ProfileGiftsContext.Filters,
|
||||
limit: Int32
|
||||
) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
@ -1353,6 +1412,7 @@ private final class ProfileGiftsContextImpl {
|
||||
self.collectionId = collectionId
|
||||
self.sorting = sorting
|
||||
self.filter = filter
|
||||
self.limit = limit
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
@ -1377,6 +1437,7 @@ private final class ProfileGiftsContextImpl {
|
||||
let postbox = self.account.postbox
|
||||
let filter = self.filter
|
||||
let sorting = self.sorting
|
||||
let limit = self.limit
|
||||
|
||||
let isFiltered = self.filter != .All || self.sorting != .date
|
||||
if !isFiltered {
|
||||
@ -1464,7 +1525,7 @@ private final class ProfileGiftsContextImpl {
|
||||
if !filter.contains(.unique) {
|
||||
flags |= (1 << 4)
|
||||
}
|
||||
return network.request(Api.functions.payments.getSavedStarGifts(flags: flags, peer: inputPeer, collectionId: collectionId, offset: initialNextOffset ?? "", limit: 36))
|
||||
return network.request(Api.functions.payments.getSavedStarGifts(flags: flags, peer: inputPeer, collectionId: collectionId, offset: initialNextOffset ?? "", limit: limit))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.payments.SavedStarGifts?, NoError> in
|
||||
return .single(nil)
|
||||
@ -2363,14 +2424,15 @@ public final class ProfileGiftsContext {
|
||||
peerId: EnginePeer.Id,
|
||||
collectionId: Int32? = nil,
|
||||
sorting: ProfileGiftsContext.Sorting = .date,
|
||||
filter: ProfileGiftsContext.Filters = .All
|
||||
filter: ProfileGiftsContext.Filters = .All,
|
||||
limit: Int32 = 36
|
||||
) {
|
||||
self.peerId = peerId
|
||||
self.collectionId = collectionId
|
||||
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return ProfileGiftsContextImpl(queue: queue, account: account, peerId: peerId, collectionId: collectionId, sorting: sorting, filter: filter)
|
||||
return ProfileGiftsContextImpl(queue: queue, account: account, peerId: peerId, collectionId: collectionId, sorting: sorting, filter: filter, limit: limit)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1678,6 +1678,8 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
|
||||
return .fail(.alreadyPaid)
|
||||
} else if error.errorDescription == "STARGIFT_USAGE_LIMITED" {
|
||||
return .fail(.starGiftOutOfStock)
|
||||
} else if error.errorDescription == "STARGIFT_USER_USAGE_LIMITED" {
|
||||
return .fail(.starGiftUserLimit)
|
||||
}
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
@ -104,20 +104,20 @@ public enum ChatTheme: PostboxCoding, Codable, Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .gift(lhsGift, _):
|
||||
if case let .gift(rhsGift, _) = rhs {
|
||||
case let .gift(lhsGift, lhsThemeSettings):
|
||||
if case let .gift(rhsGift, rhsThemeSettings) = rhs {
|
||||
switch lhsGift {
|
||||
case .generic(let lhsGeneric):
|
||||
case let .generic(lhsGeneric):
|
||||
switch rhsGift {
|
||||
case .generic(let rhsGeneric):
|
||||
return lhsGeneric == rhsGeneric
|
||||
case let .generic(rhsGeneric):
|
||||
return lhsGeneric == rhsGeneric && lhsThemeSettings == rhsThemeSettings
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case .unique(let lhsUnique):
|
||||
case let .unique(lhsUnique):
|
||||
switch rhsGift {
|
||||
case .unique(let rhsUnique):
|
||||
return lhsUnique.id == rhsUnique.id
|
||||
case let .unique(rhsUnique):
|
||||
return lhsUnique.slug == rhsUnique.slug && lhsThemeSettings == rhsThemeSettings
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@ -148,6 +148,20 @@ public enum ChatTheme: PostboxCoding, Codable, Equatable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func withThemePeerId(_ themePeerId: EnginePeer.Id?) -> ChatTheme {
|
||||
switch self {
|
||||
case .emoticon:
|
||||
return self
|
||||
case let .gift(gift, themeSettings):
|
||||
switch gift {
|
||||
case let .unique(uniqueGift):
|
||||
return .gift(.unique(uniqueGift.withThemePeerId(themePeerId)), themeSettings)
|
||||
case .generic:
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatTheme {
|
||||
@ -235,6 +249,22 @@ func _internal_setChatTheme(account: Account, peerId: PeerId, chatTheme: ChatThe
|
||||
return .complete()
|
||||
}
|
||||
return account.postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
var chatTheme = chatTheme
|
||||
if case let .gift(gift, _) = chatTheme, case let .unique(uniqueGift) = gift, let previousThemePeerId = uniqueGift.themePeerId {
|
||||
transaction.updatePeerCachedData(peerIds: Set([previousThemePeerId]), update: { _, current in
|
||||
if let current = current as? CachedUserData {
|
||||
return current.withUpdatedChatTheme(nil)
|
||||
} else if let current = current as? CachedGroupData {
|
||||
return current.withUpdatedChatTheme(nil)
|
||||
} else if let current = current as? CachedChannelData {
|
||||
return current.withUpdatedChatTheme(nil)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
chatTheme = chatTheme?.withThemePeerId(peerId)
|
||||
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedUserData {
|
||||
return current.withUpdatedChatTheme(chatTheme)
|
||||
@ -246,6 +276,7 @@ func _internal_setChatTheme(account: Account, peerId: PeerId, chatTheme: ChatThe
|
||||
return current
|
||||
}
|
||||
})
|
||||
|
||||
let inputTheme: Api.InputChatTheme
|
||||
if let chatTheme {
|
||||
inputTheme = chatTheme.apiChatTheme
|
||||
@ -466,7 +497,7 @@ public final class UniqueGiftChatThemesContext {
|
||||
private let cacheDisposable = MetaDisposable()
|
||||
|
||||
private var themes: [ChatTheme] = []
|
||||
private var nextOffset: Int32?
|
||||
private var nextOffset: Int32 = 0
|
||||
private var dataState: UniqueGiftChatThemesContext.State.DataState = .ready(canLoadMore: true)
|
||||
|
||||
private let stateValue = Promise<State>()
|
||||
@ -487,7 +518,7 @@ public final class UniqueGiftChatThemesContext {
|
||||
|
||||
public func reload() {
|
||||
self.themes = []
|
||||
self.nextOffset = nil
|
||||
self.nextOffset = 0
|
||||
self.dataState = .ready(canLoadMore: true)
|
||||
self.loadMore(reload: true)
|
||||
}
|
||||
@ -495,6 +526,7 @@ public final class UniqueGiftChatThemesContext {
|
||||
public func loadMore(reload: Bool = false) {
|
||||
let network = self.account.network
|
||||
let postbox = self.account.postbox
|
||||
let accountPeerId = self.account.peerId
|
||||
let dataState = self.dataState
|
||||
let offset = self.nextOffset
|
||||
|
||||
@ -520,13 +552,24 @@ public final class UniqueGiftChatThemesContext {
|
||||
self.pushState()
|
||||
}
|
||||
|
||||
let signal = network.request(Api.functions.account.getUniqueGiftChatThemes(offset: offset ?? 0, limit: 32, hash: 0))
|
||||
|> map { result -> ([ChatTheme], Int32?) in
|
||||
switch result {
|
||||
case let .chatThemes(_, _, themes, nextOffset):
|
||||
return (themes.compactMap { ChatTheme(apiChatTheme: $0) }, nextOffset)
|
||||
case .chatThemesNotModified:
|
||||
return ([], nil)
|
||||
let signal = network.request(Api.functions.account.getUniqueGiftChatThemes(offset: offset, limit: 50, hash: 0))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<([ChatTheme], Int32?), NoError> in
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -545,7 +588,9 @@ public final class UniqueGiftChatThemesContext {
|
||||
} else {
|
||||
self.themes.append(contentsOf: themes)
|
||||
}
|
||||
|
||||
if let nextOffset {
|
||||
self.nextOffset = nextOffset
|
||||
}
|
||||
self.dataState = .ready(canLoadMore: nextOffset != nil)
|
||||
self.pushState()
|
||||
}))
|
||||
|
||||
@ -47,13 +47,36 @@ public func makePresentationTheme(cloudTheme: TelegramTheme, dark: Bool = false)
|
||||
} else {
|
||||
settings = nil
|
||||
}
|
||||
guard let settings = settings else {
|
||||
guard let settings else {
|
||||
return nil
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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: false, 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? {
|
||||
let settings: TelegramThemeSettings?
|
||||
if let exactSettings = cloudTheme.settings?.first(where: { $0.baseTheme == baseTheme }) {
|
||||
|
||||
@ -1574,6 +1574,7 @@ public final class PresentationTheme: Equatable {
|
||||
public let chart: PresentationThemeChart
|
||||
public let preview: Bool
|
||||
public var forceSync: Bool = false
|
||||
public var starGift: StarGift?
|
||||
|
||||
public let resourceCache: PresentationsResourceCache = PresentationsResourceCache()
|
||||
|
||||
|
||||
@ -781,12 +781,15 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
} else {
|
||||
var emoji = ""
|
||||
var additionalAttributes: [String: Any] = [:]
|
||||
var giftTitle: String?
|
||||
switch chatTheme {
|
||||
case let .emoticon(emoticon):
|
||||
emoji = emoticon
|
||||
case let .gift(starGift, _):
|
||||
var file: TelegramMediaFile?
|
||||
|
||||
if case let .unique(uniqueGift) = starGift {
|
||||
giftTitle = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: dateTimeFormat))"
|
||||
for attribute in uniqueGift.attributes {
|
||||
if case let .model(_, fileValue, _) = attribute {
|
||||
file = fileValue
|
||||
@ -802,11 +805,21 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
if message.author?.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
attributedString = NSAttributedString(string: strings.Notification_ChannelChangedTheme(emoji).string, font: titleFont, textColor: primaryTextColor)
|
||||
} else if message.author?.id == accountPeerId {
|
||||
let resultTitleString = strings.Notification_YouChangedTheme(emoji)
|
||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: emojiAttributes])
|
||||
if let giftTitle {
|
||||
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 {
|
||||
let resultTitleString = strings.Notification_ChangedTheme(compactAuthorName, emoji)
|
||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: emojiAttributes])
|
||||
if let giftTitle {
|
||||
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):
|
||||
@ -817,8 +830,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
} else {
|
||||
let price: String
|
||||
if currency == "XTR" {
|
||||
//TODO:localize
|
||||
price = "\(amount) Stars"
|
||||
price = strings.Notification_PremiumGift_Stars(Int32(clamping: amount))
|
||||
} else {
|
||||
price = formatCurrencyAmount(amount, currency: currency)
|
||||
}
|
||||
@ -833,7 +845,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
case let .giftStars(currency, amount, count, _, _, _):
|
||||
let _ = count
|
||||
if !forAdditionalServiceMessage {
|
||||
attributedString = NSAttributedString(string: strings.Notification_Gift, font: titleFont, textColor: primaryTextColor)
|
||||
let starsPrice = strings.Notification_GiftStars_Stars(Int32(clamping: count))
|
||||
attributedString = NSAttributedString(string: strings.Notification_GiftStars(starsPrice).string, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
let price = formatCurrencyAmount(amount, currency: currency)
|
||||
if message.author?.id == accountPeerId {
|
||||
@ -1155,17 +1168,22 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
attributedString = mutableString
|
||||
case .prizeStars:
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsPrize, font: titleFont, textColor: primaryTextColor)
|
||||
case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, isPrepaidUpgrade, _, peerId, senderId, _, _, _, _):
|
||||
case let .starGift(gift, _, text, entities, _, _, _, _, _, upgradeStars, _, isPrepaidUpgrade, _, peerId, senderId, _, _, _, upgradeSeparate):
|
||||
if !forAdditionalServiceMessage {
|
||||
if let text {
|
||||
let mutableAttributedString = NSMutableAttributedString(attributedString: stringWithAppliedEntities(text, entities: entities ?? [], baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false, message: message._asMessage()))
|
||||
attributedString = mutableAttributedString
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Notification_Gift, font: titleFont, textColor: primaryTextColor)
|
||||
if isPrepaidUpgrade {
|
||||
let starsPrice = strings.Notification_PrepaidGiftUpgrade_Stars(Int32(clamping: upgradeStars ?? 0))
|
||||
attributedString = NSAttributedString(string: strings.Notification_PrepaidGiftUpgrade(starsPrice).string, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Notification_Gift, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
}
|
||||
} else if case let .generic(gift) = gift {
|
||||
var finalPrice = gift.price
|
||||
if let upgradeStars {
|
||||
if let upgradeStars, !upgradeSeparate {
|
||||
finalPrice += upgradeStars
|
||||
}
|
||||
let starsPrice = strings.Notification_StarsGift_Stars(Int32(clamping: finalPrice))
|
||||
|
||||
@ -485,6 +485,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Stars/BalanceNeededScreen",
|
||||
"//submodules/TelegramUI/Components/FaceScanScreen",
|
||||
"//submodules/TelegramUI/Components/MediaManager/PeerMessagesMediaPlaylist",
|
||||
"//submodules/TelegramUI/Components/ChatThemeScreen",
|
||||
"//submodules/ContactsHelper",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
|
||||
@ -241,6 +241,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
} else if case .joinedChannel = action.action {
|
||||
result.append((message, ChatMessageJoinedChannelBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
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 {
|
||||
if !canAddMessageReactions(message: message) {
|
||||
needReactions = false
|
||||
|
||||
@ -33,6 +33,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities",
|
||||
"//submodules/InvisibleInkDustNode",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -24,6 +24,7 @@ import ChatMessageItemCommon
|
||||
import TextNodeWithEntities
|
||||
import InvisibleInkDustNode
|
||||
import PeerInfoCoverComponent
|
||||
import GiftItemComponent
|
||||
|
||||
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)
|
||||
@ -45,6 +46,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
private var dustNode: InvisibleInkDustNode?
|
||||
private let placeholderNode: StickerShimmerEffectNode
|
||||
private let animationNode: AnimatedStickerNode
|
||||
private let giftIcon = ComponentView<Empty>()
|
||||
|
||||
private let modelTitleTextNode: TextNode
|
||||
private let modelValueTextNode: TextNode
|
||||
@ -416,6 +418,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var months: Int32 = 3
|
||||
var animationName: String = ""
|
||||
var animationFile: TelegramMediaFile?
|
||||
var uniqueGift: StarGift.UniqueGift?
|
||||
var title = item.presentationData.strings.Notification_PremiumGift_Title
|
||||
var text = ""
|
||||
var subtitleColor = primaryTextColor
|
||||
@ -708,6 +711,20 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
text = item.presentationData.strings.Notification_StarGift_Subtitle_Refunded
|
||||
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:
|
||||
break
|
||||
}
|
||||
@ -866,6 +883,10 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
giftSize.height += 12.0
|
||||
}
|
||||
|
||||
if let _ = uniqueGift {
|
||||
giftSize.height -= 31.0
|
||||
}
|
||||
|
||||
var labelRects = labelLayout.linesRects()
|
||||
if labelRects.count > 1 {
|
||||
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.creatorButtonTitleNode.isHidden = creatorButtonTitle.isEmpty
|
||||
|
||||
if strongSelf.item == nil && !isStoryEntity {
|
||||
if strongSelf.item == nil && !isStoryEntity && uniqueGift == nil {
|
||||
strongSelf.animationNode.started = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
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)
|
||||
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
|
||||
|
||||
@ -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)
|
||||
if let (offset, image) = backgroundMaskImage {
|
||||
if strongSelf.backgroundNode == nil {
|
||||
@ -1524,7 +1573,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
if isPlaying {
|
||||
var alreadySeen = true
|
||||
|
||||
if item.message.flags.contains(.Incoming) {
|
||||
if let action = item.message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case .setChatTheme = action.action {
|
||||
|
||||
} else if item.message.flags.contains(.Incoming) {
|
||||
if let unreadRange = item.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: item.message.id.peerId, namespace: item.message.id.namespace)] {
|
||||
if unreadRange.contains(item.message.id.id) {
|
||||
alreadySeen = false
|
||||
|
||||
@ -1656,7 +1656,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
return patternWallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, representations: representations, mode: .screen)
|
||||
|> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
|
||||
if let value = value {
|
||||
return .single(value)
|
||||
return .single(value.generator)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
@ -387,7 +387,7 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode
|
||||
updateImageSignal = patternWallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, representations: representations, mode: .thumbnail)
|
||||
|> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
|
||||
if let value {
|
||||
return .single(value)
|
||||
return .single(value.generator)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
44
submodules/TelegramUI/Components/ChatThemeScreen/BUILD
Normal file
44
submodules/TelegramUI/Components/ChatThemeScreen/BUILD
Normal file
@ -0,0 +1,44 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ChatThemeScreen",
|
||||
module_name = "ChatThemeScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/TelegramUIPreferences",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/AnimationUI",
|
||||
"//submodules/MergeLists",
|
||||
"//submodules/MediaResources",
|
||||
"//submodules/StickerResources",
|
||||
"//submodules/WallpaperResources",
|
||||
"//submodules/TooltipUI",
|
||||
"//submodules/SolidRoundedButtonNode",
|
||||
"//submodules/AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode",
|
||||
"//submodules/ShimmerEffect",
|
||||
"//submodules/AttachmentUI",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/ActivityIndicator",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -21,20 +21,22 @@ import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import ShimmerEffect
|
||||
import AttachmentUI
|
||||
import AvatarNode
|
||||
|
||||
private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let chatTheme: ChatTheme?
|
||||
let emojiFile: TelegramMediaFile?
|
||||
let themeReference: PresentationThemeReference?
|
||||
let peer: EnginePeer?
|
||||
let nightMode: Bool
|
||||
var selected: Bool
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let wallpaper: TelegramWallpaper?
|
||||
|
||||
var stableId: Int {
|
||||
return index
|
||||
var stableId: String {
|
||||
return self.chatTheme?.id ?? "\(self.index)"
|
||||
}
|
||||
|
||||
static func ==(lhs: ThemeSettingsThemeEntry, rhs: ThemeSettingsThemeEntry) -> Bool {
|
||||
@ -47,6 +49,9 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
|
||||
if lhs.themeReference?.index != rhs.themeReference?.index {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.nightMode != rhs.nightMode {
|
||||
return false
|
||||
}
|
||||
@ -70,16 +75,16 @@ private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
|
||||
}
|
||||
|
||||
func item(context: AccountContext, action: @escaping (ChatTheme?) -> Void) -> ListViewItem {
|
||||
return ThemeSettingsThemeIconItem(context: context, chatTheme: self.chatTheme, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
|
||||
return ThemeSettingsThemeIconItem(context: context, chatTheme: self.chatTheme, emojiFile: self.emojiFile, themeReference: self.themeReference, peer: self.peer, nightMode: self.nightMode, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ThemeSettingsThemeIconItem: ListViewItem {
|
||||
let context: AccountContext
|
||||
let chatTheme: ChatTheme?
|
||||
let emojiFile: TelegramMediaFile?
|
||||
let themeReference: PresentationThemeReference?
|
||||
let peer: EnginePeer?
|
||||
let nightMode: Bool
|
||||
let selected: Bool
|
||||
let theme: PresentationTheme
|
||||
@ -87,11 +92,24 @@ private class ThemeSettingsThemeIconItem: ListViewItem {
|
||||
let wallpaper: TelegramWallpaper?
|
||||
let action: (ChatTheme?) -> Void
|
||||
|
||||
public init(context: AccountContext, chatTheme: ChatTheme?, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, nightMode: Bool, selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (ChatTheme?) -> Void) {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
chatTheme: ChatTheme?,
|
||||
emojiFile: TelegramMediaFile?,
|
||||
themeReference: PresentationThemeReference?,
|
||||
peer: EnginePeer?,
|
||||
nightMode: Bool,
|
||||
selected: Bool,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
wallpaper: TelegramWallpaper?,
|
||||
action: @escaping (ChatTheme?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.chatTheme = chatTheme
|
||||
self.emojiFile = emojiFile
|
||||
self.themeReference = themeReference
|
||||
self.peer = peer
|
||||
self.nightMode = nightMode
|
||||
self.selected = selected
|
||||
self.theme = theme
|
||||
@ -240,6 +258,9 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
private let emojiImageNode: TransformImageNode
|
||||
private var animatedStickerNode: AnimatedStickerNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode
|
||||
private var bubbleNode: ASImageNode?
|
||||
private var avatarNode: AvatarNode?
|
||||
private var replaceNode: ASImageNode?
|
||||
var snapshotView: UIView?
|
||||
|
||||
var item: ThemeSettingsThemeIconItem?
|
||||
@ -489,6 +510,75 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
animatedStickerNode.frame = emojiFrame
|
||||
animatedStickerNode.updateLayout(size: emojiFrame.size)
|
||||
}
|
||||
|
||||
if let _ = item.peer {
|
||||
let bubbleNode: ASImageNode
|
||||
if let current = strongSelf.bubbleNode {
|
||||
bubbleNode = current
|
||||
} else {
|
||||
bubbleNode = ASImageNode()
|
||||
strongSelf.insertSubnode(bubbleNode, belowSubnode: strongSelf.emojiContainerNode)
|
||||
strongSelf.bubbleNode = bubbleNode
|
||||
|
||||
var bubbleColor: UIColor?
|
||||
if let theme = item.chatTheme, case let .gift(_, themeSettings) = theme {
|
||||
if item.nightMode {
|
||||
if let theme = themeSettings.first(where: { $0.baseTheme == .night || $0.baseTheme == .tinted }) {
|
||||
let color = theme.wallpaper?.settings?.colors.first ?? theme.accentColor
|
||||
bubbleColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
}
|
||||
} else {
|
||||
if let theme = themeSettings.first(where: { $0.baseTheme == .classic || $0.baseTheme == .day }) {
|
||||
let color = theme.wallpaper?.settings?.colors.first ?? theme.accentColor
|
||||
bubbleColor = UIColor(rgb: UInt32(bitPattern: color))
|
||||
}
|
||||
}
|
||||
}
|
||||
if let bubbleColor {
|
||||
bubbleNode.image = generateFilledRoundedRectImage(size: CGSize(width: 24.0, height: 48.0), cornerRadius: 12.0, color: bubbleColor)
|
||||
}
|
||||
}
|
||||
bubbleNode.frame = CGRect(origin: CGPoint(x: 50.0, y: 12.0), size: CGSize(width: 24.0, height: 48.0))
|
||||
} else if let bubbleNode = strongSelf.bubbleNode {
|
||||
strongSelf.bubbleNode = nil
|
||||
bubbleNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let peer = item.peer {
|
||||
let avatarNode: AvatarNode
|
||||
if let current = strongSelf.avatarNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0))
|
||||
strongSelf.insertSubnode(avatarNode, belowSubnode: strongSelf.emojiContainerNode)
|
||||
strongSelf.avatarNode = avatarNode
|
||||
avatarNode.setPeer(context: item.context, theme: item.theme, peer: peer, displayDimensions: CGSize(width: 20.0, height: 20.0))
|
||||
}
|
||||
avatarNode.transform = CATransform3DMakeRotation(.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
avatarNode.frame = CGRect(origin: CGPoint(x: 52.0, y: 14.0), size: CGSize(width: 20.0, height: 20.0))
|
||||
} else if let avatarNode = strongSelf.avatarNode {
|
||||
strongSelf.avatarNode = nil
|
||||
avatarNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let _ = item.peer {
|
||||
let replaceNode: ASImageNode
|
||||
if let current = strongSelf.replaceNode {
|
||||
replaceNode = current
|
||||
} else {
|
||||
replaceNode = ASImageNode()
|
||||
strongSelf.insertSubnode(replaceNode, belowSubnode: strongSelf.emojiContainerNode)
|
||||
strongSelf.replaceNode = replaceNode
|
||||
replaceNode.image = generateTintedImage(image: UIImage(bundleImageName: "Settings/Refresh"), color: .white)
|
||||
}
|
||||
replaceNode.transform = CATransform3DMakeRotation(.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
if let image = replaceNode.image {
|
||||
replaceNode.frame = CGRect(origin: CGPoint(x: 53.0, y: 37.0), size: image.size)
|
||||
}
|
||||
} else if let replaceNode = strongSelf.replaceNode {
|
||||
strongSelf.replaceNode = nil
|
||||
replaceNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -525,9 +615,9 @@ private final class ThemeSettingsThemeItemIconNode : ListViewItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatThemeScreen: ViewController {
|
||||
static let themeCrossfadeDuration: Double = 0.3
|
||||
static let themeCrossfadeDelay: Double = 0.25
|
||||
public final class ChatThemeScreen: ViewController {
|
||||
public static let themeCrossfadeDuration: Double = 0.3
|
||||
public static let themeCrossfadeDelay: Double = 0.25
|
||||
|
||||
private var controllerNode: ChatThemeScreenNode {
|
||||
return self.displayNode as! ChatThemeScreenNode
|
||||
@ -539,7 +629,7 @@ final class ChatThemeScreen: ViewController {
|
||||
private let animatedEmojiStickers: [String: [StickerPackItem]]
|
||||
private let initiallySelectedTheme: ChatTheme?
|
||||
private let peerName: String
|
||||
let canResetWallpaper: Bool
|
||||
fileprivate let canResetWallpaper: Bool
|
||||
private let previewTheme: (ChatTheme?, Bool?) -> Void
|
||||
fileprivate let changeWallpaper: () -> Void
|
||||
fileprivate let resetWallpaper: () -> Void
|
||||
@ -548,9 +638,9 @@ final class ChatThemeScreen: ViewController {
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
var dismissed: (() -> Void)?
|
||||
public var dismissed: (() -> Void)?
|
||||
|
||||
var passthroughHitTestImpl: ((CGPoint) -> UIView?)? {
|
||||
public var passthroughHitTestImpl: ((CGPoint) -> UIView?)? {
|
||||
didSet {
|
||||
if self.isNodeLoaded {
|
||||
self.controllerNode.passthroughHitTestImpl = self.passthroughHitTestImpl
|
||||
@ -558,7 +648,7 @@ final class ChatThemeScreen: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
public init(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>),
|
||||
animatedEmojiStickers: [String: [StickerPackItem]],
|
||||
@ -657,7 +747,7 @@ final class ChatThemeScreen: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
self.forEachController({ controller in
|
||||
if let controller = controller as? TooltipScreen {
|
||||
controller.dismiss()
|
||||
@ -683,7 +773,7 @@ final class ChatThemeScreen: ViewController {
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
func dimTapped() {
|
||||
public func dimTapped() {
|
||||
self.controllerNode.dimTapped()
|
||||
}
|
||||
}
|
||||
@ -743,6 +833,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
|
||||
private var initialized = false
|
||||
|
||||
private let uniqueGiftChatThemesContext: UniqueGiftChatThemesContext
|
||||
private var currentUniqueGiftChatThemesState: UniqueGiftChatThemesContext.State?
|
||||
|
||||
private let peerName: String
|
||||
|
||||
@ -875,9 +966,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
|
||||
self.cancelButtonNode.buttonNode.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.doneButton.pressed = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.doneButton.isUserInteractionEnabled = false
|
||||
if strongSelf.doneButton.font == .bold {
|
||||
strongSelf.completion?(strongSelf.selectedTheme)
|
||||
strongSelf.complete()
|
||||
} else {
|
||||
strongSelf.controller?.changeWallpaper()
|
||||
}
|
||||
@ -888,13 +978,37 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
|
||||
self.disposable.set(combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager),
|
||||
self.uniqueGiftChatThemesContext.state,
|
||||
self.uniqueGiftChatThemesContext.state
|
||||
|> mapToSignal { state -> Signal<(UniqueGiftChatThemesContext.State, [EnginePeer.Id: EnginePeer]), NoError> in
|
||||
var peerIds: [EnginePeer.Id] = []
|
||||
for theme in state.themes {
|
||||
if case let .gift(gift, _) = theme, case let .unique(uniqueGift) = gift, let themePeerId = uniqueGift.themePeerId {
|
||||
peerIds.append(themePeerId)
|
||||
}
|
||||
}
|
||||
return combineLatest(
|
||||
.single(state),
|
||||
context.engine.data.get(
|
||||
EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init))
|
||||
) |> map { peers in
|
||||
var result: [EnginePeer.Id: EnginePeer] = [:]
|
||||
for peerId in peerIds {
|
||||
if let maybePeer = peers[peerId], let peer = maybePeer {
|
||||
result[peerId] = peer
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
)
|
||||
},
|
||||
self.selectedThemePromise.get(),
|
||||
self.isDarkAppearancePromise.get()
|
||||
).startStrict(next: { [weak self] themes, uniqueGiftChatThemes, selectedTheme, isDarkAppearance in
|
||||
).startStrict(next: { [weak self] themes, uniqueGiftChatThemesStateAndPeers, selectedTheme, isDarkAppearance in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let (uniqueGiftChatThemesState, peers) = uniqueGiftChatThemesStateAndPeers
|
||||
strongSelf.currentUniqueGiftChatThemesState = uniqueGiftChatThemesState
|
||||
|
||||
let isFirstTime = strongSelf.entries == nil
|
||||
let presentationData = strongSelf.presentationData
|
||||
@ -905,61 +1019,91 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
|
||||
chatTheme: nil,
|
||||
emojiFile: nil,
|
||||
themeReference: nil,
|
||||
peer: nil,
|
||||
nightMode: false,
|
||||
selected: selectedTheme == nil,
|
||||
theme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
wallpaper: nil
|
||||
))
|
||||
for theme in themes {
|
||||
guard let emoticon = theme.emoticon else {
|
||||
continue
|
||||
|
||||
var giftThemes = uniqueGiftChatThemesState.themes
|
||||
var existingIds = Set<String>()
|
||||
if let initiallySelectedTheme, case .gift = initiallySelectedTheme {
|
||||
let initialThemeIndex = giftThemes.firstIndex(where: { $0.id == initiallySelectedTheme.id })
|
||||
if initialThemeIndex == nil || initialThemeIndex! > 50 {
|
||||
giftThemes.insert(initiallySelectedTheme, at: 0)
|
||||
}
|
||||
entries.append(ThemeSettingsThemeEntry(
|
||||
index: entries.count,
|
||||
chatTheme: .emoticon(emoticon),
|
||||
emojiFile: animatedEmojiStickers[emoticon]?.first?.file._parse(),
|
||||
themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)),
|
||||
nightMode: isDarkAppearance,
|
||||
selected: selectedTheme?.id == ChatTheme.emoticon(emoticon).id,
|
||||
theme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
wallpaper: nil
|
||||
))
|
||||
}
|
||||
for theme in uniqueGiftChatThemes.themes {
|
||||
guard case let .gift(gift, wallpaperFile) = theme else {
|
||||
|
||||
for theme in giftThemes {
|
||||
guard case let .gift(gift, themeSettings) = theme, !existingIds.contains(theme.id) else {
|
||||
continue
|
||||
}
|
||||
var emojiFile: TelegramMediaFile?
|
||||
var peer: EnginePeer?
|
||||
if case let .unique(uniqueGift) = gift {
|
||||
for attribute in uniqueGift.attributes {
|
||||
if case let .model(_, file, _) = attribute {
|
||||
emojiFile = file
|
||||
}
|
||||
}
|
||||
if let themePeerId = uniqueGift.themePeerId, theme.id != initiallySelectedTheme?.id {
|
||||
peer = peers[themePeerId]
|
||||
}
|
||||
}
|
||||
let themeReference: PresentationThemeReference
|
||||
let wallpaper: TelegramWallpaper?
|
||||
if isDarkAppearance {
|
||||
wallpaper = themeSettings.first(where: { $0.baseTheme == .night || $0.baseTheme == .tinted })?.wallpaper
|
||||
themeReference = .builtin(.night)
|
||||
} else {
|
||||
wallpaper = themeSettings.first(where: { $0.baseTheme == .classic || $0.baseTheme == .day })?.wallpaper
|
||||
themeReference = .builtin(.dayClassic)
|
||||
}
|
||||
entries.append(ThemeSettingsThemeEntry(
|
||||
index: entries.count,
|
||||
chatTheme: theme,
|
||||
emojiFile: emojiFile,
|
||||
themeReference: nil,
|
||||
themeReference: themeReference,
|
||||
peer: peer,
|
||||
nightMode: isDarkAppearance,
|
||||
selected: selectedTheme?.id == theme.id,
|
||||
theme: presentationData.theme,
|
||||
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
|
||||
))
|
||||
existingIds.insert(theme.id)
|
||||
}
|
||||
|
||||
if uniqueGiftChatThemesState.themes.count == 0 || uniqueGiftChatThemesState.dataState == .ready(canLoadMore: false) {
|
||||
for theme in themes {
|
||||
guard let emoticon = theme.emoticon else {
|
||||
continue
|
||||
}
|
||||
entries.append(ThemeSettingsThemeEntry(
|
||||
index: entries.count,
|
||||
chatTheme: .emoticon(emoticon),
|
||||
emojiFile: animatedEmojiStickers[emoticon]?.first?.file._parse(),
|
||||
themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)),
|
||||
peer: nil,
|
||||
nightMode: isDarkAppearance,
|
||||
selected: selectedTheme?.id == ChatTheme.emoticon(emoticon).id,
|
||||
theme: presentationData.theme,
|
||||
strings: presentationData.strings,
|
||||
wallpaper: nil
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let action: (ChatTheme?) -> Void = { [weak self] chatTheme in
|
||||
if let self, self.selectedTheme != chatTheme {
|
||||
self.setChatTheme(chatTheme)
|
||||
}
|
||||
}
|
||||
let previousEntries = strongSelf.entries ?? []
|
||||
let crossfade = previousEntries.count != entries.count
|
||||
let transition = preparedTransition(context: strongSelf.context, action: action, from: previousEntries, to: entries, crossfade: crossfade)
|
||||
//let crossfade = previousEntries.count != entries.count
|
||||
let transition = preparedTransition(context: strongSelf.context, action: action, from: previousEntries, to: entries, crossfade: false)
|
||||
strongSelf.enqueueTransition(transition)
|
||||
|
||||
strongSelf.entries = entries
|
||||
@ -1008,6 +1152,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()
|
||||
}
|
||||
|
||||
@ -1195,13 +1348,39 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, ASScrollViewDelega
|
||||
}
|
||||
}
|
||||
|
||||
func complete() {
|
||||
let proceed = {
|
||||
self.doneButton.isUserInteractionEnabled = false
|
||||
self.completion?(self.selectedTheme)
|
||||
}
|
||||
if case let .gift(gift, _) = self.selectedTheme, case let .unique(uniqueGift) = gift, let themePeerId = uniqueGift.themePeerId {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: themePeerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
let controller = giftThemeTransferAlertController(
|
||||
context: self.context,
|
||||
gift: uniqueGift,
|
||||
previousPeer: peer,
|
||||
commit: {
|
||||
proceed()
|
||||
}
|
||||
)
|
||||
self.controller?.present(controller, in: .window(.root))
|
||||
})
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
}
|
||||
|
||||
func dimTapped() {
|
||||
if self.selectedTheme?.id == self.initiallySelectedTheme?.id {
|
||||
self.cancelButtonPressed()
|
||||
} else {
|
||||
let alertController = textAlertController(context: self.context, updatedPresentationData: (self.presentationData, .single(self.presentationData)), title: nil, text: self.presentationData.strings.Conversation_Theme_DismissAlert, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_Theme_DismissAlertApply, action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.completion?(strongSelf.selectedTheme)
|
||||
if let self {
|
||||
self.complete()
|
||||
}
|
||||
})], actionLayout: .horizontal, dismissOnOutsideTap: true)
|
||||
self.present?(alertController)
|
||||
@ -0,0 +1,295 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
import AvatarNode
|
||||
import Markdown
|
||||
import GiftItemComponent
|
||||
import ActivityIndicator
|
||||
|
||||
private final class GiftThemeTransferAlertContentNode: AlertContentNode {
|
||||
private let context: AccountContext
|
||||
private let strings: PresentationStrings
|
||||
private var presentationTheme: PresentationTheme
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let gift: StarGift.UniqueGift
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let giftView = ComponentView<Empty>()
|
||||
private let textNode: ASTextNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let avatarNode: AvatarNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var activityIndicator: ActivityIndicator?
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
var inProgress = false {
|
||||
didSet {
|
||||
if let size = self.validLayout {
|
||||
let _ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: AlertControllerTheme,
|
||||
ptheme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
gift: StarGift.UniqueGift,
|
||||
peer: EnginePeer,
|
||||
title: String,
|
||||
text: String,
|
||||
actions: [TextAlertAction]
|
||||
) {
|
||||
self.context = context
|
||||
self.strings = strings
|
||||
self.presentationTheme = ptheme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.gift = gift
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
|
||||
self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer)
|
||||
}
|
||||
|
||||
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor)
|
||||
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
), textAlignment: .center)
|
||||
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/CutoutUndo"), color: theme.secondaryColor.withAlphaComponent(0.9))
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let avatarSize = CGSize(width: 60.0, height: 60.0)
|
||||
self.avatarNode.updateSize(size: avatarSize)
|
||||
|
||||
let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 52.0, y: origin.y), size: avatarSize)
|
||||
|
||||
let _ = self.giftView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
context: self.context,
|
||||
theme: self.presentationTheme,
|
||||
strings: self.strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: self.gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: avatarSize
|
||||
)
|
||||
if let view = self.giftView.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
view.frame = giftFrame
|
||||
}
|
||||
|
||||
if let arrowImage = self.arrowNode.image {
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||
}
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 52.0, y: origin.y), size: avatarSize)
|
||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
|
||||
origin.y += avatarSize.height + 17.0
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 5.0
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 10.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
}
|
||||
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
let contentWidth = max(size.width, minActionsWidth)
|
||||
|
||||
let actionsHeight = actionButtonHeight
|
||||
|
||||
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 24.0 + insets.top + insets.bottom)
|
||||
self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
if self.inProgress {
|
||||
let activityIndicator: ActivityIndicator
|
||||
if let current = self.activityIndicator {
|
||||
activityIndicator = current
|
||||
} else {
|
||||
activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false))
|
||||
self.addSubnode(activityIndicator)
|
||||
}
|
||||
|
||||
if let actionNode = self.actionNodes.first {
|
||||
actionNode.isHidden = true
|
||||
|
||||
let indicatorSize = CGSize(width: 22.0, height: 22.0)
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
}
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
|
||||
public func giftThemeTransferAlertController(
|
||||
context: AccountContext,
|
||||
gift: StarGift.UniqueGift,
|
||||
previousPeer: EnginePeer,
|
||||
commit: @escaping () -> Void
|
||||
) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
var contentNode: GiftThemeTransferAlertContentNode?
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Conversation_Theme_GiftTransfer_Proceed, action: {
|
||||
dismissImpl?(true)
|
||||
commit()
|
||||
})]
|
||||
|
||||
let text = strings.Conversation_Theme_GiftTransfer_Text(previousPeer.compactDisplayTitle).string
|
||||
contentNode = GiftThemeTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: previousPeer, title: "", text: text, actions: actions)
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
return controller
|
||||
}
|
||||
@ -477,7 +477,12 @@ final class GiftSetupScreenComponent: Component {
|
||||
case .starGiftUserLimit:
|
||||
if let perUserLimit, let giftFile {
|
||||
let text = presentationData.strings.Gift_Options_Gift_BuyLimitReached(perUserLimit)
|
||||
let undoController = UndoOverlayController(presentationData: presentationData, content: .sticker(context: component.context, file: giftFile, loop: true, title: nil, text: text, undoText: nil, customAction: nil), action: { _ in return false })
|
||||
let undoController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .sticker(context: component.context, file: giftFile, loop: true, title: nil, text: text, undoText: nil, customAction: nil),
|
||||
elevatedLayout: true,
|
||||
action: { _ in return false }
|
||||
)
|
||||
controller.present(undoController, in: .current)
|
||||
return
|
||||
}
|
||||
|
||||
@ -277,7 +277,7 @@ final class GiftStoreScreenComponent: Component {
|
||||
buyGift: { slug, peerId, price in
|
||||
return self.state?.starGiftsContext.buyStarGift(slug: slug, peerId: peerId, price: price) ?? .complete()
|
||||
},
|
||||
updateResellStars: { price in
|
||||
updateResellStars: { _, price in
|
||||
return self.state?.starGiftsContext.updateStarGiftResellPrice(slug: uniqueGift.slug, price: price) ?? .complete()
|
||||
}
|
||||
)
|
||||
|
||||
@ -53,6 +53,7 @@ swift_library(
|
||||
"//submodules/ActivityIndicator",
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/Stars/BalanceNeededScreen",
|
||||
"//submodules/TelegramUI/Components/ChatThemeScreen",
|
||||
"//submodules/ImageBlur",
|
||||
],
|
||||
visibility = [
|
||||
|
||||
@ -149,10 +149,6 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
gift: gift
|
||||
)
|
||||
controller.push(storeController)
|
||||
|
||||
Queue.mainQueue().after(2.0, {
|
||||
controller.dismiss(animated: false)
|
||||
})
|
||||
}
|
||||
|
||||
func openGiftFragmentResale(url: String) {
|
||||
@ -205,8 +201,6 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
let dateTimeFormat = environment.dateTimeFormat
|
||||
//let nameDisplayOrder = component.context.sharedContext.currentPresentationData.with { $0 }.nameDisplayOrder
|
||||
//let controller = environment.controller
|
||||
|
||||
let state = context.state
|
||||
|
||||
@ -319,12 +313,12 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
|
||||
var descriptionText: String
|
||||
if component.valueInfo.valueIsAverage {
|
||||
descriptionText = "This is the average sale price of **\(giftCollectionTitle)** on Telegram and Fragment over the past month."
|
||||
descriptionText = strings.Gift_Value_DescriptionAveragePrice(giftCollectionTitle).string
|
||||
} else {
|
||||
if component.valueInfo.isLastSaleOnFragment {
|
||||
descriptionText = "This is the last price at which **\(giftTitle)** was last sold on Fragment."
|
||||
descriptionText = strings.Gift_Value_DescriptionLastPriceFragment(giftTitle).string
|
||||
} else {
|
||||
descriptionText = "This is the last price at which **\(giftTitle)** was last sold on Telegram."
|
||||
descriptionText = strings.Gift_Value_DescriptionLastPriceTelegram(giftTitle).string
|
||||
}
|
||||
}
|
||||
if !descriptionText.isEmpty {
|
||||
@ -394,7 +388,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "initialDate",
|
||||
title: "Initial Sale",
|
||||
title: strings.Gift_Value_InitialSale,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: component.valueInfo.initialSaleDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
@ -410,7 +404,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
|
||||
tableItems.append(.init(
|
||||
id: "initialPrice",
|
||||
title: "Initial Price",
|
||||
title: strings.Gift_Value_InitialPrice,
|
||||
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
@ -425,7 +419,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
if let lastSaleDate = component.valueInfo.lastSaleDate {
|
||||
tableItems.append(.init(
|
||||
id: "lastDate",
|
||||
title: "Last Sale",
|
||||
title: strings.Gift_Value_LastSale,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: stringForMediumDate(timestamp: lastSaleDate, strings: strings, dateTimeFormat: dateTimeFormat), font: tableFont, textColor: tableTextColor)))
|
||||
)
|
||||
@ -457,7 +451,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
state?.showAttributeInfo(tag: tag, text: "**\(lastSalePriceString)** is the last price for \(giftCollectionTitle) gifts listed on Telegram and Fragment.")
|
||||
state?.showAttributeInfo(tag: tag, text: strings.Gift_Value_LastPriceInfo(lastSalePriceString, giftCollectionTitle).string)
|
||||
|
||||
}
|
||||
).tagged(tag))
|
||||
@ -467,7 +461,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
)
|
||||
tableItems.append(.init(
|
||||
id: "lastPrice",
|
||||
title: "Last Price",
|
||||
title: strings.Gift_Value_LastPrice,
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
@ -494,8 +488,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
state?.showAttributeInfo(tag: tag, text: "**\(floorPriceString)** is the floor price for \(giftCollectionTitle) gifts listed on Telegram and Fragment.")
|
||||
|
||||
state?.showAttributeInfo(tag: tag, text: strings.Gift_Value_MinimumPriceInfo(floorPriceString, giftCollectionTitle).string)
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
@ -504,7 +497,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
)
|
||||
tableItems.append(.init(
|
||||
id: "floorPrice",
|
||||
title: "Minumum Price",
|
||||
title: strings.Gift_Value_MinimumPrice,
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
@ -531,7 +524,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
color: theme.list.itemAccentColor
|
||||
)),
|
||||
action: { [weak state] in
|
||||
state?.showAttributeInfo(tag: tag, text: "**\(averagePriceString)** is the average sale price of \(giftCollectionTitle) on Telegram and Fragment over the past month.")
|
||||
state?.showAttributeInfo(tag: tag, text: strings.Gift_Value_AveragePriceInfo(averagePriceString, giftCollectionTitle).string)
|
||||
}
|
||||
).tagged(tag))
|
||||
))
|
||||
@ -540,7 +533,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
)
|
||||
tableItems.append(.init(
|
||||
id: "averagePrice",
|
||||
title: "Average Price",
|
||||
title: strings.Gift_Value_AveragePrice,
|
||||
hasBackground: false,
|
||||
component: itemComponent
|
||||
))
|
||||
@ -587,7 +580,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: " for sale on Telegram", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: " \(strings.Gift_Value_ForSaleOnTelegram)", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
||||
BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor)
|
||||
@ -639,7 +632,7 @@ private final class GiftValueSheetContent: CombinedComponent {
|
||||
)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: " for sale on Fragment", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: " \(strings.Gift_Value_ForSaleOnFragment)", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
|
||||
BundleIconComponent(name: "Chat/Context Menu/Arrow", tintColor: theme.actionSheet.controlAccentColor)
|
||||
@ -724,6 +717,7 @@ final class GiftValueSheetComponent: CombinedComponent {
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
followContentSizeChanges: true,
|
||||
clipsContent: true,
|
||||
autoAnimateOut: false,
|
||||
externalState: sheetExternalState,
|
||||
animateOut: animateOut,
|
||||
onPan: {
|
||||
|
||||
@ -36,6 +36,7 @@ import StarsBalanceOverlayComponent
|
||||
import BalanceNeededScreen
|
||||
import GiftItemComponent
|
||||
import GiftAnimationComponent
|
||||
import ChatThemeScreen
|
||||
|
||||
private final class GiftViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
@ -104,6 +105,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
var cachedHiddenImage: (UIImage, PresentationTheme)?
|
||||
|
||||
var inProgress = false
|
||||
var canSkip = false
|
||||
|
||||
var testUpgradeAnimation = !"".isEmpty
|
||||
|
||||
@ -152,7 +154,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
super.init()
|
||||
|
||||
if let arguments = subject.arguments {
|
||||
if let upgradeStars = arguments.upgradeStars, upgradeStars > 0, !arguments.nameHidden {
|
||||
if let upgradeStars = arguments.upgradeStars, upgradeStars > 0, !arguments.nameHidden && !arguments.upgradeSeparate {
|
||||
self.keepOriginalInfo = true
|
||||
}
|
||||
|
||||
@ -595,7 +597,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
if let convertToStars = controller?.convertToStars {
|
||||
convertToStars()
|
||||
convertToStars(reference)
|
||||
} else {
|
||||
let _ = (self.context.engine.payments.convertStarGift(reference: reference)
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
@ -668,14 +670,34 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
return
|
||||
}
|
||||
self.isOpeningValue = true
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let _ = (self.context.engine.payments.getUniqueStarGiftValueInfo(slug: uniqueGift.slug)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] valueInfo in
|
||||
guard let self, let valueInfo else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isOpeningValue = false
|
||||
let valueController = GiftValueScreen(context: self.context, gift: gift, valueInfo: valueInfo)
|
||||
controller.push(valueController)
|
||||
Queue.mainQueue().after(0.2) {
|
||||
self.isOpeningValue = false
|
||||
}
|
||||
if let valueInfo {
|
||||
let valueController = GiftValueScreen(context: self.context, gift: gift, valueInfo: valueInfo)
|
||||
controller.push(valueController)
|
||||
} else {
|
||||
guard let controller = self.getController() as? GiftViewScreen else {
|
||||
return
|
||||
}
|
||||
let alertController = textAlertController(
|
||||
context: self.context,
|
||||
title: nil,
|
||||
text: presentationData.strings.Login_UnknownError,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
|
||||
],
|
||||
parseMarkdown: true
|
||||
)
|
||||
controller.present(alertController, in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -782,6 +804,83 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
controller.present(shareController, in: .window(.root))
|
||||
}
|
||||
|
||||
func setAsGiftTheme() {
|
||||
guard let arguments = self.subject.arguments, let controller = self.getController() as? GiftViewScreen, let navigationController = controller.navigationController as? NavigationController, case let .unique(gift) = arguments.gift else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
|
||||
let themePeerId = Promise<EnginePeer.Id?>()
|
||||
themePeerId.set(
|
||||
.single(gift.themePeerId)
|
||||
|> then(
|
||||
context.engine.payments.getUniqueStarGift(slug: gift.slug)
|
||||
|> map { gift in
|
||||
return gift?.themePeerId
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
let peerController = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: [.user(.init(isBot: false, isPremium: nil))], hasContactSelector: false, hasCreation: false))
|
||||
peerController.peerSelected = { [weak peerController, weak navigationController] peer, _ in
|
||||
if let navigationController {
|
||||
let proceed = {
|
||||
let _ = context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: nil, forBoth: true).startStandalone()
|
||||
let _ = context.engine.themes.setChatTheme(peerId: peer.id, chatTheme: .gift(.unique(gift), [])).startStandalone()
|
||||
|
||||
peerController?.dismiss()
|
||||
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||
navigationController: navigationController,
|
||||
chatController: nil,
|
||||
context: context,
|
||||
chatLocation: .peer(peer),
|
||||
subject: nil,
|
||||
botStart: nil,
|
||||
updateTextInputState: nil,
|
||||
keepStack: .always,
|
||||
useExisting: true,
|
||||
purposefulAction: nil,
|
||||
scrollToEndIfExists: false,
|
||||
activateMessageSearch: nil,
|
||||
animated: true
|
||||
))
|
||||
}
|
||||
|
||||
let _ = (themePeerId.get()
|
||||
|> deliverOnMainQueue
|
||||
|> take(1)).start(next: { [weak navigationController] themePeerId in
|
||||
if let themePeerId, themePeerId != peer.id {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: themePeerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak navigationController] peer in
|
||||
guard let peer else {
|
||||
proceed()
|
||||
return
|
||||
}
|
||||
let controller = giftThemeTransferAlertController(
|
||||
context: context,
|
||||
gift: gift,
|
||||
previousPeer: peer,
|
||||
commit: {
|
||||
proceed()
|
||||
}
|
||||
)
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
|
||||
})
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
self.dismiss(animated: true)
|
||||
|
||||
Queue.mainQueue().after(0.4) {
|
||||
navigationController.pushViewController(peerController)
|
||||
}
|
||||
}
|
||||
|
||||
func transferGift() {
|
||||
guard let arguments = self.subject.arguments, let controller = self.getController() as? GiftViewScreen, case let .unique(gift) = arguments.gift, let reference = arguments.reference, let transferStars = arguments.transferStars else {
|
||||
return
|
||||
@ -882,7 +981,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let self, let controller else {
|
||||
return
|
||||
}
|
||||
let _ = ((controller.updateResellStars?(nil) ?? context.engine.payments.updateStarGiftResalePrice(reference: reference, price: nil))
|
||||
let _ = ((controller.updateResellStars?(reference, nil) ?? context.engine.payments.updateStarGiftResalePrice(reference: reference, price: nil))
|
||||
|> deliverOnMainQueue).startStandalone(error: { error in
|
||||
|
||||
}, completed: { [weak self, weak controller] in
|
||||
@ -932,7 +1031,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = ((controller.updateResellStars?(price) ?? context.engine.payments.updateStarGiftResalePrice(reference: reference, price: price))
|
||||
let _ = ((controller.updateResellStars?(reference, price) ?? context.engine.payments.updateStarGiftResalePrice(reference: reference, price: price))
|
||||
|> deliverOnMainQueue).startStandalone(error: { [weak self, weak controller] error in
|
||||
guard let self else {
|
||||
return
|
||||
@ -1061,7 +1160,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
var items: [ContextMenuItem] = []
|
||||
let strings = presentationData.strings
|
||||
|
||||
if let _ = arguments.reference, case .unique = arguments.gift, let togglePinnedToTop = controller.togglePinnedToTop, let pinnedToTop = arguments.pinnedToTop {
|
||||
if let reference = arguments.reference, case .unique = arguments.gift, let togglePinnedToTop = controller.togglePinnedToTop, let pinnedToTop = arguments.pinnedToTop {
|
||||
items.append(.action(ContextMenuActionItem(text: pinnedToTop ? strings.PeerInfo_Gifts_Context_Unpin : strings.PeerInfo_Gifts_Context_Pin , icon: { theme in generateTintedImage(image: UIImage(bundleImageName: pinnedToTop ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in
|
||||
c?.dismiss(completion: { [weak self, weak controller] in
|
||||
guard let self, let controller else {
|
||||
@ -1069,7 +1168,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
let pinnedToTop = !pinnedToTop
|
||||
if togglePinnedToTop(pinnedToTop) {
|
||||
if togglePinnedToTop(reference, pinnedToTop) {
|
||||
if pinnedToTop {
|
||||
controller.dismissAnimated()
|
||||
} else {
|
||||
@ -1113,6 +1212,16 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
|
||||
self?.shareGift()
|
||||
})))
|
||||
|
||||
if gift.flags.contains(.isThemeAvailable) {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Gift_View_Context_SetAsTheme, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
c?.dismiss(completion: nil)
|
||||
|
||||
self?.setAsGiftTheme()
|
||||
})))
|
||||
}
|
||||
|
||||
if let _ = arguments.transferStars {
|
||||
if case let .channel(channel) = peer, !channel.flags.contains(.isCreator) {
|
||||
@ -1528,7 +1637,28 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
func skipAnimation() {
|
||||
guard let arguments = self.subject.arguments, case let .unique(uniqueGift) = arguments.gift else {
|
||||
return
|
||||
}
|
||||
self.canSkip = false
|
||||
self.revealedNumberDigits = "\(uniqueGift.number)".count
|
||||
self.revealedAttributes.insert(.backdrop)
|
||||
self.revealedAttributes.insert(.pattern)
|
||||
self.revealedAttributes.insert(.model)
|
||||
|
||||
self.updated(transition: .easeInOut(duration: 0.2))
|
||||
}
|
||||
|
||||
func commitUpgrade() {
|
||||
let duration = Double.random(in: 0.85 ..< 2.25)
|
||||
let firstFraction = Double.random(in: 0.2 ..< 0.4)
|
||||
let secondFraction = Double.random(in: 0.2 ..< 0.4)
|
||||
let thirdFraction = 1.0 - firstFraction - secondFraction
|
||||
let firstDuration = duration * firstFraction
|
||||
let secondDuration = duration * secondFraction
|
||||
let thirdDuration = duration * thirdFraction
|
||||
|
||||
if self.testUpgradeAnimation, let arguments = self.subject.arguments, case let .unique(uniqueGift) = arguments.gift {
|
||||
self.inProgress = true
|
||||
self.updated()
|
||||
@ -1538,8 +1668,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.5, {
|
||||
self.inUpgradePreview = false
|
||||
self.canSkip = true
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
self.inProgress = false
|
||||
self.inUpgradePreview = false
|
||||
|
||||
self.justUpgraded = true
|
||||
self.revealedNumberDigits = -1
|
||||
@ -1550,19 +1683,23 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
self.updated(transition: .immediate)
|
||||
}
|
||||
}
|
||||
self.updated(transition: .spring(duration: 0.4))
|
||||
|
||||
Queue.mainQueue().after(1.2) {
|
||||
Queue.mainQueue().after(firstDuration) {
|
||||
self.revealedAttributes.insert(.backdrop)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
Queue.mainQueue().after(0.7) {
|
||||
Queue.mainQueue().after(secondDuration) {
|
||||
self.revealedAttributes.insert(.pattern)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
Queue.mainQueue().after(0.7) {
|
||||
Queue.mainQueue().after(thirdDuration) {
|
||||
self.revealedAttributes.insert(.model)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
Queue.mainQueue().after(0.55) {
|
||||
self.canSkip = false
|
||||
self.updated(transition: .easeInOut(duration: 0.2))
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.6) {
|
||||
if let controller = self.getController() as? GiftViewScreen {
|
||||
@ -1572,6 +1709,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.updated(transition: .spring(duration: 0.4))
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -1592,8 +1731,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
let context = self.context
|
||||
let upgradeGiftImpl: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)
|
||||
if let upgradeGift = controller.upgradeGift {
|
||||
guard let reference = arguments.reference else {
|
||||
return
|
||||
}
|
||||
upgradeGiftImpl = { formId, keepOriginalInfo in
|
||||
return upgradeGift(formId, keepOriginalInfo)
|
||||
return upgradeGift(formId, reference, keepOriginalInfo)
|
||||
|> afterCompleted {
|
||||
if formId != nil {
|
||||
context.starsContext?.load(force: true)
|
||||
@ -1619,6 +1761,9 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
||||
return
|
||||
}
|
||||
self.canSkip = true
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
self.inProgress = false
|
||||
self.inUpgradePreview = false
|
||||
|
||||
@ -1639,17 +1784,22 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2) {
|
||||
Queue.mainQueue().after(firstDuration) {
|
||||
self.revealedAttributes.insert(.backdrop)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
Queue.mainQueue().after(0.7) {
|
||||
Queue.mainQueue().after(secondDuration) {
|
||||
self.revealedAttributes.insert(.pattern)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
Queue.mainQueue().after(0.7) {
|
||||
Queue.mainQueue().after(thirdDuration) {
|
||||
self.revealedAttributes.insert(.model)
|
||||
self.updated(transition: .immediate)
|
||||
|
||||
Queue.mainQueue().after(0.55) {
|
||||
self.canSkip = false
|
||||
self.updated(transition: .easeInOut(duration: 0.2))
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(0.6) {
|
||||
if let controller = self.getController() as? GiftViewScreen {
|
||||
@ -1661,7 +1811,6 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
self.subject = .profileGift(peerId, result)
|
||||
controller.animateSuccess()
|
||||
self.updated(transition: .spring(duration: 0.4))
|
||||
|
||||
Queue.mainQueue().after(0.5) {
|
||||
@ -2267,13 +2416,13 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
availableSize: CGSize(width: context.availableSize.width - perksSideInset * 2.0, height: 10000.0),
|
||||
transition: context.transition
|
||||
)
|
||||
headerComponents.append({
|
||||
context.add(wearPerks
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + wearPerks.size.height / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
})
|
||||
|
||||
context.add(wearPerks
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + wearPerks.size.height / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
|
||||
originY += wearPerks.size.height
|
||||
originY += 16.0
|
||||
} else if showUpgradePreview {
|
||||
@ -3166,8 +3315,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
HeaderButtonComponent(
|
||||
title: uniqueGift.resellAmounts == nil ? strings.Gift_View_Sell : strings.Gift_View_Unlist,
|
||||
iconName: uniqueGift.resellAmounts == nil ? "Premium/Collectible/Sell" : "Premium/Collectible/Unlist"
|
||||
title: (uniqueGift.resellAmounts ?? []).isEmpty ? strings.Gift_View_Sell : strings.Gift_View_Unlist,
|
||||
iconName: (uniqueGift.resellAmounts ?? []).isEmpty ? "Premium/Collectible/Sell" : "Premium/Collectible/Unlist"
|
||||
)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
@ -3629,7 +3778,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
} else {
|
||||
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .stars })
|
||||
}
|
||||
if let resellAmount {
|
||||
if let resellAmount, wearPeerNameChild == nil {
|
||||
if incoming || ownerPeerId == component.context.account.peerId {
|
||||
let priceButton = priceButton.update(
|
||||
component: PlainButtonComponent(
|
||||
@ -3658,7 +3807,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 textColor = theme.list.itemSecondaryTextColor
|
||||
let linkColor = theme.actionSheet.controlAccentColor
|
||||
@ -3672,7 +3826,9 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
|
||||
var addressToOpen: String?
|
||||
var descriptionText: String
|
||||
if let uniqueGift, selling {
|
||||
if isChatTheme {
|
||||
descriptionText = strings.Gift_View_OpenChatTheme
|
||||
} else if let uniqueGift, selling {
|
||||
let ownerName: String
|
||||
if case let .peerId(peerId) = uniqueGift.owner {
|
||||
ownerName = state.peerMap[peerId]?.compactDisplayTitle ?? ""
|
||||
@ -3731,7 +3887,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
},
|
||||
tapAction: { [weak state] attributes, _ in
|
||||
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)
|
||||
} else {
|
||||
state?.updateSavedToProfile(!savedToProfile)
|
||||
@ -3761,7 +3920,25 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
)
|
||||
let buttonChild: _UpdatedChildComponent
|
||||
if showWearPreview, let uniqueGift {
|
||||
if state.canSkip {
|
||||
buttonChild = button.update(
|
||||
component: ButtonComponent(
|
||||
background: buttonBackground,
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable("skip"),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Upgrade_Skip, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: state.inProgress,
|
||||
action: { [weak state] in
|
||||
if let state {
|
||||
state.skipAnimation()
|
||||
}
|
||||
}),
|
||||
availableSize: buttonSize,
|
||||
transition: context.transition
|
||||
)
|
||||
} else if showWearPreview, let uniqueGift {
|
||||
let buttonContent: AnyComponentWithIdentity<Empty>
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||
@ -4081,7 +4258,9 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
isEnabled: true,
|
||||
displaysProgress: state.inProgress,
|
||||
action: { [weak state] in
|
||||
state?.dismiss(animated: true)
|
||||
if let state {
|
||||
state.dismiss(animated: true)
|
||||
}
|
||||
}),
|
||||
availableSize: buttonSize,
|
||||
transition: context.transition
|
||||
@ -4090,7 +4269,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: originY), size: buttonChild.size)
|
||||
|
||||
var buttonAlpha: CGFloat = 1.0
|
||||
if let nextGiftToUpgrade = state.nextGiftToUpgrade, case let .generic(gift) = nextGiftToUpgrade.gift {
|
||||
if let nextGiftToUpgrade = state.nextGiftToUpgrade, case let .generic(gift) = nextGiftToUpgrade.gift, !state.canSkip {
|
||||
buttonAlpha = 0.0
|
||||
|
||||
let upgradeNextButton = upgradeNextButton.update(
|
||||
@ -4098,7 +4277,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
content: AnyComponent(
|
||||
HStack([
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Upgrade Next Gift", font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Upgrade_UpgradeNext, font: Font.regular(17.0), textColor: theme.actionSheet.controlAccentColor)))
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "icon", component: AnyComponent(
|
||||
GiftItemComponent(
|
||||
@ -4278,12 +4457,12 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
case upgradePreview([StarGift.UniqueGift.Attribute], String)
|
||||
case wearPreview(StarGift.UniqueGift)
|
||||
|
||||
var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, refunded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, resellAmounts: [CurrencyAmount]?, canExportDate: Int32?, upgradeMessageId: Int32?, canTransferDate: Int32?, canResaleDate: Int32?, prepaidUpgradeHash: String?)? {
|
||||
var arguments: (peerId: EnginePeer.Id?, fromPeerId: EnginePeer.Id?, fromPeerName: String?, messageId: EngineMessage.Id?, reference: StarGiftReference?, incoming: Bool, gift: StarGift, date: Int32, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, pinnedToTop: Bool?, converted: Bool, upgraded: Bool, refunded: Bool, canUpgrade: Bool, upgradeStars: Int64?, transferStars: Int64?, resellAmounts: [CurrencyAmount]?, canExportDate: Int32?, upgradeMessageId: Int32?, canTransferDate: Int32?, canResaleDate: Int32?, prepaidUpgradeHash: String?, upgradeSeparate: Bool)? {
|
||||
switch self {
|
||||
case let .message(message):
|
||||
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash, giftMessageId, _):
|
||||
case let .starGift(gift, convertStars, text, entities, nameHidden, savedToProfile, converted, upgraded, canUpgrade, upgradeStars, isRefunded, _, upgradeMessageId, peerId, senderId, savedId, prepaidUpgradeHash, giftMessageId, upgradeSeparate):
|
||||
var reference: StarGiftReference
|
||||
if let peerId, let giftMessageId {
|
||||
reference = .message(messageId: EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Cloud, id: giftMessageId))
|
||||
@ -4292,7 +4471,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
} else {
|
||||
reference = .message(messageId: message.id)
|
||||
}
|
||||
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash)
|
||||
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate)
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate):
|
||||
var reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
@ -4317,13 +4496,13 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
if case let .unique(uniqueGift) = gift {
|
||||
resellAmounts = uniqueGift.resellAmounts
|
||||
}
|
||||
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, false, nil, transferStars, resellAmounts, canExportDate, nil, canTransferDate, canResaleDate, nil)
|
||||
return (message.id.peerId, senderId ?? message.author?.id, message.author?.compactDisplayTitle, message.id, reference, incoming, gift, message.timestamp, nil, nil, nil, false, savedToProfile, nil, false, false, false, false, nil, transferStars, resellAmounts, canExportDate, nil, canTransferDate, canResaleDate, nil, false)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case let .uniqueGift(gift, _), let .wearPreview(gift):
|
||||
return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, false, nil, nil, gift.resellAmounts, nil, nil, nil, nil, nil)
|
||||
return (nil, nil, nil, nil, nil, false, .unique(gift), 0, nil, nil, nil, false, false, nil, false, false, false, false, nil, nil, gift.resellAmounts, nil, nil, nil, nil, nil, false)
|
||||
case let .profileGift(peerId, gift):
|
||||
var messageId: EngineMessage.Id?
|
||||
if case let .message(messageIdValue) = gift.reference {
|
||||
@ -4333,7 +4512,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
if case let .unique(uniqueGift) = gift.gift {
|
||||
resellAmounts = uniqueGift.resellAmounts
|
||||
}
|
||||
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash)
|
||||
return (peerId, gift.fromPeer?.id, gift.fromPeer?.compactDisplayTitle, messageId, gift.reference, false, gift.gift, gift.date, gift.convertStars, gift.text, gift.entities, gift.nameHidden, gift.savedToProfile, gift.pinnedToTop, false, false, false, gift.canUpgrade, gift.upgradeStars, gift.transferStars, resellAmounts, gift.canExportDate, nil, gift.canTransferDate, gift.canResaleDate, gift.prepaidUpgradeHash, gift.upgradeSeparate)
|
||||
case .soldOutGift:
|
||||
return nil
|
||||
case .upgradePreview:
|
||||
@ -4374,13 +4553,14 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
fileprivate let balanceOverlay = ComponentView<Empty>()
|
||||
|
||||
fileprivate let updateSavedToProfile: ((StarGiftReference, Bool) -> Void)?
|
||||
fileprivate let convertToStars: (() -> Void)?
|
||||
fileprivate let convertToStars: ((StarGiftReference) -> Void)?
|
||||
fileprivate let transferGift: ((Bool, EnginePeer.Id) -> Signal<Never, TransferStarGiftError>)?
|
||||
fileprivate let upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)?
|
||||
fileprivate let upgradeGift: ((Int64?, StarGiftReference, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)?
|
||||
fileprivate let buyGift: ((String, EnginePeer.Id, CurrencyAmount?) -> Signal<Never, BuyStarGiftError>)?
|
||||
fileprivate let updateResellStars: ((CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)?
|
||||
fileprivate let togglePinnedToTop: ((Bool) -> Bool)?
|
||||
fileprivate let updateResellStars: ((StarGiftReference, CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)?
|
||||
fileprivate let togglePinnedToTop: ((StarGiftReference, Bool) -> Bool)?
|
||||
fileprivate let shareStory: ((StarGift.UniqueGift) -> Void)?
|
||||
fileprivate let openChatTheme: (() -> Void)?
|
||||
|
||||
public var disposed: () -> Void = {}
|
||||
|
||||
@ -4391,13 +4571,14 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
index: Int? = nil,
|
||||
forceDark: Bool = false,
|
||||
updateSavedToProfile: ((StarGiftReference, Bool) -> Void)? = nil,
|
||||
convertToStars: (() -> Void)? = nil,
|
||||
convertToStars: ((StarGiftReference) -> Void)? = nil,
|
||||
transferGift: ((Bool, EnginePeer.Id) -> Signal<Never, TransferStarGiftError>)? = nil,
|
||||
upgradeGift: ((Int64?, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? = nil,
|
||||
upgradeGift: ((Int64?, StarGiftReference, Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError>)? = nil,
|
||||
buyGift: ((String, EnginePeer.Id, CurrencyAmount?) -> Signal<Never, BuyStarGiftError>)? = nil,
|
||||
updateResellStars: ((CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)? = nil,
|
||||
togglePinnedToTop: ((Bool) -> Bool)? = nil,
|
||||
shareStory: ((StarGift.UniqueGift) -> Void)? = nil
|
||||
updateResellStars: ((StarGiftReference, CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)? = nil,
|
||||
togglePinnedToTop: ((StarGiftReference, Bool) -> Bool)? = nil,
|
||||
shareStory: ((StarGift.UniqueGift) -> Void)? = nil,
|
||||
openChatTheme: (() -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
@ -4410,6 +4591,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
self.updateResellStars = updateResellStars
|
||||
self.togglePinnedToTop = togglePinnedToTop
|
||||
self.shareStory = shareStory
|
||||
self.openChatTheme = openChatTheme
|
||||
|
||||
if case let .unique(gift) = subject.arguments?.gift, gift.resellForTonOnly {
|
||||
self.balanceCurrency = .ton
|
||||
@ -4453,8 +4635,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
self.navigationPresentation = .flatModal
|
||||
self.automaticallyControlPresentationContextLayout = false
|
||||
|
||||
if "".isEmpty {
|
||||
let upgradableGiftsContext = ProfileGiftsContext(account: context.account, peerId: context.account.peerId, collectionId: nil, sorting: .date, filter: [.displayed, .hidden, .limitedUpgradable])
|
||||
if let gift = subject.arguments?.gift, case .generic = gift {
|
||||
let upgradableGiftsContext = ProfileGiftsContext(account: context.account, peerId: context.account.peerId, collectionId: nil, sorting: .date, filter: [.displayed, .hidden, .limitedUpgradable], limit: 50)
|
||||
self.upgradableDisposable = (upgradableGiftsContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let self else {
|
||||
@ -4529,7 +4711,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
|
||||
if let arguments = self.subject.arguments, let _ = self.subject.arguments?.resellAmounts {
|
||||
if let arguments = self.subject.arguments, let resellAmounts = self.subject.arguments?.resellAmounts, !resellAmounts.isEmpty {
|
||||
if case let .unique(uniqueGift) = arguments.gift, case .peerId(self.context.account.peerId) = uniqueGift.owner {
|
||||
} else {
|
||||
self.showBalance = true
|
||||
|
||||
@ -127,7 +127,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable {
|
||||
}))
|
||||
}
|
||||
|
||||
let presence: EnginePeer.Presence
|
||||
var presence: EnginePeer.Presence
|
||||
if member.peer.id == context.account.peerId {
|
||||
presence = EnginePeer.Presence(status: .present(until: Int32.max), lastActivity: 0)
|
||||
} else if let value = member.presence {
|
||||
@ -136,6 +136,17 @@ private enum PeerMembersListEntry: Comparable, Identifiable {
|
||||
presence = EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0)
|
||||
}
|
||||
|
||||
var status: ContactsPeerItemStatus = .presence(presence, presentationData.dateTimeFormat)
|
||||
if let user = member.peer as? TelegramUser, let botInfo = user.botInfo {
|
||||
let botStatus: String
|
||||
if botInfo.flags.contains(.hasAccessToChatHistory) {
|
||||
botStatus = presentationData.strings.Bot_GroupStatusReadsHistory
|
||||
} else {
|
||||
botStatus = presentationData.strings.Bot_GroupStatusDoesNotReadHistory
|
||||
}
|
||||
status = .custom(string: NSAttributedString(string: botStatus, font: Font.regular(floor(presentationData.listsFontSize.itemListBaseFontSize * 14.0 / 17.0)), textColor: presentationData.theme.list.itemSecondaryTextColor), multiline: false, isActive: false, icon: nil)
|
||||
}
|
||||
|
||||
return ContactsPeerItem(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
style: .plain,
|
||||
@ -145,7 +156,7 @@ private enum PeerMembersListEntry: Comparable, Identifiable {
|
||||
context: context,
|
||||
peerMode: .memberList,
|
||||
peer: .peer(peer: EnginePeer(member.peer), chatPeer: EnginePeer(member.peer)),
|
||||
status: .presence(presence, presentationData.dateTimeFormat),
|
||||
status: status,
|
||||
rightLabelText: label,
|
||||
enabled: true,
|
||||
selection: .none,
|
||||
|
||||
@ -589,14 +589,14 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var currentSavedMusic: TelegramMediaFile?
|
||||
if !self.isSettings, let screenData {
|
||||
if let peer, peer.id != self.context.account.peerId || self.isMyProfile, let screenData {
|
||||
if let savedMusicState = screenData.savedMusicState {
|
||||
currentSavedMusic = savedMusicState.files.first
|
||||
} else if let cachedUserData = screenData.cachedData as? CachedUserData {
|
||||
currentSavedMusic = cachedUserData.savedMusic
|
||||
}
|
||||
}
|
||||
let musicHeight: CGFloat = hasBackground ? 24.0 : 16.0
|
||||
let musicHeight: CGFloat = hasBackground || self.isAvatarExpanded ? 24.0 : 16.0
|
||||
let bottomInset: CGFloat = currentSavedMusic != nil ? musicHeight : 0.0
|
||||
|
||||
let isLandscape = containerInset > 16.0
|
||||
@ -2640,7 +2640,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
return musicBackground
|
||||
}()
|
||||
musicTransition.updateFrame(view: musicBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: backgroundHeight - musicHeight - buttonRightOrigin.y), size: CGSize(width: backgroundFrame.width, height: musicHeight)))
|
||||
musicTransition.updateFrame(view: musicBackground, frame: CGRect(origin: CGPoint(x: 0.0, y: backgroundHeight - 24.0 - buttonRightOrigin.y), size: CGSize(width: backgroundFrame.width, height: 24.0)))
|
||||
|
||||
if let _ = self.navigationTransition {
|
||||
transition.updateAlpha(layer: musicBackground.layer, alpha: 1.0 - transitionFraction)
|
||||
@ -2698,7 +2698,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: backgroundFrame.width, height: musicHeight)
|
||||
)
|
||||
let musicFrame = CGRect(origin: CGPoint(x: 0.0, y: (apparentBackgroundHeight - backgroundHeight) + backgroundHeight - musicHeight - (hasBackground ? 0.0 : 4.0)), size: musicSize)
|
||||
let musicFrame = CGRect(origin: CGPoint(x: 0.0, y: (apparentBackgroundHeight - backgroundHeight) + backgroundHeight - musicHeight - (hasBackground || self.isAvatarExpanded ? 0.0 : 4.0)), size: musicSize)
|
||||
if let musicView = music.view {
|
||||
if musicView.superview == nil {
|
||||
self.regularContentNode.view.addSubview(musicView)
|
||||
|
||||
@ -1163,9 +1163,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: "Set as Main Tab", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: params.presentationData.strings.PeerInfo_Tabs_SetMainTab, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ReorderItems"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] _, f in
|
||||
guard let self else {
|
||||
@ -1183,7 +1182,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, ASGestureRecognizerDelegat
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = UndoOverlayController(presentationData: params.presentationData, content: .actionSucceeded(title: nil, text: "Tab order changed.", cancel: nil, destructive: false), action: { _ in return true })
|
||||
let controller = UndoOverlayController(presentationData: params.presentationData, content: .actionSucceeded(title: nil, text: params.presentationData.strings.PeerInfo_Tabs_SetMainTab_Succeed, cancel: nil, destructive: false), action: { _ in return true })
|
||||
self.parentController?.present(controller, in: .current)
|
||||
})
|
||||
}
|
||||
|
||||
@ -5012,8 +5012,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
profileGifts.updateStarGiftAddedToProfile(reference: reference, added: added)
|
||||
},
|
||||
convertToStars: { [weak profileGifts] in
|
||||
guard let profileGifts, let reference = gift.reference else {
|
||||
convertToStars: { [weak profileGifts] reference in
|
||||
guard let profileGifts else {
|
||||
return
|
||||
}
|
||||
profileGifts.convertStarGift(reference: reference)
|
||||
@ -5024,8 +5024,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
return profileGifts.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId)
|
||||
},
|
||||
upgradeGift: { [weak profileGifts] formId, keepOriginalInfo in
|
||||
guard let profileGifts, let reference = gift.reference else {
|
||||
upgradeGift: { [weak profileGifts] formId, reference, keepOriginalInfo in
|
||||
guard let profileGifts else {
|
||||
return .never()
|
||||
}
|
||||
return profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
||||
|
||||
@ -611,8 +611,8 @@ final class GiftsListView: UIView {
|
||||
}
|
||||
self.profileGifts.updateStarGiftAddedToProfile(reference: reference, added: added)
|
||||
},
|
||||
convertToStars: { [weak self] in
|
||||
guard let self, let reference = product.reference else {
|
||||
convertToStars: { [weak self] reference in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.profileGifts.convertStarGift(reference: reference)
|
||||
@ -623,8 +623,8 @@ final class GiftsListView: UIView {
|
||||
}
|
||||
return self.profileGifts.transferStarGift(prepaid: prepaid, reference: reference, peerId: peerId)
|
||||
},
|
||||
upgradeGift: { [weak self] formId, keepOriginalInfo in
|
||||
guard let self, let reference = product.reference else {
|
||||
upgradeGift: { [weak self] formId, reference, keepOriginalInfo in
|
||||
guard let self else {
|
||||
return .never()
|
||||
}
|
||||
return self.profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
||||
@ -635,36 +635,34 @@ final class GiftsListView: UIView {
|
||||
}
|
||||
return self.profileGifts.buyStarGift(slug: slug, peerId: peerId, price: price)
|
||||
},
|
||||
updateResellStars: { [weak self] price in
|
||||
guard let self, let reference = product.reference else {
|
||||
updateResellStars: { [weak self] reference, price in
|
||||
guard let self else {
|
||||
return .never()
|
||||
}
|
||||
return self.profileGifts.updateStarGiftResellPrice(reference: reference, price: price)
|
||||
},
|
||||
togglePinnedToTop: { [weak self] pinnedToTop in
|
||||
togglePinnedToTop: { [weak self] reference, pinnedToTop in
|
||||
guard let self else {
|
||||
return false
|
||||
}
|
||||
if let reference = product.reference {
|
||||
if pinnedToTop && self.pinnedReferences.count >= self.maxPinnedCount {
|
||||
self.displayUnpinScreen?(product, {
|
||||
dismissImpl?()
|
||||
})
|
||||
return false
|
||||
}
|
||||
self.profileGifts.updateStarGiftPinnedToTop(reference: reference, pinnedToTop: pinnedToTop)
|
||||
|
||||
var title = ""
|
||||
if case let .unique(uniqueGift) = product.gift {
|
||||
title = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: params.presentationData.dateTimeFormat))"
|
||||
}
|
||||
|
||||
if pinnedToTop {
|
||||
Queue.mainQueue().after(0.35) {
|
||||
let toastTitle = params.presentationData.strings.PeerInfo_Gifts_ToastPinned_TitleNew(title).string
|
||||
let toastText = params.presentationData.strings.PeerInfo_Gifts_ToastPinned_Text
|
||||
self.parentController?.present(UndoOverlayController(presentationData: params.presentationData, content: .universal(animation: "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
if pinnedToTop && self.pinnedReferences.count >= self.maxPinnedCount {
|
||||
self.displayUnpinScreen?(product, {
|
||||
dismissImpl?()
|
||||
})
|
||||
return false
|
||||
}
|
||||
self.profileGifts.updateStarGiftPinnedToTop(reference: reference, pinnedToTop: pinnedToTop)
|
||||
|
||||
var title = ""
|
||||
if case let .unique(uniqueGift) = product.gift {
|
||||
title = "\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: params.presentationData.dateTimeFormat))"
|
||||
}
|
||||
|
||||
if pinnedToTop {
|
||||
Queue.mainQueue().after(0.35) {
|
||||
let toastTitle = params.presentationData.strings.PeerInfo_Gifts_ToastPinned_TitleNew(title).string
|
||||
let toastText = params.presentationData.strings.PeerInfo_Gifts_ToastPinned_Text
|
||||
self.parentController?.present(UndoOverlayController(presentationData: params.presentationData, content: .universal(animation: "anim_toastpin", scale: 0.06, colors: [:], title: toastTitle, text: toastText, customUndoText: nil, timeout: 5), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
}
|
||||
}
|
||||
return true
|
||||
@ -996,7 +994,7 @@ final class GiftsListView: UIView {
|
||||
|
||||
fadeTransition.setAlpha(view: self.emptyResultsClippingView, alpha: visibleHeight < 300.0 ? 0.0 : 1.0)
|
||||
|
||||
if self.peerId == self.context.account.peerId, !self.canSelect && !self.filteredResultsAreEmpty && self.profileGifts.collectionId == nil {
|
||||
if self.peerId == self.context.account.peerId, !self.canSelect && !self.filteredResultsAreEmpty && self.profileGifts.collectionId == nil && self.emptyResultsClippingView.isHidden {
|
||||
let footerText: ComponentView<Empty>
|
||||
if let current = self.footerText {
|
||||
footerText = current
|
||||
@ -1024,6 +1022,13 @@ final class GiftsListView: UIView {
|
||||
transition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: floor((size.width - footerTextSize.width) / 2.0), y: contentHeight), size: footerTextSize))
|
||||
}
|
||||
contentHeight += footerTextSize.height
|
||||
} else if let footerText = self.footerText {
|
||||
self.footerText = nil
|
||||
if let view = footerText.view {
|
||||
fadeTransition.setAlpha(view: view, alpha: 0.0, completion: { _ in
|
||||
view.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return contentHeight
|
||||
|
||||
@ -90,7 +90,6 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
private let tabSelector = ComponentView<Empty>()
|
||||
public private(set) var currentCollection: GiftCollection = .all
|
||||
|
||||
private var footerText: ComponentView<Empty>?
|
||||
private var panelBackground: NavigationBackgroundNode?
|
||||
private var panelSeparator: ASDisplayNode?
|
||||
private var panelButton: ComponentView<Empty>?
|
||||
|
||||
@ -377,24 +377,46 @@ private final class ProfileLevelInfoScreenComponent: Component {
|
||||
descriptionTextString = environment.strings.ProfileLevelInfo_OtherDescription(component.peer.compactDisplayTitle).string
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
var titleItems: [AnimatedTextComponent.Item] = []
|
||||
|
||||
let ratingTitle = environment.strings.ProfileLevelInfo_RatingTitle
|
||||
let futureTitle = environment.strings.ProfileLevelInfo_FutureRatingTitle
|
||||
|
||||
if self.isPreviewingPendingRating {
|
||||
titleItems.append(AnimatedTextComponent.Item(
|
||||
id: AnyHashable(0),
|
||||
isUnbreakable: false,
|
||||
content: .text("Future ")
|
||||
))
|
||||
titleItems.append(AnimatedTextComponent.Item(
|
||||
id: AnyHashable(1),
|
||||
isUnbreakable: true,
|
||||
content: .text("Rating")
|
||||
))
|
||||
if let range = futureTitle.range(of: ratingTitle) {
|
||||
if !futureTitle[..<range.lowerBound].isEmpty {
|
||||
titleItems.append(AnimatedTextComponent.Item(
|
||||
id: AnyHashable(0),
|
||||
isUnbreakable: false,
|
||||
content: .text(String(futureTitle[..<range.lowerBound]))
|
||||
))
|
||||
}
|
||||
|
||||
titleItems.append(AnimatedTextComponent.Item(
|
||||
id: AnyHashable(1),
|
||||
isUnbreakable: true,
|
||||
content: .text(ratingTitle)
|
||||
))
|
||||
|
||||
if !futureTitle[range.upperBound...].isEmpty {
|
||||
titleItems.append(AnimatedTextComponent.Item(
|
||||
id: AnyHashable(2),
|
||||
isUnbreakable: false,
|
||||
content: .text(String(futureTitle[range.upperBound...]))
|
||||
))
|
||||
}
|
||||
} else {
|
||||
titleItems.append(AnimatedTextComponent.Item(
|
||||
id: AnyHashable(0),
|
||||
isUnbreakable: true,
|
||||
content: .text(futureTitle)
|
||||
))
|
||||
}
|
||||
} else {
|
||||
titleItems.append(AnimatedTextComponent.Item(
|
||||
id: AnyHashable(1),
|
||||
isUnbreakable: true,
|
||||
content: .text("Rating")
|
||||
content: .text(ratingTitle)
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -465,7 +465,9 @@ final class UserAppearanceScreenComponent: Component {
|
||||
resellForTonOnly: false,
|
||||
releasedBy: nil,
|
||||
valueAmount: nil,
|
||||
valueCurrency: nil
|
||||
valueCurrency: nil,
|
||||
flags: [],
|
||||
themePeerId: nil
|
||||
)
|
||||
signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate)
|
||||
} else {
|
||||
|
||||
@ -273,9 +273,9 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode {
|
||||
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)
|
||||
|> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
|
||||
if let value = value {
|
||||
return .single(value)
|
||||
|> mapToSignal { generatorAndRects -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
|
||||
if let (generator, _) = generatorAndRects {
|
||||
return .single(generator)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
@ -673,14 +673,13 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
let withdrawAvailable = (self.revenueState?.balances.overallRevenue.amount.value ?? 0) > 0
|
||||
|
||||
if component.starsContext.ton {
|
||||
//TODO:localize
|
||||
let proceedsSize = self.proceedsView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Proceeds Overview".uppercased(),
|
||||
string: environment.strings.Ton_ProceedsOverview.uppercased(),
|
||||
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
@ -691,14 +690,14 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
AnyComponentWithIdentity(id: 0, component: AnyComponent(StarsOverviewItemComponent(
|
||||
theme: environment.theme,
|
||||
dateTimeFormat: environment.dateTimeFormat,
|
||||
title: "Balance Available to Withdraw",
|
||||
title: environment.strings.Ton_AvailableBalance,
|
||||
value: self.revenueState?.balances.availableBalance ?? CurrencyAmount(amount: .zero, currency: .ton),
|
||||
rate: self.revenueState?.usdRate ?? 0.0
|
||||
))),
|
||||
AnyComponentWithIdentity(id: 1, component: AnyComponent(StarsOverviewItemComponent(
|
||||
theme: environment.theme,
|
||||
dateTimeFormat: environment.dateTimeFormat,
|
||||
title: "Total Lifetime Proceeds",
|
||||
title: environment.strings.Ton_LifetimeProceeds,
|
||||
value: self.revenueState?.balances.overallRevenue ?? CurrencyAmount(amount: .zero, currency: .ton),
|
||||
rate: self.revenueState?.usdRate ?? 0.0
|
||||
)))
|
||||
@ -725,7 +724,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
let balanceInfoRawString = "Collect your TON using Fragment. [Learn More >]()"
|
||||
let balanceInfoRawString = environment.strings.Ton_WithdrawViaFragment_Info
|
||||
let balanceInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(balanceInfoRawString, attributes: termsMarkdownAttributes, textAlignment: .natural))
|
||||
if self.cachedChevronImage == nil || self.cachedChevronImage?.1 !== environment.theme {
|
||||
self.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme)
|
||||
@ -754,7 +753,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
},
|
||||
tapAction: { [weak self] attributes, _ in
|
||||
if let controller = self?.controller?() as? StarsTransactionsScreen, let navigationController = controller.navigationController as? NavigationController {
|
||||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: environment.strings.Stars_BotRevenue_Withdraw_Info_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: component.starsContext.ton ? environment.strings.Ton_WithdrawViaFragment_Info_URL : environment.strings.Stars_BotRevenue_Withdraw_Info_URL, forceExternal: false, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
|
||||
}
|
||||
}
|
||||
)) : nil,
|
||||
@ -766,7 +765,7 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
count: self.starsState?.balance ?? StarsAmount.zero,
|
||||
currency: component.starsContext.ton ? .ton : .stars,
|
||||
rate: nil,
|
||||
actionTitle: component.starsContext.ton ? "Withdraw via Fragment" : (withdrawAvailable ? environment.strings.Stars_Intro_BuyShort : environment.strings.Stars_Intro_Buy),
|
||||
actionTitle: component.starsContext.ton ? environment.strings.Ton_WithdrawViaFragment : (withdrawAvailable ? environment.strings.Stars_Intro_BuyShort : environment.strings.Stars_Intro_Buy),
|
||||
actionAvailable: (!premiumConfiguration.areStarsDisabled && !premiumConfiguration.isPremiumDisabled),
|
||||
actionIsEnabled: true,
|
||||
actionIcon: component.starsContext.ton ? nil : PresentationResourcesItemList.itemListRoundTopupIcon(environment.theme),
|
||||
|
||||
12
submodules/TelegramUI/Images.xcassets/Settings/Refresh.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Refresh.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "rotate_18.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Settings/Refresh.imageset/rotate_18.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/Refresh.imageset/rotate_18.pdf
vendored
Normal file
Binary file not shown.
@ -100,10 +100,23 @@ func openWebAppImpl(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var botPeer = botPeer
|
||||
if case let .inline(bot) = source {
|
||||
botPeer = bot
|
||||
}
|
||||
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.BotAppSettings(id: botPeer.id))
|
||||
|> deliverOnMainQueue).start(next: { appSettings in
|
||||
let openWebView = { [weak parentController] in
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -305,6 +318,11 @@ func openWebAppImpl(
|
||||
presentImpl = { [weak controller] c, a in
|
||||
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
|
||||
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: {
|
||||
@ -314,25 +332,37 @@ func openWebAppImpl(
|
||||
}
|
||||
}
|
||||
|
||||
if skipTermsOfService {
|
||||
openWebView()
|
||||
} else {
|
||||
var botPeer = botPeer
|
||||
if case let .inline(bot) = source {
|
||||
botPeer = bot
|
||||
var isAttachMenuBotInstalled: Bool?
|
||||
if let _ = attachMenuBot {
|
||||
if let _ = attachMenuBots.first(where: { $0.peer.id == botPeer.id && !$0.flags.contains(.notActivated) }) {
|
||||
isAttachMenuBotInstalled = true
|
||||
} else {
|
||||
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 !noticed || attachMenuBot?.flags.contains(.notActivated) == true || isAttachMenuBotInstalled == false {
|
||||
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)
|
||||
}
|
||||
|
||||
if value {
|
||||
openWebView()
|
||||
} else {
|
||||
if skipTermsOfService {
|
||||
openWebView(false)
|
||||
} else {
|
||||
let controller = webAppLaunchConfirmationController(context: context, updatedPresentationData: updatedPresentationData, peer: botPeer, completion: { _ in
|
||||
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
|
||||
openWebView()
|
||||
openWebView(false)
|
||||
}, showMore: nil, openTerms: {
|
||||
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: {})
|
||||
@ -340,7 +370,9 @@ func openWebAppImpl(
|
||||
})
|
||||
parentController.present(controller, in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
openWebView(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -123,6 +123,7 @@ import ChatEmptyNode
|
||||
import ChatMediaInputStickerGridItem
|
||||
import AdsInfoScreen
|
||||
import Photos
|
||||
import ChatThemeScreen
|
||||
|
||||
extension ChatControllerImpl {
|
||||
public func presentThemeSelection() {
|
||||
@ -182,15 +183,15 @@ extension ChatControllerImpl {
|
||||
previewTheme: { [weak self] chatTheme, dark in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentCrossfadeSnapshot()
|
||||
strongSelf.chatThemeAndDarkAppearancePreviewPromise.set(.single((chatTheme ?? .emoticon(""), dark)))
|
||||
strongSelf.chatThemeAndDarkAppearancePreviewPromise.set(.single((chatTheme, dark)))
|
||||
}
|
||||
},
|
||||
changeWallpaper: { [weak self] in
|
||||
guard let strongSelf = self, let peerId else {
|
||||
guard let self, let peerId else {
|
||||
return
|
||||
}
|
||||
if let themeController = strongSelf.themeScreen {
|
||||
strongSelf.themeScreen = nil
|
||||
if let themeController = self.themeScreen {
|
||||
self.themeScreen = nil
|
||||
themeController.dimTapped()
|
||||
}
|
||||
let dismissControllers = { [weak self] in
|
||||
@ -206,69 +207,67 @@ extension ChatControllerImpl {
|
||||
}
|
||||
var openWallpaperPickerImpl: ((Bool) -> Void)?
|
||||
let openWallpaperPicker = { [weak self] animateAppearance in
|
||||
guard let strongSelf = self else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = wallpaperMediaPickerController(
|
||||
context: strongSelf.context,
|
||||
updatedPresentationData: strongSelf.updatedPresentationData,
|
||||
context: context,
|
||||
updatedPresentationData: self.updatedPresentationData,
|
||||
peer: EnginePeer(peer),
|
||||
animateAppearance: animateAppearance,
|
||||
completion: { [weak self] _, result in
|
||||
guard let strongSelf = self, let asset = result as? PHAsset else {
|
||||
guard let self, let asset = result as? PHAsset else {
|
||||
return
|
||||
}
|
||||
let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false))
|
||||
let controller = WallpaperGalleryController(context: context, source: .asset(asset), mode: .peer(EnginePeer(peer), false))
|
||||
controller.navigationPresentation = .modal
|
||||
controller.apply = { [weak self] wallpaper, options, editedImage, cropRect, brightness, forBoth in
|
||||
if let strongSelf = self {
|
||||
uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peerId, forBoth: forBoth, completion: {
|
||||
Queue.mainQueue().after(0.3, {
|
||||
dismissControllers()
|
||||
})
|
||||
controller.apply = { wallpaper, options, editedImage, cropRect, brightness, forBoth in
|
||||
uploadCustomPeerWallpaper(context: context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peerId, forBoth: forBoth, completion: {
|
||||
Queue.mainQueue().after(0.3, {
|
||||
dismissControllers()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
self.push(controller)
|
||||
},
|
||||
openColors: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), push: { [weak self] controller in
|
||||
if let strongSelf = self {
|
||||
strongSelf.push(controller)
|
||||
let controller = standaloneColorPickerController(context: context, peer: EnginePeer(peer), push: { [weak self] controller in
|
||||
if let self {
|
||||
self.push(controller)
|
||||
}
|
||||
}, openGallery: {
|
||||
openWallpaperPickerImpl?(false)
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
strongSelf.push(controller)
|
||||
self.push(controller)
|
||||
}
|
||||
)
|
||||
controller.navigationPresentation = .flatModal
|
||||
strongSelf.push(controller)
|
||||
self.push(controller)
|
||||
}
|
||||
openWallpaperPickerImpl = openWallpaperPicker
|
||||
openWallpaperPicker(true)
|
||||
},
|
||||
resetWallpaper: { [weak self] in
|
||||
guard let strongSelf = self, let peerId else {
|
||||
guard let self, let peerId else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil, forBoth: false).startStandalone()
|
||||
let _ = self.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil, forBoth: false).startStandalone()
|
||||
},
|
||||
completion: { [weak self] chatTheme in
|
||||
guard let strongSelf = self, let peerId else {
|
||||
guard let self, let peerId else {
|
||||
return
|
||||
}
|
||||
if canResetWallpaper && chatTheme != nil {
|
||||
let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil, forBoth: false).startStandalone()
|
||||
let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil, forBoth: true).startStandalone()
|
||||
}
|
||||
strongSelf.chatThemeAndDarkAppearancePreviewPromise.set(.single((chatTheme ?? .emoticon(""), nil)))
|
||||
let _ = context.engine.themes.setChatTheme(peerId: peerId, chatTheme: chatTheme ?? .emoticon("")).startStandalone(completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatThemeAndDarkAppearancePreviewPromise.set(.single((nil, nil)))
|
||||
if let self {
|
||||
self.chatThemeAndDarkAppearancePreviewPromise.set(.single((nil, nil)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -141,6 +141,7 @@ import SuggestedPostApproveAlert
|
||||
import AVFoundation
|
||||
import BalanceNeededScreen
|
||||
import FaceScanScreen
|
||||
import ChatThemeScreen
|
||||
|
||||
public final class ChatControllerOverlayPresentationData {
|
||||
public let expandData: (ASDisplayNode?, () -> Void)
|
||||
@ -1110,8 +1111,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.present(BotReceiptController(context: self.context, messageId: message.id), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
return true
|
||||
case .setChatTheme:
|
||||
self.presentThemeSelection()
|
||||
case let .setChatTheme(chatTheme):
|
||||
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
|
||||
case let .setChatWallpaper(wallpaper, _):
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
@ -5808,10 +5830,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
}
|
||||
case let .gift(gift, wallpaper):
|
||||
let _ = gift
|
||||
let _ = wallpaper
|
||||
//TODO:release
|
||||
case .gift:
|
||||
if let darkAppearancePreview = darkAppearancePreview {
|
||||
useDarkAppearance = darkAppearancePreview
|
||||
}
|
||||
if let theme = makePresentationTheme(chatTheme: chatTheme, dark: useDarkAppearance) {
|
||||
theme.forceSync = true
|
||||
presentationData = presentationData.withUpdated(theme: theme).withUpdated(chatWallpaper: theme.chat.defaultWallpaper)
|
||||
|
||||
Queue.mainQueue().after(1.0, {
|
||||
theme.forceSync = false
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if let darkAppearancePreview = darkAppearancePreview {
|
||||
useDarkAppearance = darkAppearancePreview
|
||||
|
||||
@ -46,6 +46,7 @@ import ComponentFlow
|
||||
import ChatEmptyNode
|
||||
import SpaceWarpView
|
||||
import ChatSideTopicsPanel
|
||||
import ChatThemeScreen
|
||||
|
||||
final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem {
|
||||
let itemNode: OverlayMediaItemNode
|
||||
@ -3582,7 +3583,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
|
||||
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)
|
||||
if self.pendingSwitchToChatLocation == nil {
|
||||
|
||||
@ -2053,7 +2053,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
}
|
||||
|
||||
var isSuspiciousPeer = false
|
||||
if let cachedUserData = data.cachedData as? CachedUserData, let peerStatusSettings = cachedUserData.peerStatusSettings, peerStatusSettings.flags.contains(.canBlock) {
|
||||
if let cachedUserData = data.cachedData as? CachedUserData, let peerStatusSettings = cachedUserData.peerStatusSettings, peerStatusSettings.flags.contains(.canBlock) || peerStatusSettings.flags.contains(.canReport) {
|
||||
isSuspiciousPeer = true
|
||||
}
|
||||
|
||||
@ -2309,7 +2309,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
}
|
||||
|
||||
let rawTransition = preparedChatHistoryViewTransition(from: previous, to: processedView, reason: reason, reverse: reverse, chatLocation: chatLocation, source: source, controllerInteraction: controllerInteraction, scrollPosition: updatedScrollPosition, scrollAnimationCurve: scrollAnimationCurve, initialData: initialData?.initialData, keyboardButtonsMessage: keyboardButtonsMessage, cachedData: initialData?.cachedData, cachedDataMessages: initialData?.cachedDataMessages, readStateData: initialData?.readStateData, flashIndicators: flashIndicators, updatedMessageSelection: previousSelectedMessages != selectedMessages, messageTransitionNode: messageTransitionNode(), allUpdated: !isSavedMusic || forceUpdateAll)
|
||||
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition)
|
||||
var mappedTransition = mappedChatHistoryViewListTransition(context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, mode: mode, lastHeaderId: lastHeaderId, isSavedMusic: isSavedMusic, canReorder: processedView.filteredEntries.count > 1 && canReorder, animateFromPreviousFilter: resetScrolling, transition: rawTransition)
|
||||
|
||||
if disableAnimations {
|
||||
mappedTransition.options.remove(.AnimateInsertion)
|
||||
|
||||
@ -1481,7 +1481,7 @@ func openResolvedUrlImpl(
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
}, dismissed: {
|
||||
}, openChatTheme: nil, dismissed: {
|
||||
dismissedImpl?()
|
||||
})
|
||||
navigationController?.pushViewController(controller)
|
||||
|
||||
@ -57,6 +57,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
private var savedIdsPromise = Promise<Set<Int64>?>()
|
||||
private var savedIds: Set<Int64>?
|
||||
|
||||
private var copyProtectionEnabled = false
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
chatLocation: ChatLocation,
|
||||
@ -275,13 +277,17 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}
|
||||
|
||||
self.historyNode.endedInteractiveDragging = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch strongSelf.historyNode.visibleContentOffset() {
|
||||
switch self.historyNode.visibleContentOffset() {
|
||||
case let .known(value):
|
||||
if value <= -10.0 {
|
||||
strongSelf.requestDismiss()
|
||||
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(_, _, canReorder) = playlistLocation, canReorder {
|
||||
|
||||
} else {
|
||||
if value <= -10.0 {
|
||||
self.requestDismiss()
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
@ -388,14 +394,25 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}
|
||||
})
|
||||
|
||||
self.savedIdsDisposable = (context.engine.peers.savedMusicIds()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] savedIds in
|
||||
let copyProtectionEnabled: Signal<Bool, NoError>
|
||||
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 {
|
||||
return
|
||||
}
|
||||
let isFirstTime = self.savedIds == nil
|
||||
self.savedIds = savedIds
|
||||
self.savedIdsPromise.set(.single(savedIds))
|
||||
self.copyProtectionEnabled = copyProtectionEnabled
|
||||
|
||||
let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : .animated(duration: 0.5, curve: .spring)
|
||||
self.updateFloatingHeaderOffset(offset: self.floatingHeaderOffset ?? 0.0, transition: transition)
|
||||
@ -439,11 +456,11 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
panRecognizer.delaysTouchesBegan = false
|
||||
panRecognizer.cancelsTouchesInView = true
|
||||
panRecognizer.shouldBegin = { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
guard let self else {
|
||||
return false
|
||||
}
|
||||
if strongSelf.controlsNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.controlsNode.view)) {
|
||||
if strongSelf.controlsNode.frame.maxY <= strongSelf.historyNode.frame.minY {
|
||||
if self.controlsNode.bounds.contains(self.view.convert(point, to: self.controlsNode.view)) {
|
||||
if self.controlsNode.frame.maxY <= self.historyNode.frame.minY {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -494,8 +511,13 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
return .single(true)
|
||||
}
|
||||
})
|
||||
self.historyNode.useMainQueueTransactions = false
|
||||
self.historyNode.autoScrollWhenReordering = false
|
||||
self.historyNode.didEndScrollingWithOverscroll = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.requestDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -557,19 +579,18 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
func addToSavedMusic(file: FileMediaReference) {
|
||||
self.dismissAllTooltips()
|
||||
|
||||
var actionText: String? = "View"
|
||||
var actionText: String? = self.presentationData.strings.MediaPlayer_SavedMusic_AddedToProfile_View
|
||||
if let itemId = self.controlsNode.currentItemId as? PeerMessagesMediaPlaylistItemId, itemId.messageId.namespace == Namespaces.Message.Local && itemId.messageId.peerId == self.context.account.peerId {
|
||||
actionText = nil
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let controller = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .universalImage(
|
||||
image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SavedMusic"), color: .white)!,
|
||||
size: nil,
|
||||
title: nil,
|
||||
text: "Audio added to your profile.",
|
||||
text: self.presentationData.strings.MediaPlayer_SavedMusic_AddedToProfile,
|
||||
customUndoText: actionText,
|
||||
timeout: 3.0
|
||||
),
|
||||
@ -607,14 +628,13 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
func removeFromSavedMusic(file: FileMediaReference) {
|
||||
self.dismissAllTooltips()
|
||||
|
||||
//TODO:localize
|
||||
let controller = UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .universalImage(
|
||||
image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SavedMusic"), color: .white)!,
|
||||
size: nil,
|
||||
title: nil,
|
||||
text: "Audio removed from your profile.",
|
||||
text: self.presentationData.strings.MediaPlayer_SavedMusic_RemovedFromProfile,
|
||||
customUndoText: nil,
|
||||
timeout: 3.0
|
||||
),
|
||||
@ -638,6 +658,12 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -672,7 +698,6 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
insets.top = max(0.0, listNodeSize.height - floor(56.0 * 3.5))
|
||||
|
||||
var itemOffsetInsets = insets
|
||||
|
||||
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(_, _, canReorder) = playlistLocation, canReorder {
|
||||
itemOffsetInsets.top = 0.0
|
||||
itemOffsetInsets.bottom = 0.0
|
||||
@ -732,7 +757,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
self.requestDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if let recognizer = gestureRecognizer as? UIPanGestureRecognizer {
|
||||
let location = recognizer.location(in: self.view)
|
||||
@ -981,9 +1006,16 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
|
||||
insets.top = max(0.0, listNodeSize.height - floor(56.0 * 3.5))
|
||||
|
||||
var itemOffsetInsets = insets
|
||||
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(_, _, canReorder) = playlistLocation, canReorder {
|
||||
itemOffsetInsets.top = 0.0
|
||||
itemOffsetInsets.bottom = 0.0
|
||||
insets = itemOffsetInsets
|
||||
}
|
||||
|
||||
self.historyNode.frame = CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listNodeSize)
|
||||
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, duration: 0.0, curve: .Default(duration: nil))
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, itemOffsetInsets: itemOffsetInsets, duration: 0.0, curve: .Default(duration: nil))
|
||||
self.historyNode.updateLayout(transition: .immediate, updateSizeAndInsets: updateSizeAndInsets)
|
||||
|
||||
self.historyNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
@ -1010,10 +1042,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
//TODO:localize
|
||||
if canSaveToProfile || canSaveToSavedMessages {
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: "Save to...", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
if let self {
|
||||
var subActions: [ContextMenuItem] = []
|
||||
subActions.append(
|
||||
@ -1025,7 +1056,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
|
||||
if canSaveToProfile {
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: "Profile", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_Profile, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let self {
|
||||
@ -1037,7 +1068,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
|
||||
if canSaveToSavedMessages {
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: "Saved Messages", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_SavedMessages, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let self {
|
||||
@ -1048,7 +1079,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}
|
||||
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: "Files", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_Files, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
if let self {
|
||||
@ -1072,7 +1103,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
|
||||
let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: "Choose where you want this audio to be saved.", textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: noAction))
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_Info, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: noAction))
|
||||
)
|
||||
|
||||
c?.pushItems(items: .single(ContextController.Items(content: .list(subActions))))
|
||||
@ -1080,7 +1111,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
items.append(.action(ContextMenuActionItem(text: "Save to Files", icon: { theme in
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveToFiles, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
@ -1112,7 +1143,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
addedSeparator = true
|
||||
}
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: "Show in Chat", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_ShowInChat, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
guard let self else {
|
||||
@ -1125,7 +1156,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}
|
||||
|
||||
// items.append(
|
||||
// .action(ContextMenuActionItem(text: "Forward", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
// .action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_Forward, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
// f(.default)
|
||||
//
|
||||
// if let _ = self {
|
||||
@ -1162,9 +1193,9 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
items.append(.separator)
|
||||
addedSeparator = true
|
||||
}
|
||||
var actionTitle = "Delete"
|
||||
var actionTitle = presentationData.strings.MediaPlayer_ContextMenu_Delete
|
||||
if case .custom = self.source {
|
||||
actionTitle = "Remove"
|
||||
actionTitle = presentationData.strings.MediaPlayer_ContextMenu_Remove
|
||||
}
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: actionTitle, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] c, f in
|
||||
|
||||
@ -1026,13 +1026,12 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
||||
self.separatorNode.isHidden = hasSectionHeader
|
||||
|
||||
if hasSectionHeader {
|
||||
//TODO:localize
|
||||
let sideInset: CGFloat = 16.0
|
||||
var sectionTitle = "AUDIO IN THIS CHAT"
|
||||
var sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_ThisChat
|
||||
if let peerName = self.peerName {
|
||||
sectionTitle = "\(peerName.uppercased())'S PLAYLIST"
|
||||
sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_SavedMusic(peerName.uppercased()).string
|
||||
} else if case .custom = self.source {
|
||||
sectionTitle = "YOUR PLAYLIST"
|
||||
sectionTitle = self.presentationData.strings.MediaPlayer_Playlist_SavedMusicYou
|
||||
}
|
||||
let sectionTitleSize = self.sectionTitle.update(
|
||||
transition: .immediate,
|
||||
@ -1096,7 +1095,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
let attributedString = parseMarkdownIntoAttributedString("This audio is visible on your profile. [Remove >]()", attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
||||
let attributedString = parseMarkdownIntoAttributedString(self.presentationData.strings.MediaPlayer_SavedMusic_RemoveFromProfile, attributes: markdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = attributedString.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
|
||||
attributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
|
||||
@ -1125,7 +1124,6 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
||||
))
|
||||
profileAudioOffset = 18.0
|
||||
} else {
|
||||
//TODO:localize
|
||||
profileAudioComponent = AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: self.presentationData.theme.list.itemCheckColors.fillColor,
|
||||
@ -1139,7 +1137,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
||||
BundleIconComponent(name: "Peer Info/SaveMusic", tintColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
)),
|
||||
AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: "Add to Profile", font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)))
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: self.presentationData.strings.MediaPlayer_SavedMusic_AddToProfile, font: Font.semibold(17.0), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)))
|
||||
))
|
||||
], spacing: 8.0)
|
||||
)),
|
||||
|
||||
@ -3768,8 +3768,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return GiftViewScreen(context: context, subject: .message(message), shareStory: shareStory)
|
||||
}
|
||||
|
||||
public func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, dismissed: (() -> Void)?) -> ViewController {
|
||||
let controller = GiftViewScreen(context: context, subject: .uniqueGift(gift, nil), shareStory: shareStory)
|
||||
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, openChatTheme: openChatTheme)
|
||||
controller.disposed = {
|
||||
dismissed?()
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
|
||||
@ -8,6 +8,7 @@ import TelegramCore
|
||||
import AccountContext
|
||||
import SwiftSignalKit
|
||||
import WallpaperResources
|
||||
import StickerResources
|
||||
import FastBlur
|
||||
import Svg
|
||||
import GZip
|
||||
@ -85,6 +86,7 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
|
||||
var rotation: CGFloat { get set }
|
||||
|
||||
func update(wallpaper: TelegramWallpaper, animated: Bool)
|
||||
func update(wallpaper: TelegramWallpaper, starGift: StarGift?, animated: Bool)
|
||||
func _internalUpdateIsSettingUpWallpaper()
|
||||
func updateLayout(size: CGSize, displayMode: WallpaperDisplayMode, transition: ContainedViewLayoutTransition)
|
||||
func updateIsLooping(_ isLooping: Bool)
|
||||
@ -758,11 +760,20 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
|
||||
private var validLayout: (CGSize, WallpaperDisplayMode)?
|
||||
private var wallpaper: TelegramWallpaper?
|
||||
private var starGift: StarGift?
|
||||
private var modelRectIndex: Int32?
|
||||
|
||||
private var modelStickerNode: DefaultAnimatedStickerNodeImpl?
|
||||
|
||||
private var isSettingUpWallpaper: Bool = false
|
||||
|
||||
private struct CachedValidPatternImage {
|
||||
let generate: (TransformImageArguments) -> DrawingContext?
|
||||
let generated: ValidPatternGeneratedImage
|
||||
let rects: [WallpaperGiftPatternRect]
|
||||
let starGift: StarGift?
|
||||
let symbolImage: UIImage?
|
||||
let modelRectIndex: Int32?
|
||||
let image: UIImage
|
||||
}
|
||||
|
||||
@ -771,6 +782,10 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
private struct ValidPatternImage {
|
||||
let wallpaper: TelegramWallpaper
|
||||
let invertPattern: Bool
|
||||
let rects: [WallpaperGiftPatternRect]
|
||||
let starGift: StarGift?
|
||||
let symbolImage: UIImage?
|
||||
let modelRectIndex: Int32?
|
||||
let generate: (TransformImageArguments) -> DrawingContext?
|
||||
}
|
||||
private var validPatternImage: ValidPatternImage?
|
||||
@ -781,10 +796,38 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
let patternColor: UInt32
|
||||
let backgroundColor: UInt32
|
||||
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 let patternImageDisposable = MetaDisposable()
|
||||
private let symbolImageDisposable = MetaDisposable()
|
||||
|
||||
private var bubbleTheme: PresentationTheme?
|
||||
private var bubbleCorners: PresentationChatBubbleCorners?
|
||||
@ -930,11 +973,26 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
let previousWallpaper = self.wallpaper
|
||||
let previousStarGift = self.starGift
|
||||
|
||||
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 snapshotView = self.view.snapshotView(afterScreenUpdates: false) {
|
||||
@ -1132,6 +1190,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
}
|
||||
default:
|
||||
self.patternImageDisposable.set(nil)
|
||||
self.symbolImageDisposable.set(nil)
|
||||
self.validPatternImage = nil
|
||||
self.patternImageLayer.isHidden = true
|
||||
self.patternImageLayer.fillWithColorUntilLoaded = nil
|
||||
@ -1146,6 +1205,9 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
guard let wallpaper = self.wallpaper else {
|
||||
return
|
||||
}
|
||||
|
||||
let starGift = self.starGift
|
||||
let modelRectIndex = self.modelRectIndex
|
||||
|
||||
var invertPattern: Bool = false
|
||||
var patternIsLight: Bool = false
|
||||
@ -1169,13 +1231,20 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let previousStarGift = self.validPatternImage?.starGift, !updated {
|
||||
updated = true
|
||||
if previousStarGift.slug == starGift?.slug {
|
||||
updated = false
|
||||
}
|
||||
}
|
||||
|
||||
if updated {
|
||||
self.validPatternGeneratedImage = nil
|
||||
self.validPatternImage = nil
|
||||
|
||||
if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper && cachedValidPatternImage.generated.invertPattern == invertPattern {
|
||||
self.validPatternImage = ValidPatternImage(wallpaper: cachedValidPatternImage.generated.wallpaper, invertPattern: invertPattern, generate: cachedValidPatternImage.generate)
|
||||
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, rects: cachedValidPatternImage.rects, starGift: cachedValidPatternImage.starGift, symbolImage: cachedValidPatternImage.symbolImage, modelRectIndex: cachedValidPatternImage.modelRectIndex, generate: cachedValidPatternImage.generate)
|
||||
} else {
|
||||
func reference(for resource: EngineMediaResource, media: EngineMedia) -> MediaResourceReference {
|
||||
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))))
|
||||
|
||||
let signal = patternWallpaperImage(account: self.context.account, accountManager: self.context.sharedContext.accountManager, representations: convertedRepresentations, mode: .screen, autoFetchFullSize: true)
|
||||
self.patternImageDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] generator in
|
||||
guard let strongSelf = self else {
|
||||
var symbolImage: Signal<UIImage?, NoError> = .single(nil)
|
||||
if let starGift = self.starGift, case let .unique(uniqueGift) = starGift {
|
||||
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
|
||||
}
|
||||
|
||||
if let generator = generator {
|
||||
/*generator = { arguments in
|
||||
let scale = arguments.scale ?? UIScreenScale
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: scale, clear: true)
|
||||
|
||||
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)
|
||||
if let (generator, rects) = generator {
|
||||
self.validPatternImage = ValidPatternImage(wallpaper: wallpaper, invertPattern: invertPattern, rects: rects, starGift: starGift, symbolImage: symbolImage, modelRectIndex: modelRectIndex, generate: generator)
|
||||
self.validPatternGeneratedImage = nil
|
||||
if let (size, displayMode) = self.validLayout {
|
||||
self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: .immediate)
|
||||
} else {
|
||||
strongSelf._isReady.set(true)
|
||||
self._isReady.set(true)
|
||||
}
|
||||
} else {
|
||||
strongSelf._isReady.set(true)
|
||||
self._isReady.set(true)
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -1244,8 +1309,8 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
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 {
|
||||
self.validPatternGeneratedImage = updatedGeneratedImage
|
||||
|
||||
@ -1256,7 +1321,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
self.patternImageLayer.suspendCompositionUpdates = false
|
||||
self.patternImageLayer.updateCompositionIfNeeded()
|
||||
} 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 let drawingContext = validPatternImage.generate(patternArguments) {
|
||||
if let image = drawingContext.generateImage() {
|
||||
@ -1267,7 +1332,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
self.patternImageLayer.updateCompositionIfNeeded()
|
||||
|
||||
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 {
|
||||
self.updatePatternPresentation()
|
||||
@ -1288,7 +1353,7 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
strongSelf.updatePatternPresentation()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1306,6 +1371,68 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
|
||||
self.updatePatternPresentation()
|
||||
}
|
||||
}
|
||||
|
||||
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 > $0.containerSize.height * 0.1 && $0.center.y < $0.containerSize.height * 0.9 }
|
||||
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))
|
||||
}
|
||||
@ -1559,3 +1686,14 @@ private protocol WallpaperComponentView: AnyObject {
|
||||
public func createWallpaperBackgroundNode(context: AccountContext, forChatDisplay: Bool, useSharedAnimationPhase: Bool = false) -> WallpaperBackgroundNode {
|
||||
return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
|
||||
}
|
||||
|
||||
private extension StarGift {
|
||||
var slug: String? {
|
||||
switch self {
|
||||
case let .unique(uniqueGift):
|
||||
return uniqueGift.slug
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -353,14 +353,18 @@ public struct PatternWallpaperArguments: TransformImageCustomArguments {
|
||||
let customPatternColor: UIColor?
|
||||
let bakePatternAlpha: CGFloat
|
||||
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.rotation = rotation
|
||||
self.customPatternColor = customPatternColor
|
||||
self.preview = preview
|
||||
self.bakePatternAlpha = bakePatternAlpha
|
||||
self.displayMode = displayMode
|
||||
self.symbolImage = symbolImage
|
||||
self.modelRectIndex = modelRectIndex
|
||||
}
|
||||
|
||||
public func serialized() -> NSArray {
|
||||
@ -373,6 +377,9 @@ public struct PatternWallpaperArguments: TransformImageCustomArguments {
|
||||
array.add(NSNumber(value: self.preview))
|
||||
array.add(NSNumber(value: Double(self.bakePatternAlpha)))
|
||||
array.add(NSNumber(value: self.displayMode.rawValue))
|
||||
if let symbolImage {
|
||||
array.add(symbolImage)
|
||||
}
|
||||
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)
|
||||
|> mapToSignal { fullSizeData, fullSizeComplete in
|
||||
if !autoFetchFullSize || fullSizeComplete {
|
||||
return patternWallpaperImageInternal(fullSizeData: fullSizeData, fullSizeComplete: fullSizeComplete, mode: mode)
|
||||
return patternWallpaperImageInternal(fullSizeData: fullSizeData, fullSizeComplete: fullSizeComplete, mode: mode, forcePrepared: forcePrepared)
|
||||
} else {
|
||||
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
|
||||
if case .thumbnail = mode {
|
||||
prominent = true
|
||||
@ -491,7 +514,11 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
|
||||
|
||||
return .single((fullSizeData, fullSizeComplete))
|
||||
|> 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
|
||||
if scale.isZero {
|
||||
scale = arguments.scale ?? UIScreenScale
|
||||
@ -561,12 +588,12 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
|
||||
var image: UIImage?
|
||||
if let fullSizeData = fullSizeData {
|
||||
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 {
|
||||
image = UIImage(data: fullSizeData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let customPatternColor = customArguments.customPatternColor, customPatternColor.alpha < 1.0 {
|
||||
patternIsInverted = true
|
||||
c.setBlendMode(.copy)
|
||||
@ -674,7 +701,7 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
|
||||
} else {
|
||||
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))
|
||||
}
|
||||
case let .file(file):
|
||||
if file.settings.intensity == 100 {
|
||||
print()
|
||||
}
|
||||
|
||||
rotation = file.settings.rotation
|
||||
if file.isPattern, let intensity = file.settings.intensity, intensity < 0 {
|
||||
backgroundColor = (.black, nil, [])
|
||||
@ -1464,6 +1495,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
|
||||
} else {
|
||||
backgroundColor = (theme.chatList.backgroundColor, nil, [])
|
||||
}
|
||||
|
||||
wallpaperSignal = cachedWallpaper(account: account, slug: file.slug, settings: file.settings)
|
||||
|> mapToSignal { wallpaper in
|
||||
if let wallpaper = wallpaper, case let .file(file) = wallpaper.wallpaper {
|
||||
@ -1479,6 +1511,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
|
||||
let convertedPreviewRepresentations : [ImageRepresentationWithReference] = file.file.previewRepresentations.map {
|
||||
ImageRepresentationWithReference(representation: $0, reference: .wallpaper(wallpaper: .slug(file.slug), resource: $0.resource))
|
||||
}
|
||||
let useFallback = convertedPreviewRepresentations.isEmpty
|
||||
|
||||
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)))
|
||||
@ -1511,12 +1544,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
|
||||
arguments = PatternWallpaperArguments(colors: [.clear], rotation: nil, customPatternColor: isLight ? .black : .white)
|
||||
}
|
||||
|
||||
return patternWallpaperImage(account: account, accountManager: accountManager, representations: convertedPreviewRepresentations, mode: .thumbnail, autoFetchFullSize: true)
|
||||
|> mapToSignal { generator -> Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError> in
|
||||
return patternWallpaperImage(account: account, accountManager: accountManager, representations: useFallback ? convertedRepresentations : convertedPreviewRepresentations, mode: useFallback ? .screen : .thumbnail, autoFetchFullSize: true)
|
||||
|> mapToSignal { generatorAndRects -> Signal<((UIColor, UIColor?, [UInt32]), [UIColor], [UIColor], UIImage?, Bool, Bool, CGFloat, Int32?), NoError> in
|
||||
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 context = generator?(imageArguments)
|
||||
let context = generatorAndRects?.generator(imageArguments)
|
||||
let image = context?.generateImage()
|
||||
|
||||
if !file.settings.colors.isEmpty {
|
||||
@ -1771,7 +1803,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager<Tele
|
||||
|
||||
c.restoreGState()
|
||||
} else {
|
||||
let rect = CGRect(x: 8.0, y: arguments.drawingSize.height - 24.0 - 9.0 - 3.0, width: arguments.drawingSize.width - 8.0 * 2.0, height: 24.0)
|
||||
let rect = CGRect(x: 8.0, y: arguments.drawingSize.height - 24.0 - 9.0 - 3.0, width: 48.0, height: 24.0)
|
||||
c.addPath(UIBezierPath(roundedRect: rect, cornerRadius: 12.0).cgPath)
|
||||
c.clip()
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "11.15",
|
||||
"app": "12.0",
|
||||
"xcode": "16.2",
|
||||
"bazel": "8.3.1:0cac3a67dc5429c68272dc6944104952e9e4cf84b29d126a5ff3fbaa59045217",
|
||||
"macos": "15"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user