mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 11:20:18 +00:00
Various improvements
This commit is contained in:
parent
9c99c04e64
commit
58c532b51e
@ -8715,9 +8715,17 @@ Sorry for the inconvenience.";
|
|||||||
"AvatarEditor.Stickers" = "Stickers";
|
"AvatarEditor.Stickers" = "Stickers";
|
||||||
"AvatarEditor.SwitchToEmoji" = "SWITCH TO EMOJI";
|
"AvatarEditor.SwitchToEmoji" = "SWITCH TO EMOJI";
|
||||||
"AvatarEditor.SwitchToStickers" = "SWITCH TO STICKERS";
|
"AvatarEditor.SwitchToStickers" = "SWITCH TO STICKERS";
|
||||||
|
|
||||||
"AvatarEditor.SetProfilePhoto" = "Set as Profile Photo";
|
"AvatarEditor.SetProfilePhoto" = "Set as Profile Photo";
|
||||||
"AvatarEditor.SetGroupPhoto" = "Set as Group Photo";
|
"AvatarEditor.SetGroupPhoto" = "Set as Group Photo";
|
||||||
"AvatarEditor.SetChannelPhoto" = "Set as Group Photo";
|
"AvatarEditor.SetChannelPhoto" = "Set as Group Photo";
|
||||||
|
|
||||||
"AvatarEditor.Set" = "Set";
|
"AvatarEditor.Set" = "Set";
|
||||||
|
|
||||||
|
"Premium.UpgradeDescription" = "Your current **Telegram Premium** plan can be upgraded at a **discount**.";
|
||||||
|
"Premium.CurrentPlan" = "your current plan";
|
||||||
|
"Premium.UpgradeFor" = "Upgrade for %@ / month";
|
||||||
|
"Premium.UpgradeForAnnual" = "Upgrade for %@ / year";
|
||||||
|
|
||||||
|
"ChatList.PremiumAnnualDiscountTitle" = "Telegram Premium with a discount of %@";
|
||||||
|
"ChatList.PremiumAnnualDiscountText" = "Sign up for the annual payment plan for Telegram Premium now to get the discount.";
|
||||||
|
"ChatList.PremiumAnnualUpgradeTitle" = "Save on your subscription up to %@";
|
||||||
|
"ChatList.PremiumAnnualUpgradeText" = "Upgrade to the annual payment plan for Telegram Premium to enjoy the discount.";
|
||||||
|
|||||||
@ -185,7 +185,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
|
|||||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {})
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {})
|
||||||
interaction.isInlineMode = isInlineMode
|
interaction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
let items = (0 ..< 2).map { _ -> ChatListItem in
|
let items = (0 ..< 2).map { _ -> ChatListItem in
|
||||||
|
|||||||
@ -2124,6 +2124,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
})
|
})
|
||||||
}, openStorageManagement: {
|
}, openStorageManagement: {
|
||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
|
}, openPremiumIntro: {
|
||||||
})
|
})
|
||||||
chatListInteraction.isSearchMode = true
|
chatListInteraction.isSearchMode = true
|
||||||
|
|
||||||
@ -3325,7 +3326,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
|
|||||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {})
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {})
|
||||||
var isInlineMode = false
|
var isInlineMode = false
|
||||||
if case .topics = key {
|
if case .topics = key {
|
||||||
isInlineMode = false
|
isInlineMode = false
|
||||||
|
|||||||
@ -1846,7 +1846,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
let messageString: NSAttributedString
|
let messageString: NSAttributedString
|
||||||
if !message.text.isEmpty && entities.count > 0 {
|
if !message.text.isEmpty && entities.count > 0 {
|
||||||
messageString = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: authorAttributedString == nil ? 2 : 1), entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: italicTextFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
|
var messageText = message.text
|
||||||
|
var entities = entities
|
||||||
|
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, !translation.text.isEmpty {
|
||||||
|
messageText = translation.text
|
||||||
|
entities = translation.entities
|
||||||
|
}
|
||||||
|
|
||||||
|
messageString = stringWithAppliedEntities(trimToLineCount(messageText, lineCount: authorAttributedString == nil ? 2 : 1), entities: entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: italicTextFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message._asMessage())
|
||||||
} else if spoilers != nil || customEmojiRanges != nil {
|
} else if spoilers != nil || customEmojiRanges != nil {
|
||||||
let mutableString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
|
let mutableString = NSMutableAttributedString(string: messageText, font: textFont, textColor: theme.messageTextColor)
|
||||||
if let spoilers = spoilers {
|
if let spoilers = spoilers {
|
||||||
|
|||||||
@ -94,6 +94,7 @@ public final class ChatListNodeInteraction {
|
|||||||
let openForumThread: (EnginePeer.Id, Int64) -> Void
|
let openForumThread: (EnginePeer.Id, Int64) -> Void
|
||||||
let openStorageManagement: () -> Void
|
let openStorageManagement: () -> Void
|
||||||
let openPasswordSetup: () -> Void
|
let openPasswordSetup: () -> Void
|
||||||
|
let openPremiumIntro: () -> Void
|
||||||
|
|
||||||
public var searchTextHighightState: String?
|
public var searchTextHighightState: String?
|
||||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||||
@ -137,7 +138,8 @@ public final class ChatListNodeInteraction {
|
|||||||
present: @escaping (ViewController) -> Void,
|
present: @escaping (ViewController) -> Void,
|
||||||
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void,
|
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void,
|
||||||
openStorageManagement: @escaping () -> Void,
|
openStorageManagement: @escaping () -> Void,
|
||||||
openPasswordSetup: @escaping () -> Void
|
openPasswordSetup: @escaping () -> Void,
|
||||||
|
openPremiumIntro: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.activateSearch = activateSearch
|
self.activateSearch = activateSearch
|
||||||
self.peerSelected = peerSelected
|
self.peerSelected = peerSelected
|
||||||
@ -169,6 +171,7 @@ public final class ChatListNodeInteraction {
|
|||||||
self.openForumThread = openForumThread
|
self.openForumThread = openForumThread
|
||||||
self.openStorageManagement = openStorageManagement
|
self.openStorageManagement = openStorageManagement
|
||||||
self.openPasswordSetup = openPasswordSetup
|
self.openPasswordSetup = openPasswordSetup
|
||||||
|
self.openPremiumIntro = openPremiumIntro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -615,6 +618,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
nodeInteraction?.openStorageManagement()
|
nodeInteraction?.openStorageManagement()
|
||||||
case .setupPassword:
|
case .setupPassword:
|
||||||
nodeInteraction?.openPasswordSetup()
|
nodeInteraction?.openPasswordSetup()
|
||||||
|
case .premiumUpgrade, .premiumAnnualDiscount:
|
||||||
|
nodeInteraction?.openPremiumIntro()
|
||||||
}
|
}
|
||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
}
|
}
|
||||||
@ -866,6 +871,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
nodeInteraction?.openStorageManagement()
|
nodeInteraction?.openStorageManagement()
|
||||||
case .setupPassword:
|
case .setupPassword:
|
||||||
nodeInteraction?.openPasswordSetup()
|
nodeInteraction?.openPasswordSetup()
|
||||||
|
case .premiumUpgrade, .premiumAnnualDiscount:
|
||||||
|
nodeInteraction?.openPremiumIntro()
|
||||||
}
|
}
|
||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
case .HeaderEntry:
|
case .HeaderEntry:
|
||||||
@ -1363,16 +1370,25 @@ public final class ChatListNode: ListView {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Queue.mainQueue().after(0.6) { [weak self] in
|
||||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.6, execute: { [weak self] in
|
if let self {
|
||||||
guard let self else {
|
let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .setupPassword).start()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .setupPassword).start()
|
}
|
||||||
})
|
|
||||||
|
|
||||||
let controller = self.context.sharedContext.makeSetupTwoFactorAuthController(context: self.context)
|
let controller = self.context.sharedContext.makeSetupTwoFactorAuthController(context: self.context)
|
||||||
self.push?(controller)
|
self.push?(controller)
|
||||||
|
}, openPremiumIntro: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Queue.mainQueue().after(0.6) { [weak self] in
|
||||||
|
if let self {
|
||||||
|
let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .annualPremium).start()
|
||||||
|
let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .upgradePremium).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads)
|
||||||
|
self.push?(controller)
|
||||||
})
|
})
|
||||||
nodeInteraction.isInlineMode = isInlineMode
|
nodeInteraction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
@ -1437,31 +1453,66 @@ public final class ChatListNode: ListView {
|
|||||||
} else {
|
} else {
|
||||||
displayArchiveIntro = .single(false)
|
displayArchiveIntro = .single(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
let suggestPasswordSetup: Signal<Bool, NoError>
|
let suggestedChatListNotice: Signal<ChatListNotice?, NoError>
|
||||||
if case .chatList(groupId: .root) = location, chatListFilter == nil {
|
if case .chatList(groupId: .root) = location, chatListFilter == nil {
|
||||||
suggestPasswordSetup = .single(false) |> then(combineLatest(
|
suggestedChatListNotice = .single(nil)
|
||||||
getServerProvidedSuggestions(account: context.account),
|
|> then (
|
||||||
context.engine.auth.twoStepVerificationConfiguration()
|
combineLatest(
|
||||||
)
|
getServerProvidedSuggestions(account: context.account),
|
||||||
|> map { suggestions, configuration -> Bool in
|
context.engine.auth.twoStepVerificationConfiguration()
|
||||||
var notSet = false
|
)
|
||||||
switch configuration {
|
|> mapToSignal { suggestions, configuration -> Signal<ChatListNotice?, NoError> in
|
||||||
case let .notSet(pendingEmail):
|
if suggestions.contains(.setupPassword) {
|
||||||
if pendingEmail == nil {
|
var notSet = false
|
||||||
notSet = true
|
switch configuration {
|
||||||
|
case let .notSet(pendingEmail):
|
||||||
|
if pendingEmail == nil {
|
||||||
|
notSet = true
|
||||||
|
}
|
||||||
|
case .set:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if notSet {
|
||||||
|
return .single(.setupPassword)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||||
|
return inAppPurchaseManager.availableProducts
|
||||||
|
|> map { products -> ChatListNotice? in
|
||||||
|
if products.count > 1 {
|
||||||
|
let shortestOptionPrice: (Int64, NSDecimalNumber)
|
||||||
|
if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) {
|
||||||
|
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
|
||||||
|
} else {
|
||||||
|
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
|
||||||
|
}
|
||||||
|
for product in products {
|
||||||
|
if product.id.hasSuffix(".annual") {
|
||||||
|
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(12) / Float(shortestOptionPrice.0)
|
||||||
|
let discount = Int32(round((1.0 - fraction) * 20.0) * 5.0)
|
||||||
|
if suggestions.contains(.annualPremium) {
|
||||||
|
return .premiumAnnualDiscount(discount: discount)
|
||||||
|
} else if suggestions.contains(.upgradePremium) {
|
||||||
|
return .premiumUpgrade(discount: discount)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
}
|
}
|
||||||
case .set:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
if !notSet {
|
)
|
||||||
return false
|
|
||||||
}
|
|
||||||
return suggestions.contains(.setupPassword)
|
|
||||||
})
|
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
} else {
|
} else {
|
||||||
suggestPasswordSetup = .single(false)
|
suggestedChatListNotice = .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let storageInfo: Signal<Double?, NoError>
|
let storageInfo: Signal<Double?, NoError>
|
||||||
@ -1553,13 +1604,31 @@ public final class ChatListNode: ListView {
|
|||||||
|
|
||||||
let currentPeerId: EnginePeer.Id = context.account.peerId
|
let currentPeerId: EnginePeer.Id = context.account.peerId
|
||||||
|
|
||||||
let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestPasswordSetup, savedMessagesPeer, chatListViewUpdate, self.statePromise.get())
|
let chatListNodeViewTransition = combineLatest(
|
||||||
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestPasswordSetup, savedMessagesPeer, updateAndFilter, state) -> Signal<ChatListNodeListViewTransition, NoError> in
|
queue: viewProcessingQueue,
|
||||||
|
hideArchivedFolderByDefault,
|
||||||
|
displayArchiveIntro,
|
||||||
|
storageInfo,
|
||||||
|
suggestedChatListNotice,
|
||||||
|
savedMessagesPeer,
|
||||||
|
chatListViewUpdate,
|
||||||
|
self.statePromise.get()
|
||||||
|
)
|
||||||
|
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state) -> Signal<ChatListNodeListViewTransition, NoError> in
|
||||||
let (update, filter) = updateAndFilter
|
let (update, filter) = updateAndFilter
|
||||||
|
|
||||||
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
|
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
|
||||||
|
|
||||||
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, storageInfo: storageInfo, suggestPasswordSetup: suggestPasswordSetup, mode: mode, chatListLocation: location)
|
let notice: ChatListNotice?
|
||||||
|
if let suggestedChatListNotice {
|
||||||
|
notice = suggestedChatListNotice
|
||||||
|
} else if let storageInfo {
|
||||||
|
notice = .clearStorage(sizeFraction: storageInfo)
|
||||||
|
} else {
|
||||||
|
notice = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location)
|
||||||
var isEmpty = true
|
var isEmpty = true
|
||||||
var entries = rawEntries.filter { entry in
|
var entries = rawEntries.filter { entry in
|
||||||
switch entry {
|
switch entry {
|
||||||
|
|||||||
@ -49,6 +49,8 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
|
|||||||
enum ChatListNotice: Equatable {
|
enum ChatListNotice: Equatable {
|
||||||
case clearStorage(sizeFraction: Double)
|
case clearStorage(sizeFraction: Double)
|
||||||
case setupPassword
|
case setupPassword
|
||||||
|
case premiumUpgrade(discount: Int32)
|
||||||
|
case premiumAnnualDiscount(discount: Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||||
@ -404,7 +406,7 @@ private func offsetPinnedIndex(_ index: EngineChatList.Item.Index, offset: UInt1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, storageInfo: Double?, suggestPasswordSetup: Bool, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) {
|
func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) {
|
||||||
var result: [ChatListNodeEntry] = []
|
var result: [ChatListNodeEntry] = []
|
||||||
|
|
||||||
var pinnedIndexOffset: UInt16 = 0
|
var pinnedIndexOffset: UInt16 = 0
|
||||||
@ -666,10 +668,9 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
|||||||
if displayArchiveIntro {
|
if displayArchiveIntro {
|
||||||
result.append(.ArchiveIntro(presentationData: state.presentationData))
|
result.append(.ArchiveIntro(presentationData: state.presentationData))
|
||||||
}
|
}
|
||||||
if suggestPasswordSetup {
|
|
||||||
result.append(.Notice(presentationData: state.presentationData, notice: .setupPassword))
|
if let notice {
|
||||||
} else if let storageInfo {
|
result.append(.Notice(presentationData: state.presentationData, notice: notice))
|
||||||
result.append(.Notice(presentationData: state.presentationData, notice: .clearStorage(sizeFraction: storageInfo)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append(.HeaderEntry)
|
result.append(.HeaderEntry)
|
||||||
|
|||||||
@ -141,6 +141,26 @@ class ChatListStorageInfoItemNode: ListViewItemNode {
|
|||||||
//TODO:localize
|
//TODO:localize
|
||||||
titleString = NSAttributedString(string: "Protect Your Account", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)
|
titleString = NSAttributedString(string: "Protect Your Account", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)
|
||||||
textString = NSAttributedString(string: "Set a password that will be required each time you log in with this phone number.", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
textString = NSAttributedString(string: "Set a password that will be required each time you log in with this phone number.", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||||
|
case let .premiumUpgrade(discount):
|
||||||
|
let discountString = "\(discount)%"
|
||||||
|
let rawTitleString = item.strings.ChatList_PremiumAnnualUpgradeTitle(discountString)
|
||||||
|
let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
|
||||||
|
if let range = rawTitleString.ranges.first {
|
||||||
|
titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range)
|
||||||
|
}
|
||||||
|
titleString = titleStringValue
|
||||||
|
|
||||||
|
textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualUpgradeText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||||
|
case let .premiumAnnualDiscount(discount):
|
||||||
|
let discountString = "\(discount)%"
|
||||||
|
let rawTitleString = item.strings.ChatList_PremiumAnnualDiscountTitle(discountString)
|
||||||
|
let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
|
||||||
|
if let range = rawTitleString.ranges.first {
|
||||||
|
titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range)
|
||||||
|
}
|
||||||
|
titleString = titleStringValue
|
||||||
|
|
||||||
|
textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||||
|
|||||||
@ -21,21 +21,12 @@ public func reactionStaticImage(context: AccountContext, animation: TelegramMedi
|
|||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: MediaResourceReference.standalone(resource: animation.resource)).start()
|
let fetchDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .image, reference: MediaResourceReference.standalone(resource: animation.resource)).start()
|
||||||
|
|
||||||
let type: AnimationCacheAnimationType
|
|
||||||
if animation.isVideoSticker || animation.isVideoEmoji {
|
|
||||||
type = .video
|
|
||||||
} else if animation.isAnimatedSticker {
|
|
||||||
type = .lottie
|
|
||||||
} else {
|
|
||||||
type = .still
|
|
||||||
}
|
|
||||||
|
|
||||||
var customColor: UIColor?
|
var customColor: UIColor?
|
||||||
if animation.isCustomTemplateEmoji {
|
if animation.isCustomTemplateEmoji {
|
||||||
customColor = nil
|
customColor = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetchFrame = animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: MediaResourceReference.standalone(resource: animation.resource), type: type, keyframeOnly: true, customColor: customColor)
|
let fetchFrame = animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: MediaResourceReference.standalone(resource: animation.resource), type: AnimationCacheAnimationType(file: animation), keyframeOnly: true, customColor: customColor)
|
||||||
|
|
||||||
class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
|
||||||
let queue: Queue
|
let queue: Queue
|
||||||
|
|||||||
@ -281,7 +281,7 @@ class StickerPickerScreen: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let context = controller.context
|
let context = controller.context
|
||||||
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
|
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
|
||||||
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { views in
|
|> deliverOnMainQueue).start(next: { views in
|
||||||
|
|||||||
@ -93,6 +93,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
}, openForumThread: { _, _ in
|
}, openForumThread: { _, _ in
|
||||||
}, openStorageManagement: {
|
}, openStorageManagement: {
|
||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
|
}, openPremiumIntro: {
|
||||||
})
|
})
|
||||||
|
|
||||||
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
||||||
|
|||||||
@ -154,10 +154,12 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
|
|
||||||
private final class PaymentTransactionContext {
|
private final class PaymentTransactionContext {
|
||||||
var state: SKPaymentTransactionState?
|
var state: SKPaymentTransactionState?
|
||||||
|
var isUpgrade: Bool
|
||||||
var targetPeerId: PeerId?
|
var targetPeerId: PeerId?
|
||||||
let subscriber: (TransactionState) -> Void
|
let subscriber: (TransactionState) -> Void
|
||||||
|
|
||||||
init(targetPeerId: PeerId?, subscriber: @escaping (TransactionState) -> Void) {
|
init(isUpgrade: Bool, targetPeerId: PeerId?, subscriber: @escaping (TransactionState) -> Void) {
|
||||||
|
self.isUpgrade = isUpgrade
|
||||||
self.targetPeerId = targetPeerId
|
self.targetPeerId = targetPeerId
|
||||||
self.subscriber = subscriber
|
self.subscriber = subscriber
|
||||||
}
|
}
|
||||||
@ -236,7 +238,7 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func buyProduct(_ product: Product, targetPeerId: PeerId? = nil) -> Signal<PurchaseState, PurchaseError> {
|
public func buyProduct(_ product: Product, isUpgrade: Bool = false, targetPeerId: PeerId? = nil) -> Signal<PurchaseState, PurchaseError> {
|
||||||
if !self.canMakePayments {
|
if !self.canMakePayments {
|
||||||
return .fail(.cantMakePayments)
|
return .fail(.cantMakePayments)
|
||||||
}
|
}
|
||||||
@ -258,7 +260,7 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
self.stateQueue.async {
|
self.stateQueue.async {
|
||||||
let paymentContext = PaymentTransactionContext(targetPeerId: targetPeerId, subscriber: { state in
|
let paymentContext = PaymentTransactionContext(isUpgrade: isUpgrade, targetPeerId: targetPeerId, subscriber: { state in
|
||||||
switch state {
|
switch state {
|
||||||
case let .purchased(transactionId), let .restored(transactionId):
|
case let .purchased(transactionId), let .restored(transactionId):
|
||||||
if let transactionId = transactionId {
|
if let transactionId = transactionId {
|
||||||
@ -305,6 +307,13 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
}
|
}
|
||||||
return signal
|
return signal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getValidTransactionIds() -> [String] {
|
||||||
|
guard let data = getReceiptData(), let receipt = parseReceipt(data) else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return receipt.purchases.map { $0.transactionId }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension InAppPurchaseManager: SKProductsRequestDelegate {
|
extension InAppPurchaseManager: SKProductsRequestDelegate {
|
||||||
@ -370,6 +379,7 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
|||||||
productId: transaction.payment.productIdentifier,
|
productId: transaction.payment.productIdentifier,
|
||||||
content: PendingInAppPurchaseState(
|
content: PendingInAppPurchaseState(
|
||||||
productId: transaction.payment.productIdentifier,
|
productId: transaction.payment.productIdentifier,
|
||||||
|
isUpgrade: paymentContext.isUpgrade,
|
||||||
targetPeerId: paymentContext.targetPeerId
|
targetPeerId: paymentContext.targetPeerId
|
||||||
)
|
)
|
||||||
).start()
|
).start()
|
||||||
@ -431,7 +441,23 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
purpose = .single(.subscription)
|
let isUpgrade: Signal<Bool, NoError>
|
||||||
|
if let isUpgradeValue = paymentContexts[productIdentifier]?.isUpgrade {
|
||||||
|
isUpgrade = .single(isUpgradeValue)
|
||||||
|
} else {
|
||||||
|
isUpgrade = pendingInAppPurchaseState(engine: self.engine, productId: productIdentifier)
|
||||||
|
|> mapToSignal { state -> Signal<Bool, NoError> in
|
||||||
|
if let state = state {
|
||||||
|
return .single(state.isUpgrade)
|
||||||
|
} else {
|
||||||
|
return .single(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
purpose = isUpgrade
|
||||||
|
|> map { isUpgrade in
|
||||||
|
return isUpgrade ? .upgrade : .subscription
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let receiptData = getReceiptData() ?? Data()
|
let receiptData = getReceiptData() ?? Data()
|
||||||
@ -508,10 +534,12 @@ extension InAppPurchaseManager: SKPaymentTransactionObserver {
|
|||||||
|
|
||||||
private final class PendingInAppPurchaseState: Codable {
|
private final class PendingInAppPurchaseState: Codable {
|
||||||
public let productId: String
|
public let productId: String
|
||||||
|
public let isUpgrade: Bool
|
||||||
public let targetPeerId: PeerId?
|
public let targetPeerId: PeerId?
|
||||||
|
|
||||||
public init(productId: String, targetPeerId: PeerId?) {
|
public init(productId: String, isUpgrade: Bool, targetPeerId: PeerId?) {
|
||||||
self.productId = productId
|
self.productId = productId
|
||||||
|
self.isUpgrade = isUpgrade
|
||||||
self.targetPeerId = targetPeerId
|
self.targetPeerId = targetPeerId
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,6 +547,7 @@ private final class PendingInAppPurchaseState: Codable {
|
|||||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
self.productId = try container.decode(String.self, forKey: "productId")
|
self.productId = try container.decode(String.self, forKey: "productId")
|
||||||
|
self.isUpgrade = try container.decodeIfPresent(Bool.self, forKey: "isUpgrade") ?? false
|
||||||
self.targetPeerId = (try container.decodeIfPresent(Int64.self, forKey: "targetPeerId")).flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }
|
self.targetPeerId = (try container.decodeIfPresent(Int64.self, forKey: "targetPeerId")).flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,6 +555,7 @@ private final class PendingInAppPurchaseState: Codable {
|
|||||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||||
|
|
||||||
try container.encode(self.productId, forKey: "productId")
|
try container.encode(self.productId, forKey: "productId")
|
||||||
|
try container.encode(self.isUpgrade, forKey: "isUpgrade")
|
||||||
if let targetPeerId = self.targetPeerId {
|
if let targetPeerId = self.targetPeerId {
|
||||||
try container.encode(targetPeerId.id._internalGetInt64Value(), forKey: "targetPeerId")
|
try container.encode(targetPeerId.id._internalGetInt64Value(), forKey: "targetPeerId")
|
||||||
}
|
}
|
||||||
|
|||||||
230
submodules/InAppPurchaseManager/Sources/Receipt.swift
Normal file
230
submodules/InAppPurchaseManager/Sources/Receipt.swift
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
private struct Asn1Tag {
|
||||||
|
static let integer: Int32 = 0x02
|
||||||
|
static let octetString: Int32 = 0x04
|
||||||
|
static let objectIdentifier: Int32 = 0x06
|
||||||
|
static let sequence: Int32 = 0x10
|
||||||
|
static let set: Int32 = 0x11
|
||||||
|
static let utf8String: Int32 = 0x0c
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Asn1Entry {
|
||||||
|
let tag: Int32
|
||||||
|
let data: Data
|
||||||
|
let length: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
private func parse(_ data: Data, startIndex: Int = 0) -> Asn1Entry {
|
||||||
|
var index = startIndex
|
||||||
|
var value = data[index]
|
||||||
|
index += 1
|
||||||
|
var tagValue = Int32(value & 0x1f)
|
||||||
|
if tagValue == 31 {
|
||||||
|
value = data[index]
|
||||||
|
index += 1
|
||||||
|
while (value & 0x80) != 0 {
|
||||||
|
tagValue <<= 8
|
||||||
|
tagValue |= Int32(value & 0x7f)
|
||||||
|
value = data[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
tagValue <<= 8
|
||||||
|
tagValue |= Int32(value & 0x7f)
|
||||||
|
}
|
||||||
|
|
||||||
|
var length = 0
|
||||||
|
var nextTag = 0
|
||||||
|
value = data[index]
|
||||||
|
index += 1
|
||||||
|
if value & 0x80 == 0 {
|
||||||
|
length = Int(value)
|
||||||
|
nextTag = index + length
|
||||||
|
} else if value != 0x80 {
|
||||||
|
let octetsCount = Int(value & 0x7f)
|
||||||
|
for _ in 0 ..< octetsCount {
|
||||||
|
length <<= 8
|
||||||
|
value = data[index]
|
||||||
|
index += 1
|
||||||
|
length |= Int(value) & 0xff
|
||||||
|
}
|
||||||
|
nextTag = index + length
|
||||||
|
} else {
|
||||||
|
var scanIndex = index
|
||||||
|
while data[scanIndex] != 0 && data[scanIndex + 1] != 0 {
|
||||||
|
scanIndex += 1
|
||||||
|
}
|
||||||
|
length = scanIndex - index
|
||||||
|
nextTag = scanIndex + 2
|
||||||
|
}
|
||||||
|
return Asn1Entry(tag: tagValue, data: data.subdata(in: index ..< (index + length)), length: nextTag - startIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func parseSequence(_ data: Data) -> [Asn1Entry] {
|
||||||
|
var result : [Asn1Entry] = []
|
||||||
|
var index = 0
|
||||||
|
while index < data.count {
|
||||||
|
let entry = parse(data, startIndex: index)
|
||||||
|
result.append(entry)
|
||||||
|
index += entry.length
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private func parseInteger(_ data: Data) -> Int32 {
|
||||||
|
let length = data.count
|
||||||
|
var value: Int32 = 0
|
||||||
|
for i in 0 ..< length {
|
||||||
|
if i == 0 {
|
||||||
|
value = Int32(data[i] & 0x7f)
|
||||||
|
} else {
|
||||||
|
value <<= 8
|
||||||
|
value |= Int32(data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if length > 0 && data[0] & 0x80 != 0 {
|
||||||
|
let complement: Int32 = 1 << (length * 8)
|
||||||
|
value -= complement
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
private func parseObjectIdentifier(_ data: Data, startIndex: Int = 0, length: Int? = nil) -> [Int32] {
|
||||||
|
let dataLen = length ?? data.count
|
||||||
|
var index = startIndex
|
||||||
|
var identifier: [Int32] = []
|
||||||
|
while index < startIndex + dataLen {
|
||||||
|
var subidentifier: Int32 = 0
|
||||||
|
var value = data[index]
|
||||||
|
index += 1
|
||||||
|
while (value & 0x80) != 0 {
|
||||||
|
subidentifier <<= 7
|
||||||
|
subidentifier |= Int32(value & 0x7f)
|
||||||
|
value = data[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
subidentifier <<= 7
|
||||||
|
subidentifier |= Int32(value & 0x7f)
|
||||||
|
identifier.append(subidentifier)
|
||||||
|
}
|
||||||
|
return identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ObjectIdentifier {
|
||||||
|
static let pkcs7Data: [Int32] = [42, 840, 113549, 1, 7, 1]
|
||||||
|
static let pkcs7SignedData: [Int32] = [42, 840, 113549, 1, 7, 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Receipt {
|
||||||
|
fileprivate struct Tag {
|
||||||
|
static let purchases: Int32 = 17
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Purchase {
|
||||||
|
fileprivate struct Tag {
|
||||||
|
static let productIdentifier: Int32 = 1702
|
||||||
|
static let transactionIdentifier: Int32 = 1703
|
||||||
|
}
|
||||||
|
|
||||||
|
let productId: String
|
||||||
|
let transactionId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
let purchases: [Purchase]
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseReceipt(_ data: Data) -> Receipt? {
|
||||||
|
let root = parseSequence(data)
|
||||||
|
guard root.count == 1 && root[0].tag == Asn1Tag.sequence else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let rootSeq = parseSequence(root[0].data)
|
||||||
|
guard rootSeq.count == 2 && rootSeq[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(rootSeq[0].data) == ObjectIdentifier.pkcs7SignedData else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let signedData = parseSequence(rootSeq[1].data)
|
||||||
|
guard signedData.count == 1 && signedData[0].tag == Asn1Tag.sequence else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let signedDataSeq = parseSequence(signedData[0].data)
|
||||||
|
guard signedDataSeq.count > 3 && signedDataSeq[2].tag == Asn1Tag.sequence else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentData = parseSequence(signedDataSeq[2].data)
|
||||||
|
guard contentData.count == 2 && contentData[0].tag == Asn1Tag.objectIdentifier && parseObjectIdentifier(contentData[0].data) == ObjectIdentifier.pkcs7Data else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = parse(contentData[1].data)
|
||||||
|
guard payload.tag == Asn1Tag.octetString else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let payloadRoot = parse(payload.data)
|
||||||
|
guard payloadRoot.tag == Asn1Tag.set else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var purchases: [Receipt.Purchase] = []
|
||||||
|
|
||||||
|
let receiptAttributes = parseSequence(payloadRoot.data)
|
||||||
|
for attribute in receiptAttributes {
|
||||||
|
if attribute.tag != Asn1Tag.sequence { continue }
|
||||||
|
let attributeEntries = parseSequence(attribute.data)
|
||||||
|
guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = parseInteger(attributeEntries[0].data)
|
||||||
|
let value = attributeEntries[2].data
|
||||||
|
switch (type) {
|
||||||
|
case Receipt.Tag.purchases:
|
||||||
|
if let purchase = parsePurchaseAttributes(value) {
|
||||||
|
purchases.append(purchase)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Receipt(purchases: purchases)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func parsePurchaseAttributes(_ data: Data) -> Receipt.Purchase? {
|
||||||
|
let root = parse(data)
|
||||||
|
guard root.tag == Asn1Tag.set else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var productId: String?
|
||||||
|
var transactionId: String?
|
||||||
|
|
||||||
|
let receiptAttributes = parseSequence(root.data)
|
||||||
|
for attribute in receiptAttributes {
|
||||||
|
if attribute.tag != Asn1Tag.sequence { continue }
|
||||||
|
let attributeEntries = parseSequence(attribute.data)
|
||||||
|
guard attributeEntries.count == 3 && attributeEntries[0].tag == Asn1Tag.integer && attributeEntries[1].tag == Asn1Tag.integer && attributeEntries[2].tag == Asn1Tag.octetString else { return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = parseInteger(attributeEntries[0].data)
|
||||||
|
let value = attributeEntries[2].data
|
||||||
|
switch (type) {
|
||||||
|
case Receipt.Purchase.Tag.productIdentifier:
|
||||||
|
let valEntry = parse(value)
|
||||||
|
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
|
||||||
|
productId = String(bytes: valEntry.data, encoding: .utf8)
|
||||||
|
case Receipt.Purchase.Tag.transactionIdentifier:
|
||||||
|
let valEntry = parse(value)
|
||||||
|
guard valEntry.tag == Asn1Tag.utf8String else { return nil }
|
||||||
|
transactionId = String(bytes: valEntry.data, encoding: .utf8)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let productId, let transactionId else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Receipt.Purchase(productId: productId, transactionId: transactionId)
|
||||||
|
}
|
||||||
@ -26,7 +26,7 @@ typedef void (^TGMediaAvatarPresentImpl)(id<LegacyComponentsContext>, void (^)(U
|
|||||||
@property (nonatomic, copy) void (^requestSearchController)(TGMediaAssetsController *);
|
@property (nonatomic, copy) void (^requestSearchController)(TGMediaAssetsController *);
|
||||||
@property (nonatomic, copy) CGRect (^sourceRect)(void);
|
@property (nonatomic, copy) CGRect (^sourceRect)(void);
|
||||||
|
|
||||||
@property (nonatomic, copy) void (^requestAvatarEditor)(void (^)(UIImage *image, NSURL *asset, TGVideoEditAdjustments *adjustments, void(^commit)(void)));
|
@property (nonatomic, copy) void (^requestAvatarEditor)(void (^)(UIImage *image, void(^commit)(void)), void (^)(UIImage *image, NSURL *asset, TGVideoEditAdjustments *adjustments, void(^commit)(void)));
|
||||||
|
|
||||||
@property (nonatomic, strong) id<TGPhotoPaintStickersContext> stickersContext;
|
@property (nonatomic, strong) id<TGPhotoPaintStickersContext> stickersContext;
|
||||||
|
|
||||||
|
|||||||
@ -195,7 +195,7 @@
|
|||||||
|
|
||||||
if (!_signup) {
|
if (!_signup) {
|
||||||
TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SetEmoji") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SetEmoji") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
||||||
{
|
{
|
||||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||||
if (strongSelf == nil)
|
if (strongSelf == nil)
|
||||||
return;
|
return;
|
||||||
@ -205,8 +205,26 @@
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
[strongController dismissAnimated:true];
|
[strongController dismissAnimated:true];
|
||||||
if (strongSelf != nil)
|
if (strongSelf != nil) {
|
||||||
strongSelf.requestAvatarEditor(^(UIImage *image, NSURL *asset, TGVideoEditAdjustments *adjustments, void (^commit)(void)) {
|
strongSelf.requestAvatarEditor(^(UIImage *image, void (^commit)(void)) {
|
||||||
|
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||||
|
if (strongSelf == nil)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (strongSelf.willFinishWithImage != nil) {
|
||||||
|
strongSelf.willFinishWithImage(image, ^{
|
||||||
|
if (strongSelf.didFinishWithImage != nil)
|
||||||
|
strongSelf.didFinishWithImage(image);
|
||||||
|
|
||||||
|
commit();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (strongSelf.didFinishWithImage != nil)
|
||||||
|
strongSelf.didFinishWithImage(image);
|
||||||
|
|
||||||
|
commit();
|
||||||
|
}
|
||||||
|
}, ^(UIImage *image, NSURL *asset, TGVideoEditAdjustments *adjustments, void (^commit)(void)) {
|
||||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||||
if (strongSelf == nil)
|
if (strongSelf == nil)
|
||||||
return;
|
return;
|
||||||
@ -225,6 +243,7 @@
|
|||||||
commit();
|
commit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
[itemViews addObject:viewItem];
|
[itemViews addObject:viewItem];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,7 @@ public final class ListMessageItem: ListViewItem {
|
|||||||
let chatLocation: ChatLocation
|
let chatLocation: ChatLocation
|
||||||
let interaction: ListMessageItemInteraction
|
let interaction: ListMessageItemInteraction
|
||||||
let message: Message?
|
let message: Message?
|
||||||
|
let translateToLanguage: String?
|
||||||
public let selection: ChatHistoryMessageSelection
|
public let selection: ChatHistoryMessageSelection
|
||||||
let hintIsLink: Bool
|
let hintIsLink: Bool
|
||||||
let isGlobalSearchResult: Bool
|
let isGlobalSearchResult: Bool
|
||||||
@ -61,12 +62,13 @@ public final class ListMessageItem: ListViewItem {
|
|||||||
|
|
||||||
public let selectable: Bool = true
|
public let selectable: Bool = true
|
||||||
|
|
||||||
public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message?, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, isDownloadList: Bool = false, displayFileInfo: Bool = true, displayBackground: Bool = false, style: ItemListStyle = .plain) {
|
public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message?, translateToLanguage: String? = nil, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, isDownloadList: Bool = false, displayFileInfo: Bool = true, displayBackground: Bool = false, style: ItemListStyle = .plain) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.context = context
|
self.context = context
|
||||||
self.chatLocation = chatLocation
|
self.chatLocation = chatLocation
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
self.message = message
|
self.message = message
|
||||||
|
self.translateToLanguage = translateToLanguage
|
||||||
if let header = customHeader {
|
if let header = customHeader {
|
||||||
self.header = header
|
self.header = header
|
||||||
} else if displayHeader, let message = message {
|
} else if displayHeader, let message = message {
|
||||||
|
|||||||
@ -424,7 +424,12 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
|
|||||||
let (messageTextUrl, _) = parseUrl(url: message.text, wasConcealed: false)
|
let (messageTextUrl, _) = parseUrl(url: message.text, wasConcealed: false)
|
||||||
|
|
||||||
if messageTextUrl != rawUrlString, !item.isGlobalSearchResult {
|
if messageTextUrl != rawUrlString, !item.isGlobalSearchResult {
|
||||||
mutableDescriptionText.append(NSAttributedString(string: message.text + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
|
var messageText = message.text
|
||||||
|
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, !translation.text.isEmpty, item.translateToLanguage == translation.toLang {
|
||||||
|
messageText = translation.text
|
||||||
|
}
|
||||||
|
|
||||||
|
mutableDescriptionText.append(NSAttributedString(string: messageText + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
let urlAttributedString = NSMutableAttributedString()
|
let urlAttributedString = NSMutableAttributedString()
|
||||||
@ -439,8 +444,17 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
|
|||||||
}
|
}
|
||||||
break loop
|
break loop
|
||||||
case let .TextUrl(url):
|
case let .TextUrl(url):
|
||||||
|
var messageText = message.text
|
||||||
|
var entity = entity
|
||||||
|
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, !translation.text.isEmpty, item.translateToLanguage == translation.toLang {
|
||||||
|
messageText = translation.text
|
||||||
|
if entities.count == translation.entities.count, let index = entities.firstIndex(of: entity), index < translation.entities.count {
|
||||||
|
entity = translation.entities[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
|
var range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
|
||||||
let nsString = message.text as NSString
|
let nsString = messageText as NSString
|
||||||
if range.location + range.length > nsString.length {
|
if range.location + range.length > nsString.length {
|
||||||
range.location = max(0, nsString.length - range.length)
|
range.location = max(0, nsString.length - range.length)
|
||||||
range.length = nsString.length - range.location
|
range.length = nsString.length - range.location
|
||||||
@ -470,7 +484,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
|
|||||||
let (messageTextUrl, _) = parseUrl(url: message.text, wasConcealed: false)
|
let (messageTextUrl, _) = parseUrl(url: message.text, wasConcealed: false)
|
||||||
|
|
||||||
if messageTextUrl != rawUrlString, !item.isGlobalSearchResult {
|
if messageTextUrl != rawUrlString, !item.isGlobalSearchResult {
|
||||||
mutableDescriptionText.append(NSAttributedString(string: message.text + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
|
mutableDescriptionText.append(NSAttributedString(string: messageText + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
let urlAttributedString = NSMutableAttributedString()
|
let urlAttributedString = NSMutableAttributedString()
|
||||||
|
|||||||
@ -484,6 +484,14 @@ private struct PremiumProduct: Equatable {
|
|||||||
var pricePerMonth: String {
|
var pricePerMonth: String {
|
||||||
return self.storeProduct.pricePerMonth(Int(self.months))
|
return self.storeProduct.pricePerMonth(Int(self.months))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isCurrent: Bool {
|
||||||
|
return self.option.isCurrent
|
||||||
|
}
|
||||||
|
|
||||||
|
var transactionId: String? {
|
||||||
|
return self.option.transactionId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class PremiumOptionComponent: CombinedComponent {
|
final class PremiumOptionComponent: CombinedComponent {
|
||||||
@ -815,10 +823,12 @@ private final class CheckComponent: Component {
|
|||||||
final class SectionGroupComponent: Component {
|
final class SectionGroupComponent: Component {
|
||||||
public final class Item: Equatable {
|
public final class Item: Equatable {
|
||||||
public let content: AnyComponentWithIdentity<Empty>
|
public let content: AnyComponentWithIdentity<Empty>
|
||||||
|
public let isEnabled: Bool
|
||||||
public let action: () -> Void
|
public let action: () -> Void
|
||||||
|
|
||||||
public init(_ content: AnyComponentWithIdentity<Empty>, action: @escaping () -> Void) {
|
public init(_ content: AnyComponentWithIdentity<Empty>, isEnabled: Bool = true, action: @escaping () -> Void) {
|
||||||
self.content = content
|
self.content = content
|
||||||
|
self.isEnabled = isEnabled
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -826,7 +836,9 @@ final class SectionGroupComponent: Component {
|
|||||||
if lhs.content != rhs.content {
|
if lhs.content != rhs.content {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isEnabled != rhs.isEnabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -931,6 +943,8 @@ final class SectionGroupComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: size.width - sideInset, height: .greatestFiniteMagnitude)
|
containerSize: CGSize(width: size.width - sideInset, height: .greatestFiniteMagnitude)
|
||||||
)
|
)
|
||||||
|
buttonView.isEnabled = item.isEnabled
|
||||||
|
itemView.alpha = item.isEnabled ? 1.0 : 0.3
|
||||||
|
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: itemSize)
|
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: itemSize)
|
||||||
buttonView.frame = CGRect(origin: itemFrame.origin, size: CGSize(width: availableSize.width, height: itemSize.height + UIScreenPixel))
|
buttonView.frame = CGRect(origin: itemFrame.origin, size: CGSize(width: availableSize.width, height: itemSize.height + UIScreenPixel))
|
||||||
@ -1159,22 +1173,26 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let source: PremiumSource
|
let source: PremiumSource
|
||||||
let isPremium: Bool?
|
let isPremium: Bool?
|
||||||
|
let justBought: Bool
|
||||||
let otherPeerName: String?
|
let otherPeerName: String?
|
||||||
let products: [PremiumProduct]?
|
let products: [PremiumProduct]?
|
||||||
let selectedProductId: String?
|
let selectedProductId: String?
|
||||||
|
let validTransactionIds: [String]
|
||||||
let promoConfiguration: PremiumPromoConfiguration?
|
let promoConfiguration: PremiumPromoConfiguration?
|
||||||
let present: (ViewController) -> Void
|
let present: (ViewController) -> Void
|
||||||
let selectProduct: (String) -> Void
|
let selectProduct: (String) -> Void
|
||||||
let buy: () -> Void
|
let buy: () -> Void
|
||||||
let updateIsFocused: (Bool) -> Void
|
let updateIsFocused: (Bool) -> Void
|
||||||
|
|
||||||
init(context: AccountContext, source: PremiumSource, isPremium: Bool?, otherPeerName: String?, products: [PremiumProduct]?, selectedProductId: String?, promoConfiguration: PremiumPromoConfiguration?, present: @escaping (ViewController) -> Void, selectProduct: @escaping (String) -> Void, buy: @escaping () -> Void, updateIsFocused: @escaping (Bool) -> Void) {
|
init(context: AccountContext, source: PremiumSource, isPremium: Bool?, justBought: Bool, otherPeerName: String?, products: [PremiumProduct]?, selectedProductId: String?, validTransactionIds: [String], promoConfiguration: PremiumPromoConfiguration?, present: @escaping (ViewController) -> Void, selectProduct: @escaping (String) -> Void, buy: @escaping () -> Void, updateIsFocused: @escaping (Bool) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.source = source
|
self.source = source
|
||||||
self.isPremium = isPremium
|
self.isPremium = isPremium
|
||||||
|
self.justBought = justBought
|
||||||
self.otherPeerName = otherPeerName
|
self.otherPeerName = otherPeerName
|
||||||
self.products = products
|
self.products = products
|
||||||
self.selectedProductId = selectedProductId
|
self.selectedProductId = selectedProductId
|
||||||
|
self.validTransactionIds = validTransactionIds
|
||||||
self.promoConfiguration = promoConfiguration
|
self.promoConfiguration = promoConfiguration
|
||||||
self.present = present
|
self.present = present
|
||||||
self.selectProduct = selectProduct
|
self.selectProduct = selectProduct
|
||||||
@ -1192,6 +1210,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
if lhs.isPremium != rhs.isPremium {
|
if lhs.isPremium != rhs.isPremium {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.justBought != rhs.justBought {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.otherPeerName != rhs.otherPeerName {
|
if lhs.otherPeerName != rhs.otherPeerName {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1201,6 +1222,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
if lhs.selectedProductId != rhs.selectedProductId {
|
if lhs.selectedProductId != rhs.selectedProductId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.validTransactionIds != rhs.validTransactionIds {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.promoConfiguration != rhs.promoConfiguration {
|
if lhs.promoConfiguration != rhs.promoConfiguration {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -1213,6 +1237,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
|
|
||||||
var products: [PremiumProduct]?
|
var products: [PremiumProduct]?
|
||||||
var selectedProductId: String?
|
var selectedProductId: String?
|
||||||
|
var validTransactionIds: [String] = []
|
||||||
|
|
||||||
var isPremium: Bool?
|
var isPremium: Bool?
|
||||||
|
|
||||||
@ -1230,6 +1255,18 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
return self.products?.first(where: { $0.id == self.selectedProductId })?.id.hasSuffix(".annual") ?? false
|
return self.products?.first(where: { $0.id == self.selectedProductId })?.id.hasSuffix(".annual") ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var canUpgrade: Bool {
|
||||||
|
if let products = self.products, let current = products.first(where: { $0.isCurrent }), let transactionId = current.transactionId {
|
||||||
|
if self.validTransactionIds.contains(transactionId) {
|
||||||
|
return products.first(where: { $0.months > current.months }) != nil
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(context: AccountContext, source: PremiumSource) {
|
init(context: AccountContext, source: PremiumSource) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
|
||||||
@ -1317,6 +1354,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
let state = context.state
|
let state = context.state
|
||||||
state.products = context.component.products
|
state.products = context.component.products
|
||||||
state.selectedProductId = context.component.selectedProductId
|
state.selectedProductId = context.component.selectedProductId
|
||||||
|
state.validTransactionIds = context.component.validTransactionIds
|
||||||
state.isPremium = context.component.isPremium
|
state.isPremium = context.component.isPremium
|
||||||
|
|
||||||
let theme = environment.theme
|
let theme = environment.theme
|
||||||
@ -1378,7 +1416,11 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
textString = strings.Premium_PersonalDescription
|
textString = strings.Premium_PersonalDescription
|
||||||
}
|
}
|
||||||
} else if context.component.isPremium == true {
|
} else if context.component.isPremium == true {
|
||||||
textString = strings.Premium_SubscribedDescription
|
if !context.component.justBought, let products = state.products, let current = products.first(where: { $0.isCurrent }), current.months == 1 {
|
||||||
|
textString = strings.Premium_UpgradeDescription
|
||||||
|
} else {
|
||||||
|
textString = strings.Premium_SubscribedDescription
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
textString = strings.Premium_Description
|
textString = strings.Premium_Description
|
||||||
}
|
}
|
||||||
@ -1398,11 +1440,22 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
lineSpacing: 0.2
|
lineSpacing: 0.2
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
availableSize: CGSize(width: availableWidth - sideInsets, height: 240.0),
|
availableSize: CGSize(width: availableWidth - sideInsets - 8.0, height: 240.0),
|
||||||
transition: context.transition
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(text
|
context.add(text
|
||||||
.position(CGPoint(x: size.width / 2.0, y: size.height + text.size.height / 2.0))
|
.position(CGPoint(x: size.width / 2.0, y: size.height + text.size.height / 2.0))
|
||||||
|
// .update(Transition.Update { _, view, _ in
|
||||||
|
// if let snapshot = view.snapshotView(afterScreenUpdates: false) {
|
||||||
|
// let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||||
|
// view.superview?.addSubview(snapshot)
|
||||||
|
// transition.setAlpha(view: snapshot, alpha: 0.0, completion: { [weak snapshot] _ in
|
||||||
|
// snapshot?.removeFromSuperview()
|
||||||
|
// })
|
||||||
|
// snapshot.frame = view.frame
|
||||||
|
// transition.animateAlpha(view: view, from: 0.0, to: 1.0)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
)
|
)
|
||||||
size.height += text.size.height
|
size.height += text.size.height
|
||||||
size.height += 21.0
|
size.height += 21.0
|
||||||
@ -1430,9 +1483,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
let updateIsFocused = context.component.updateIsFocused
|
let updateIsFocused = context.component.updateIsFocused
|
||||||
|
|
||||||
let layoutOptions = {
|
let layoutOptions = {
|
||||||
if state.isPremium == true {
|
if let products = state.products, products.count > 1, state.isPremium == false || (!context.component.justBought && state.canUpgrade) {
|
||||||
|
|
||||||
} else if let products = state.products, products.count > 1 {
|
|
||||||
var optionsItems: [SectionGroupComponent.Item] = []
|
var optionsItems: [SectionGroupComponent.Item] = []
|
||||||
let gradientColors: [UIColor] = [
|
let gradientColors: [UIColor] = [
|
||||||
UIColor(rgb: 0x8e77ff),
|
UIColor(rgb: 0x8e77ff),
|
||||||
@ -1447,11 +1498,11 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
|
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentProductMonths = state.products?.first(where: { $0.isCurrent })?.months ?? 0
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
for product in products {
|
for product in products {
|
||||||
let giftTitle: String
|
let giftTitle: String
|
||||||
let months = product.months
|
|
||||||
|
|
||||||
if product.id.hasSuffix(".monthly") {
|
if product.id.hasSuffix(".monthly") {
|
||||||
giftTitle = strings.Premium_Monthly
|
giftTitle = strings.Premium_Monthly
|
||||||
} else if product.id.hasSuffix(".semiannual") {
|
} else if product.id.hasSuffix(".semiannual") {
|
||||||
@ -1459,8 +1510,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
} else {
|
} else {
|
||||||
giftTitle = strings.Premium_Annual
|
giftTitle = strings.Premium_Annual
|
||||||
}
|
}
|
||||||
|
|
||||||
let discountValue = Int((1.0 - Float(product.storeProduct.priceCurrencyAndAmount.amount) / Float(months) / Float(shortestOptionPrice.0)) * 100.0)
|
let fraction = Float(product.storeProduct.priceCurrencyAndAmount.amount) / Float(product.months) / Float(shortestOptionPrice.0)
|
||||||
|
let discountValue = Int(round((1.0 - fraction) * 20.0) * 5.0)
|
||||||
let discount: String
|
let discount: String
|
||||||
if discountValue > 0 {
|
if discountValue > 0 {
|
||||||
discount = "-\(discountValue)%"
|
discount = "-\(discountValue)%"
|
||||||
@ -1468,22 +1520,25 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
discount = ""
|
discount = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaultPrice = product.storeProduct.defaultPrice(shortestOptionPrice.1, monthsCount: Int(months))
|
let defaultPrice = product.storeProduct.defaultPrice(shortestOptionPrice.1, monthsCount: Int(product.months))
|
||||||
|
|
||||||
var subtitle = ""
|
var subtitle = ""
|
||||||
var pricePerMonth = product.price
|
var pricePerMonth = product.price
|
||||||
if months > 1 {
|
if product.months > 1 {
|
||||||
pricePerMonth = product.storeProduct.pricePerMonth(Int(months))
|
pricePerMonth = product.storeProduct.pricePerMonth(Int(product.months))
|
||||||
|
|
||||||
if discountValue > 0 {
|
if discountValue > 0 {
|
||||||
subtitle = "**\(defaultPrice)** \(product.price)"
|
subtitle = "**\(defaultPrice)** \(product.price)"
|
||||||
if months == 12 {
|
if product.months == 12 {
|
||||||
subtitle = environment.strings.Premium_PricePerYear(subtitle).string
|
subtitle = environment.strings.Premium_PricePerYear(subtitle).string
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
subtitle = product.price
|
subtitle = product.price
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if product.isCurrent {
|
||||||
|
subtitle = environment.strings.Premium_CurrentPlan
|
||||||
|
}
|
||||||
pricePerMonth = environment.strings.Premium_PricePerMonth(pricePerMonth).string
|
pricePerMonth = environment.strings.Premium_PricePerMonth(pricePerMonth).string
|
||||||
|
|
||||||
optionsItems.append(
|
optionsItems.append(
|
||||||
@ -1496,7 +1551,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
subtitle: subtitle,
|
subtitle: subtitle,
|
||||||
labelPrice: pricePerMonth,
|
labelPrice: pricePerMonth,
|
||||||
discount: discount,
|
discount: discount,
|
||||||
selected: product.id == state.selectedProductId,
|
selected: !product.isCurrent && product.id == state.selectedProductId,
|
||||||
primaryTextColor: textColor,
|
primaryTextColor: textColor,
|
||||||
secondaryTextColor: subtitleColor,
|
secondaryTextColor: subtitleColor,
|
||||||
accentColor: gradientColors[i],
|
accentColor: gradientColors[i],
|
||||||
@ -1505,6 +1560,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
isEnabled: product.months > currentProductMonths,
|
||||||
action: {
|
action: {
|
||||||
selectProduct(product.id)
|
selectProduct(product.id)
|
||||||
}
|
}
|
||||||
@ -1738,6 +1794,38 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let controller = environment.controller
|
||||||
|
let termsTapActionImpl: ([NSAttributedString.Key: Any]) -> Void = { attributes in
|
||||||
|
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
||||||
|
let controller = controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
|
||||||
|
if url.hasPrefix("https://apps.apple.com/account/subscriptions") {
|
||||||
|
controller.context.sharedContext.applicationBindings.openSubscriptions()
|
||||||
|
} else if url.hasPrefix("https://") || url.hasPrefix("tg://") {
|
||||||
|
controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://"), presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {})
|
||||||
|
} else {
|
||||||
|
let context = controller.context
|
||||||
|
let signal: Signal<ResolvedUrl, NoError>?
|
||||||
|
switch url {
|
||||||
|
case "terms":
|
||||||
|
signal = cachedTermsPage(context: context)
|
||||||
|
case "privacy":
|
||||||
|
signal = cachedPrivacyPage(context: context)
|
||||||
|
default:
|
||||||
|
signal = nil
|
||||||
|
}
|
||||||
|
if let signal = signal {
|
||||||
|
let _ = (signal
|
||||||
|
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
||||||
|
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in
|
||||||
|
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in
|
||||||
|
controller?.push(c)
|
||||||
|
}, dismissInput: {}, contentContext: nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let termsText = termsText.update(
|
let termsText = termsText.update(
|
||||||
component: MultilineTextComponent(
|
component: MultilineTextComponent(
|
||||||
text: termsString,
|
text: termsString,
|
||||||
@ -1752,35 +1840,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tapAction: { [weak environment] attributes, _ in
|
tapAction: { attributes, _ in
|
||||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
termsTapActionImpl(attributes)
|
||||||
let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
|
|
||||||
if url.hasPrefix("https://apps.apple.com/account/subscriptions") {
|
|
||||||
controller.context.sharedContext.applicationBindings.openSubscriptions()
|
|
||||||
} else if url.hasPrefix("https://") || url.hasPrefix("tg://") {
|
|
||||||
controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://"), presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {})
|
|
||||||
} else {
|
|
||||||
let context = controller.context
|
|
||||||
let signal: Signal<ResolvedUrl, NoError>?
|
|
||||||
switch url {
|
|
||||||
case "terms":
|
|
||||||
signal = cachedTermsPage(context: context)
|
|
||||||
case "privacy":
|
|
||||||
signal = cachedPrivacyPage(context: context)
|
|
||||||
default:
|
|
||||||
signal = nil
|
|
||||||
}
|
|
||||||
if let signal = signal {
|
|
||||||
let _ = (signal
|
|
||||||
|> deliverOnMainQueue).start(next: { resolvedUrl in
|
|
||||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in
|
|
||||||
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in
|
|
||||||
controller?.push(c)
|
|
||||||
}, dismissInput: {}, contentContext: nil)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -1903,9 +1964,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
private(set) var products: [PremiumProduct]?
|
private(set) var products: [PremiumProduct]?
|
||||||
private(set) var selectedProductId: String?
|
private(set) var selectedProductId: String?
|
||||||
|
fileprivate var validTransactionIds: [String] = []
|
||||||
|
|
||||||
var isPremium: Bool?
|
var isPremium: Bool?
|
||||||
var otherPeerName: String?
|
var otherPeerName: String?
|
||||||
|
var justBought = false
|
||||||
|
|
||||||
let animationCache: AnimationCache
|
let animationCache: AnimationCache
|
||||||
let animationRenderer: MultiAnimationRenderer
|
let animationRenderer: MultiAnimationRenderer
|
||||||
@ -1928,6 +1991,18 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
return self.products?.first(where: { $0.id == self.selectedProductId })?.id.hasSuffix(".annual") ?? false
|
return self.products?.first(where: { $0.id == self.selectedProductId })?.id.hasSuffix(".annual") ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var canUpgrade: Bool {
|
||||||
|
if let products = self.products, let current = products.first(where: { $0.isCurrent }), let transactionId = current.transactionId {
|
||||||
|
if self.validTransactionIds.contains(transactionId) {
|
||||||
|
return products.first(where: { $0.months > current.months }) != nil
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(context: AccountContext, source: PremiumSource, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
|
init(context: AccountContext, source: PremiumSource, updateInProgress: @escaping (Bool) -> Void, present: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updateInProgress = updateInProgress
|
self.updateInProgress = updateInProgress
|
||||||
@ -1939,6 +2014,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.validTransactionIds = context.inAppPurchaseManager?.getValidTransactionIds() ?? []
|
||||||
|
|
||||||
let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
|
let availableProducts: Signal<[InAppPurchaseManager.Product], NoError>
|
||||||
if let inAppPurchaseManager = context.inAppPurchaseManager {
|
if let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||||
availableProducts = inAppPurchaseManager.availableProducts
|
availableProducts = inAppPurchaseManager.availableProducts
|
||||||
@ -1995,7 +2072,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
strongSelf.otherPeerName = otherPeerName
|
strongSelf.otherPeerName = otherPeerName
|
||||||
|
|
||||||
if !hadProducts {
|
if !hadProducts {
|
||||||
strongSelf.selectedProductId = strongSelf.products?.last?.id
|
if let _ = products.first(where: { $0.isCurrent }) {
|
||||||
|
strongSelf.selectedProductId = strongSelf.products?.first?.id
|
||||||
|
} else {
|
||||||
|
strongSelf.selectedProductId = strongSelf.products?.last?.id
|
||||||
|
}
|
||||||
|
|
||||||
for (_, video) in promoConfiguration.videos {
|
for (_, video) in promoConfiguration.videos {
|
||||||
strongSelf.preloadDisposableSet.add(preloadVideoResource(postbox: context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: .standalone(resource: video.resource), duration: 3.0).start())
|
strongSelf.preloadDisposableSet.add(preloadVideoResource(postbox: context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: .standalone(resource: video.resource), duration: 3.0).start())
|
||||||
@ -2037,18 +2118,19 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
let premiumProduct = self.products?.first(where: { $0.id == self.selectedProductId }), !self.inProgress else {
|
let premiumProduct = self.products?.first(where: { $0.id == self.selectedProductId }), !self.inProgress else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
let isUpgrade = self.products?.first(where: { $0.isCurrent }) != nil
|
||||||
|
|
||||||
addAppLogEvent(postbox: self.context.account.postbox, type: "premium.promo_screen_accept")
|
addAppLogEvent(postbox: self.context.account.postbox, type: "premium.promo_screen_accept")
|
||||||
|
|
||||||
self.inProgress = true
|
self.inProgress = true
|
||||||
self.updateInProgress(true)
|
self.updateInProgress(true)
|
||||||
self.updated(transition: .immediate)
|
self.updated(transition: .immediate)
|
||||||
|
|
||||||
let _ = (self.context.engine.payments.canPurchasePremium(purpose: .subscription)
|
let _ = (self.context.engine.payments.canPurchasePremium(purpose: isUpgrade ? .upgrade : .subscription)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] available in
|
|> deliverOnMainQueue).start(next: { [weak self] available in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if available {
|
if available {
|
||||||
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct.storeProduct)
|
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(premiumProduct.storeProduct, isUpgrade: isUpgrade)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
if let strongSelf = self, case .purchased = status {
|
if let strongSelf = self, case .purchased = status {
|
||||||
strongSelf.activationDisposable.set((strongSelf.context.account.postbox.peerView(id: strongSelf.context.account.peerId)
|
strongSelf.activationDisposable.set((strongSelf.context.account.postbox.peerView(id: strongSelf.context.account.peerId)
|
||||||
@ -2085,6 +2167,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
strongSelf.updateInProgress(false)
|
strongSelf.updateInProgress(false)
|
||||||
|
|
||||||
strongSelf.isPremium = true
|
strongSelf.isPremium = true
|
||||||
|
strongSelf.justBought = true
|
||||||
|
|
||||||
strongSelf.updated(transition: .easeInOut(duration: 0.25))
|
strongSelf.updated(transition: .easeInOut(duration: 0.25))
|
||||||
strongSelf.completion()
|
strongSelf.completion()
|
||||||
}
|
}
|
||||||
@ -2226,7 +2310,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
} else if case .gift = context.component.source {
|
} else if case .gift = context.component.source {
|
||||||
titleString = environment.strings.Premium_GiftedTitle
|
titleString = environment.strings.Premium_GiftedTitle
|
||||||
} else if state.isPremium == true {
|
} else if state.isPremium == true {
|
||||||
titleString = environment.strings.Premium_SubscribedTitle
|
if !state.justBought && state.canUpgrade {
|
||||||
|
titleString = environment.strings.Premium_Title
|
||||||
|
} else {
|
||||||
|
titleString = environment.strings.Premium_SubscribedTitle
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
titleString = environment.strings.Premium_Title
|
titleString = environment.strings.Premium_Title
|
||||||
}
|
}
|
||||||
@ -2357,7 +2445,7 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let bottomPanelPadding: CGFloat = 12.0
|
let bottomPanelPadding: CGFloat = 12.0
|
||||||
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
|
let bottomInset: CGFloat = environment.safeInsets.bottom > 0.0 ? environment.safeInsets.bottom + 5.0 : bottomPanelPadding
|
||||||
let bottomPanelHeight: CGFloat = state.isPremium == true ? bottomInset : bottomPanelPadding + 50.0 + bottomInset
|
let bottomPanelHeight: CGFloat = state.isPremium == true && !state.canUpgrade ? bottomInset : bottomPanelPadding + 50.0 + bottomInset
|
||||||
|
|
||||||
let scrollContent = scrollContent.update(
|
let scrollContent = scrollContent.update(
|
||||||
component: ScrollComponent<EnvironmentType>(
|
component: ScrollComponent<EnvironmentType>(
|
||||||
@ -2365,9 +2453,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
context: context.component.context,
|
context: context.component.context,
|
||||||
source: context.component.source,
|
source: context.component.source,
|
||||||
isPremium: state.isPremium,
|
isPremium: state.isPremium,
|
||||||
|
justBought: state.justBought,
|
||||||
otherPeerName: state.otherPeerName,
|
otherPeerName: state.otherPeerName,
|
||||||
products: state.products,
|
products: state.products,
|
||||||
selectedProductId: state.selectedProductId,
|
selectedProductId: state.selectedProductId,
|
||||||
|
validTransactionIds: state.validTransactionIds,
|
||||||
promoConfiguration: state.promoConfiguration,
|
promoConfiguration: state.promoConfiguration,
|
||||||
present: context.component.present,
|
present: context.component.present,
|
||||||
selectProduct: { [weak state] productId in
|
selectProduct: { [weak state] productId in
|
||||||
@ -2454,6 +2544,17 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: max(topInset + 160.0 - titleOffset, environment.statusBarHeight + (environment.navigationHeight - environment.statusBarHeight) / 2.0)))
|
||||||
.scale(titleScale)
|
.scale(titleScale)
|
||||||
.opacity(titleAlpha)
|
.opacity(titleAlpha)
|
||||||
|
// .update(Transition.Update { _, view, _ in
|
||||||
|
// if let snapshot = view.snapshotView(afterScreenUpdates: false) {
|
||||||
|
// let transition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||||
|
// view.superview?.addSubview(snapshot)
|
||||||
|
// transition.setAlpha(view: snapshot, alpha: 0.0, completion: { [weak snapshot] _ in
|
||||||
|
// snapshot?.removeFromSuperview()
|
||||||
|
// })
|
||||||
|
// snapshot.frame = view.frame
|
||||||
|
// transition.animateAlpha(view: view, from: 0.0, to: titleAlpha)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
)
|
)
|
||||||
|
|
||||||
context.add(secondaryTitle
|
context.add(secondaryTitle
|
||||||
@ -2469,14 +2570,20 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.isPremium == true || isGiftView {
|
if (state.isPremium == true && (!state.canUpgrade || state.justBought)) || isGiftView {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
let buttonTitle: String
|
||||||
|
if state.isPremium == true && state.canUpgrade {
|
||||||
|
buttonTitle = state.isAnnual ? environment.strings.Premium_UpgradeForAnnual(state.price ?? "—").string : environment.strings.Premium_UpgradeFor(state.price ?? "—").string
|
||||||
|
} else {
|
||||||
|
buttonTitle = state.isAnnual ? environment.strings.Premium_SubscribeForAnnual(state.price ?? "—").string : environment.strings.Premium_SubscribeFor(state.price ?? "—").string
|
||||||
|
}
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
let button = button.update(
|
let button = button.update(
|
||||||
component: SolidRoundedButtonComponent(
|
component: SolidRoundedButtonComponent(
|
||||||
title: state.isAnnual ? environment.strings.Premium_SubscribeForAnnual(state.price ?? "—").string : environment.strings.Premium_SubscribeFor(state.price ?? "—").string,
|
title: buttonTitle,
|
||||||
theme: SolidRoundedButtonComponent.Theme(
|
theme: SolidRoundedButtonComponent.Theme(
|
||||||
backgroundColor: UIColor(rgb: 0x8878ff),
|
backgroundColor: UIColor(rgb: 0x8878ff),
|
||||||
backgroundColors: [
|
backgroundColors: [
|
||||||
|
|||||||
@ -222,8 +222,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
|||||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
|
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
|
||||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {})
|
||||||
})
|
|
||||||
|
|
||||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||||
|
|
||||||
|
|||||||
@ -843,9 +843,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
|||||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in
|
}, present: { _ in
|
||||||
}, openForumThread: { _, _ in },
|
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {})
|
||||||
openStorageManagement: {}, openPasswordSetup: {
|
|
||||||
})
|
|
||||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||||
|
|
||||||
func makeChatListItem(
|
func makeChatListItem(
|
||||||
|
|||||||
@ -367,8 +367,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in
|
}, present: { _ in
|
||||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {
|
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {} )
|
||||||
})
|
|
||||||
|
|
||||||
func makeChatListItem(
|
func makeChatListItem(
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
|
|||||||
@ -56,6 +56,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[2104224014] = { return Api.AttachMenuPeerType.parse_attachMenuPeerTypeSameBotPM($0) }
|
dict[2104224014] = { return Api.AttachMenuPeerType.parse_attachMenuPeerTypeSameBotPM($0) }
|
||||||
dict[-1392388579] = { return Api.Authorization.parse_authorization($0) }
|
dict[-1392388579] = { return Api.Authorization.parse_authorization($0) }
|
||||||
dict[-1896171181] = { return Api.AutoDownloadSettings.parse_autoDownloadSettings($0) }
|
dict[-1896171181] = { return Api.AutoDownloadSettings.parse_autoDownloadSettings($0) }
|
||||||
|
dict[-2124403385] = { return Api.AutoSaveException.parse_autoSaveException($0) }
|
||||||
|
dict[-934791986] = { return Api.AutoSaveSettings.parse_autoSaveSettings($0) }
|
||||||
dict[-1065882623] = { return Api.AvailableReaction.parse_availableReaction($0) }
|
dict[-1065882623] = { return Api.AvailableReaction.parse_availableReaction($0) }
|
||||||
dict[-177732982] = { return Api.BankCardOpenUrl.parse_bankCardOpenUrl($0) }
|
dict[-177732982] = { return Api.BankCardOpenUrl.parse_bankCardOpenUrl($0) }
|
||||||
dict[1527845466] = { return Api.BaseTheme.parse_baseThemeArctic($0) }
|
dict[1527845466] = { return Api.BaseTheme.parse_baseThemeArctic($0) }
|
||||||
@ -273,7 +275,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1536380829] = { return Api.InputChannel.parse_inputChannelFromMessage($0) }
|
dict[1536380829] = { return Api.InputChannel.parse_inputChannelFromMessage($0) }
|
||||||
dict[-1991004873] = { return Api.InputChatPhoto.parse_inputChatPhoto($0) }
|
dict[-1991004873] = { return Api.InputChatPhoto.parse_inputChatPhoto($0) }
|
||||||
dict[480546647] = { return Api.InputChatPhoto.parse_inputChatPhotoEmpty($0) }
|
dict[480546647] = { return Api.InputChatPhoto.parse_inputChatPhotoEmpty($0) }
|
||||||
dict[-968723890] = { return Api.InputChatPhoto.parse_inputChatUploadedPhoto($0) }
|
dict[-1110593856] = { return Api.InputChatPhoto.parse_inputChatUploadedPhoto($0) }
|
||||||
dict[-1736378792] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordEmpty($0) }
|
dict[-1736378792] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordEmpty($0) }
|
||||||
dict[-763367294] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordSRP($0) }
|
dict[-763367294] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordSRP($0) }
|
||||||
dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) }
|
dict[1968737087] = { return Api.InputClientProxy.parse_inputClientProxy($0) }
|
||||||
@ -777,6 +779,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[511092620] = { return Api.TopPeerCategory.parse_topPeerCategoryPhoneCalls($0) }
|
dict[511092620] = { return Api.TopPeerCategory.parse_topPeerCategoryPhoneCalls($0) }
|
||||||
dict[-75283823] = { return Api.TopPeerCategoryPeers.parse_topPeerCategoryPeers($0) }
|
dict[-75283823] = { return Api.TopPeerCategoryPeers.parse_topPeerCategoryPeers($0) }
|
||||||
dict[397910539] = { return Api.Update.parse_updateAttachMenuBots($0) }
|
dict[397910539] = { return Api.Update.parse_updateAttachMenuBots($0) }
|
||||||
|
dict[-335171433] = { return Api.Update.parse_updateAutoSaveSettings($0) }
|
||||||
dict[-1177566067] = { return Api.Update.parse_updateBotCallbackQuery($0) }
|
dict[-1177566067] = { return Api.Update.parse_updateBotCallbackQuery($0) }
|
||||||
dict[299870598] = { return Api.Update.parse_updateBotChatInviteRequester($0) }
|
dict[299870598] = { return Api.Update.parse_updateBotChatInviteRequester($0) }
|
||||||
dict[1299263278] = { return Api.Update.parse_updateBotCommands($0) }
|
dict[1299263278] = { return Api.Update.parse_updateBotCommands($0) }
|
||||||
@ -908,7 +911,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-496024847] = { return Api.UserStatus.parse_userStatusRecently($0) }
|
dict[-496024847] = { return Api.UserStatus.parse_userStatusRecently($0) }
|
||||||
dict[-1274595769] = { return Api.Username.parse_username($0) }
|
dict[-1274595769] = { return Api.Username.parse_username($0) }
|
||||||
dict[-567037804] = { return Api.VideoSize.parse_videoSize($0) }
|
dict[-567037804] = { return Api.VideoSize.parse_videoSize($0) }
|
||||||
dict[195933766] = { return Api.VideoSize.parse_videoSizeEmojiMarkup($0) }
|
dict[-128171716] = { return Api.VideoSize.parse_videoSizeEmojiMarkup($0) }
|
||||||
|
dict[228623102] = { return Api.VideoSize.parse_videoSizeStickerMarkup($0) }
|
||||||
dict[-1539849235] = { return Api.WallPaper.parse_wallPaper($0) }
|
dict[-1539849235] = { return Api.WallPaper.parse_wallPaper($0) }
|
||||||
dict[-528465642] = { return Api.WallPaper.parse_wallPaperNoFile($0) }
|
dict[-528465642] = { return Api.WallPaper.parse_wallPaperNoFile($0) }
|
||||||
dict[499236004] = { return Api.WallPaperSettings.parse_wallPaperSettings($0) }
|
dict[499236004] = { return Api.WallPaperSettings.parse_wallPaperSettings($0) }
|
||||||
@ -925,6 +929,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-1389486888] = { return Api.account.AuthorizationForm.parse_authorizationForm($0) }
|
dict[-1389486888] = { return Api.account.AuthorizationForm.parse_authorizationForm($0) }
|
||||||
dict[1275039392] = { return Api.account.Authorizations.parse_authorizations($0) }
|
dict[1275039392] = { return Api.account.Authorizations.parse_authorizations($0) }
|
||||||
dict[1674235686] = { return Api.account.AutoDownloadSettings.parse_autoDownloadSettings($0) }
|
dict[1674235686] = { return Api.account.AutoDownloadSettings.parse_autoDownloadSettings($0) }
|
||||||
|
dict[1279133341] = { return Api.account.AutoSaveSettings.parse_autoSaveSettings($0) }
|
||||||
dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) }
|
dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) }
|
||||||
dict[731303195] = { return Api.account.EmailVerified.parse_emailVerified($0) }
|
dict[731303195] = { return Api.account.EmailVerified.parse_emailVerified($0) }
|
||||||
dict[-507835039] = { return Api.account.EmailVerified.parse_emailVerifiedLogin($0) }
|
dict[-507835039] = { return Api.account.EmailVerified.parse_emailVerifiedLogin($0) }
|
||||||
@ -1202,6 +1207,10 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.AutoDownloadSettings:
|
case let _1 as Api.AutoDownloadSettings:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.AutoSaveException:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.AutoSaveSettings:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.AvailableReaction:
|
case let _1 as Api.AvailableReaction:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.BankCardOpenUrl:
|
case let _1 as Api.BankCardOpenUrl:
|
||||||
@ -1696,6 +1705,8 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.account.AutoDownloadSettings:
|
case let _1 as Api.account.AutoDownloadSettings:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.account.AutoSaveSettings:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.account.ContentSettings:
|
case let _1 as Api.account.ContentSettings:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.account.EmailVerified:
|
case let _1 as Api.account.EmailVerified:
|
||||||
|
|||||||
@ -522,6 +522,90 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api {
|
||||||
|
enum AutoSaveException: TypeConstructorDescription {
|
||||||
|
case autoSaveException(peer: Api.Peer, settings: Api.AutoSaveSettings)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .autoSaveException(let peer, let settings):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-2124403385)
|
||||||
|
}
|
||||||
|
peer.serialize(buffer, true)
|
||||||
|
settings.serialize(buffer, true)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .autoSaveException(let peer, let settings):
|
||||||
|
return ("autoSaveException", [("peer", peer as Any), ("settings", settings as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_autoSaveException(_ reader: BufferReader) -> AutoSaveException? {
|
||||||
|
var _1: Api.Peer?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||||
|
}
|
||||||
|
var _2: Api.AutoSaveSettings?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_2 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
if _c1 && _c2 {
|
||||||
|
return Api.AutoSaveException.autoSaveException(peer: _1!, settings: _2!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public extension Api {
|
||||||
|
enum AutoSaveSettings: TypeConstructorDescription {
|
||||||
|
case autoSaveSettings(flags: Int32, videoMaxSize: Int64?)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .autoSaveSettings(let flags, let videoMaxSize):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-934791986)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {serializeInt64(videoMaxSize!, buffer: buffer, boxed: false)}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .autoSaveSettings(let flags, let videoMaxSize):
|
||||||
|
return ("autoSaveSettings", [("flags", flags as Any), ("videoMaxSize", videoMaxSize as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_autoSaveSettings(_ reader: BufferReader) -> AutoSaveSettings? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Int64?
|
||||||
|
if Int(_1!) & Int(1 << 2) != 0 {_2 = reader.readInt64() }
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = (Int(_1!) & Int(1 << 2) == 0) || _2 != nil
|
||||||
|
if _c1 && _c2 {
|
||||||
|
return Api.AutoSaveSettings.autoSaveSettings(flags: _1!, videoMaxSize: _2)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum AvailableReaction: TypeConstructorDescription {
|
enum AvailableReaction: TypeConstructorDescription {
|
||||||
case availableReaction(flags: Int32, reaction: String, title: String, staticIcon: Api.Document, appearAnimation: Api.Document, selectAnimation: Api.Document, activateAnimation: Api.Document, effectAnimation: Api.Document, aroundAnimation: Api.Document?, centerIcon: Api.Document?)
|
case availableReaction(flags: Int32, reaction: String, title: String, staticIcon: Api.Document, appearAnimation: Api.Document, selectAnimation: Api.Document, activateAnimation: Api.Document, effectAnimation: Api.Document, aroundAnimation: Api.Document?, centerIcon: Api.Document?)
|
||||||
@ -940,75 +1024,3 @@ public extension Api {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api {
|
|
||||||
enum BotInfo: TypeConstructorDescription {
|
|
||||||
case botInfo(flags: Int32, userId: Int64?, description: String?, descriptionPhoto: Api.Photo?, descriptionDocument: Api.Document?, commands: [Api.BotCommand]?, menuButton: Api.BotMenuButton?)
|
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
|
||||||
switch self {
|
|
||||||
case .botInfo(let flags, let userId, let description, let descriptionPhoto, let descriptionDocument, let commands, let menuButton):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(-1892676777)
|
|
||||||
}
|
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(userId!, buffer: buffer, boxed: false)}
|
|
||||||
if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)}
|
|
||||||
if Int(flags) & Int(1 << 4) != 0 {descriptionPhoto!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 5) != 0 {descriptionDocument!.serialize(buffer, true)}
|
|
||||||
if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(commands!.count))
|
|
||||||
for item in commands! {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}}
|
|
||||||
if Int(flags) & Int(1 << 3) != 0 {menuButton!.serialize(buffer, true)}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
|
||||||
switch self {
|
|
||||||
case .botInfo(let flags, let userId, let description, let descriptionPhoto, let descriptionDocument, let commands, let menuButton):
|
|
||||||
return ("botInfo", [("flags", flags as Any), ("userId", userId as Any), ("description", description as Any), ("descriptionPhoto", descriptionPhoto as Any), ("descriptionDocument", descriptionDocument as Any), ("commands", commands as Any), ("menuButton", menuButton as Any)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? {
|
|
||||||
var _1: Int32?
|
|
||||||
_1 = reader.readInt32()
|
|
||||||
var _2: Int64?
|
|
||||||
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt64() }
|
|
||||||
var _3: String?
|
|
||||||
if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) }
|
|
||||||
var _4: Api.Photo?
|
|
||||||
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
|
|
||||||
_4 = Api.parse(reader, signature: signature) as? Api.Photo
|
|
||||||
} }
|
|
||||||
var _5: Api.Document?
|
|
||||||
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
|
|
||||||
_5 = Api.parse(reader, signature: signature) as? Api.Document
|
|
||||||
} }
|
|
||||||
var _6: [Api.BotCommand]?
|
|
||||||
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
|
|
||||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self)
|
|
||||||
} }
|
|
||||||
var _7: Api.BotMenuButton?
|
|
||||||
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
|
||||||
_7 = Api.parse(reader, signature: signature) as? Api.BotMenuButton
|
|
||||||
} }
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
|
||||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
|
||||||
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
|
|
||||||
let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil
|
|
||||||
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
|
|
||||||
let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil
|
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
|
||||||
return Api.BotInfo.botInfo(flags: _1!, userId: _2, description: _3, descriptionPhoto: _4, descriptionDocument: _5, commands: _6, menuButton: _7)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,3 +1,75 @@
|
|||||||
|
public extension Api {
|
||||||
|
enum BotInfo: TypeConstructorDescription {
|
||||||
|
case botInfo(flags: Int32, userId: Int64?, description: String?, descriptionPhoto: Api.Photo?, descriptionDocument: Api.Document?, commands: [Api.BotCommand]?, menuButton: Api.BotMenuButton?)
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .botInfo(let flags, let userId, let description, let descriptionPhoto, let descriptionDocument, let commands, let menuButton):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-1892676777)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(userId!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 1) != 0 {serializeString(description!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 4) != 0 {descriptionPhoto!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 5) != 0 {descriptionDocument!.serialize(buffer, true)}
|
||||||
|
if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(commands!.count))
|
||||||
|
for item in commands! {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}}
|
||||||
|
if Int(flags) & Int(1 << 3) != 0 {menuButton!.serialize(buffer, true)}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .botInfo(let flags, let userId, let description, let descriptionPhoto, let descriptionDocument, let commands, let menuButton):
|
||||||
|
return ("botInfo", [("flags", flags as Any), ("userId", userId as Any), ("description", description as Any), ("descriptionPhoto", descriptionPhoto as Any), ("descriptionDocument", descriptionDocument as Any), ("commands", commands as Any), ("menuButton", menuButton as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_botInfo(_ reader: BufferReader) -> BotInfo? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: Int64?
|
||||||
|
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt64() }
|
||||||
|
var _3: String?
|
||||||
|
if Int(_1!) & Int(1 << 1) != 0 {_3 = parseString(reader) }
|
||||||
|
var _4: Api.Photo?
|
||||||
|
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_4 = Api.parse(reader, signature: signature) as? Api.Photo
|
||||||
|
} }
|
||||||
|
var _5: Api.Document?
|
||||||
|
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_5 = Api.parse(reader, signature: signature) as? Api.Document
|
||||||
|
} }
|
||||||
|
var _6: [Api.BotCommand]?
|
||||||
|
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
|
||||||
|
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.BotCommand.self)
|
||||||
|
} }
|
||||||
|
var _7: Api.BotMenuButton?
|
||||||
|
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_7 = Api.parse(reader, signature: signature) as? Api.BotMenuButton
|
||||||
|
} }
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||||
|
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||||
|
let _c4 = (Int(_1!) & Int(1 << 4) == 0) || _4 != nil
|
||||||
|
let _c5 = (Int(_1!) & Int(1 << 5) == 0) || _5 != nil
|
||||||
|
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
|
||||||
|
let _c7 = (Int(_1!) & Int(1 << 3) == 0) || _7 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||||
|
return Api.BotInfo.botInfo(flags: _1!, userId: _2, description: _3, descriptionPhoto: _4, descriptionDocument: _5, commands: _6, menuButton: _7)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum BotInlineMessage: TypeConstructorDescription {
|
enum BotInlineMessage: TypeConstructorDescription {
|
||||||
case botInlineMessageMediaAuto(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?)
|
case botInlineMessageMediaAuto(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?)
|
||||||
|
|||||||
@ -723,6 +723,7 @@ public extension Api {
|
|||||||
public extension Api {
|
public extension Api {
|
||||||
indirect enum Update: TypeConstructorDescription {
|
indirect enum Update: TypeConstructorDescription {
|
||||||
case updateAttachMenuBots
|
case updateAttachMenuBots
|
||||||
|
case updateAutoSaveSettings
|
||||||
case updateBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?)
|
case updateBotCallbackQuery(flags: Int32, queryId: Int64, userId: Int64, peer: Api.Peer, msgId: Int32, chatInstance: Int64, data: Buffer?, gameShortName: String?)
|
||||||
case updateBotChatInviteRequester(peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32)
|
case updateBotChatInviteRequester(peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32)
|
||||||
case updateBotCommands(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand])
|
case updateBotCommands(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand])
|
||||||
@ -839,6 +840,12 @@ public extension Api {
|
|||||||
buffer.appendInt32(397910539)
|
buffer.appendInt32(397910539)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
case .updateAutoSaveSettings:
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-335171433)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName):
|
case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName):
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -1785,6 +1792,8 @@ public extension Api {
|
|||||||
switch self {
|
switch self {
|
||||||
case .updateAttachMenuBots:
|
case .updateAttachMenuBots:
|
||||||
return ("updateAttachMenuBots", [])
|
return ("updateAttachMenuBots", [])
|
||||||
|
case .updateAutoSaveSettings:
|
||||||
|
return ("updateAutoSaveSettings", [])
|
||||||
case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName):
|
case .updateBotCallbackQuery(let flags, let queryId, let userId, let peer, let msgId, let chatInstance, let data, let gameShortName):
|
||||||
return ("updateBotCallbackQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("peer", peer as Any), ("msgId", msgId as Any), ("chatInstance", chatInstance as Any), ("data", data as Any), ("gameShortName", gameShortName as Any)])
|
return ("updateBotCallbackQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("peer", peer as Any), ("msgId", msgId as Any), ("chatInstance", chatInstance as Any), ("data", data as Any), ("gameShortName", gameShortName as Any)])
|
||||||
case .updateBotChatInviteRequester(let peer, let date, let userId, let about, let invite, let qts):
|
case .updateBotChatInviteRequester(let peer, let date, let userId, let about, let invite, let qts):
|
||||||
@ -2007,6 +2016,9 @@ public extension Api {
|
|||||||
public static func parse_updateAttachMenuBots(_ reader: BufferReader) -> Update? {
|
public static func parse_updateAttachMenuBots(_ reader: BufferReader) -> Update? {
|
||||||
return Api.Update.updateAttachMenuBots
|
return Api.Update.updateAttachMenuBots
|
||||||
}
|
}
|
||||||
|
public static func parse_updateAutoSaveSettings(_ reader: BufferReader) -> Update? {
|
||||||
|
return Api.Update.updateAutoSaveSettings
|
||||||
|
}
|
||||||
public static func parse_updateBotCallbackQuery(_ reader: BufferReader) -> Update? {
|
public static func parse_updateBotCallbackQuery(_ reader: BufferReader) -> Update? {
|
||||||
var _1: Int32?
|
var _1: Int32?
|
||||||
_1 = reader.readInt32()
|
_1 = reader.readInt32()
|
||||||
|
|||||||
@ -917,7 +917,8 @@ public extension Api {
|
|||||||
public extension Api {
|
public extension Api {
|
||||||
enum VideoSize: TypeConstructorDescription {
|
enum VideoSize: TypeConstructorDescription {
|
||||||
case videoSize(flags: Int32, type: String, w: Int32, h: Int32, size: Int32, videoStartTs: Double?)
|
case videoSize(flags: Int32, type: String, w: Int32, h: Int32, size: Int32, videoStartTs: Double?)
|
||||||
case videoSizeEmojiMarkup(type: String, emojiId: Int64, backgroundColors: [Int32])
|
case videoSizeEmojiMarkup(emojiId: Int64, backgroundColors: [Int32])
|
||||||
|
case videoSizeStickerMarkup(stickerset: Api.InputStickerSet, stickerId: Int64, backgroundColors: [Int32])
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -932,11 +933,10 @@ public extension Api {
|
|||||||
serializeInt32(size, buffer: buffer, boxed: false)
|
serializeInt32(size, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 0) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
case .videoSizeEmojiMarkup(let type, let emojiId, let backgroundColors):
|
case .videoSizeEmojiMarkup(let emojiId, let backgroundColors):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(195933766)
|
buffer.appendInt32(-128171716)
|
||||||
}
|
}
|
||||||
serializeString(type, buffer: buffer, boxed: false)
|
|
||||||
serializeInt64(emojiId, buffer: buffer, boxed: false)
|
serializeInt64(emojiId, buffer: buffer, boxed: false)
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
buffer.appendInt32(Int32(backgroundColors.count))
|
buffer.appendInt32(Int32(backgroundColors.count))
|
||||||
@ -944,6 +944,18 @@ public extension Api {
|
|||||||
serializeInt32(item, buffer: buffer, boxed: false)
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case .videoSizeStickerMarkup(let stickerset, let stickerId, let backgroundColors):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(228623102)
|
||||||
|
}
|
||||||
|
stickerset.serialize(buffer, true)
|
||||||
|
serializeInt64(stickerId, buffer: buffer, boxed: false)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(backgroundColors.count))
|
||||||
|
for item in backgroundColors {
|
||||||
|
serializeInt32(item, buffer: buffer, boxed: false)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -951,8 +963,10 @@ public extension Api {
|
|||||||
switch self {
|
switch self {
|
||||||
case .videoSize(let flags, let type, let w, let h, let size, let videoStartTs):
|
case .videoSize(let flags, let type, let w, let h, let size, let videoStartTs):
|
||||||
return ("videoSize", [("flags", flags as Any), ("type", type as Any), ("w", w as Any), ("h", h as Any), ("size", size as Any), ("videoStartTs", videoStartTs as Any)])
|
return ("videoSize", [("flags", flags as Any), ("type", type as Any), ("w", w as Any), ("h", h as Any), ("size", size as Any), ("videoStartTs", videoStartTs as Any)])
|
||||||
case .videoSizeEmojiMarkup(let type, let emojiId, let backgroundColors):
|
case .videoSizeEmojiMarkup(let emojiId, let backgroundColors):
|
||||||
return ("videoSizeEmojiMarkup", [("type", type as Any), ("emojiId", emojiId as Any), ("backgroundColors", backgroundColors as Any)])
|
return ("videoSizeEmojiMarkup", [("emojiId", emojiId as Any), ("backgroundColors", backgroundColors as Any)])
|
||||||
|
case .videoSizeStickerMarkup(let stickerset, let stickerId, let backgroundColors):
|
||||||
|
return ("videoSizeStickerMarkup", [("stickerset", stickerset as Any), ("stickerId", stickerId as Any), ("backgroundColors", backgroundColors as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -983,8 +997,26 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static func parse_videoSizeEmojiMarkup(_ reader: BufferReader) -> VideoSize? {
|
public static func parse_videoSizeEmojiMarkup(_ reader: BufferReader) -> VideoSize? {
|
||||||
var _1: String?
|
var _1: Int64?
|
||||||
_1 = parseString(reader)
|
_1 = reader.readInt64()
|
||||||
|
var _2: [Int32]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
if _c1 && _c2 {
|
||||||
|
return Api.VideoSize.videoSizeEmojiMarkup(emojiId: _1!, backgroundColors: _2!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static func parse_videoSizeStickerMarkup(_ reader: BufferReader) -> VideoSize? {
|
||||||
|
var _1: Api.InputStickerSet?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.InputStickerSet
|
||||||
|
}
|
||||||
var _2: Int64?
|
var _2: Int64?
|
||||||
_2 = reader.readInt64()
|
_2 = reader.readInt64()
|
||||||
var _3: [Int32]?
|
var _3: [Int32]?
|
||||||
@ -995,7 +1027,7 @@ public extension Api {
|
|||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
if _c1 && _c2 && _c3 {
|
if _c1 && _c2 && _c3 {
|
||||||
return Api.VideoSize.videoSizeEmojiMarkup(type: _1!, emojiId: _2!, backgroundColors: _3!)
|
return Api.VideoSize.videoSizeStickerMarkup(stickerset: _1!, stickerId: _2!, backgroundColors: _3!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -308,6 +308,86 @@ public extension Api.account {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.account {
|
||||||
|
enum AutoSaveSettings: TypeConstructorDescription {
|
||||||
|
case autoSaveSettings(usersSettings: Api.AutoSaveSettings, chatsSettings: Api.AutoSaveSettings, broadcastsSettings: Api.AutoSaveSettings, exceptions: [Api.AutoSaveException], chats: [Api.Chat], users: [Api.User])
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .autoSaveSettings(let usersSettings, let chatsSettings, let broadcastsSettings, let exceptions, let chats, let users):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(1279133341)
|
||||||
|
}
|
||||||
|
usersSettings.serialize(buffer, true)
|
||||||
|
chatsSettings.serialize(buffer, true)
|
||||||
|
broadcastsSettings.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(exceptions.count))
|
||||||
|
for item in exceptions {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(chats.count))
|
||||||
|
for item in chats {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(users.count))
|
||||||
|
for item in users {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .autoSaveSettings(let usersSettings, let chatsSettings, let broadcastsSettings, let exceptions, let chats, let users):
|
||||||
|
return ("autoSaveSettings", [("usersSettings", usersSettings as Any), ("chatsSettings", chatsSettings as Any), ("broadcastsSettings", broadcastsSettings as Any), ("exceptions", exceptions as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_autoSaveSettings(_ reader: BufferReader) -> AutoSaveSettings? {
|
||||||
|
var _1: Api.AutoSaveSettings?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_1 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings
|
||||||
|
}
|
||||||
|
var _2: Api.AutoSaveSettings?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_2 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings
|
||||||
|
}
|
||||||
|
var _3: Api.AutoSaveSettings?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
_3 = Api.parse(reader, signature: signature) as? Api.AutoSaveSettings
|
||||||
|
}
|
||||||
|
var _4: [Api.AutoSaveException]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.AutoSaveException.self)
|
||||||
|
}
|
||||||
|
var _5: [Api.Chat]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||||
|
}
|
||||||
|
var _6: [Api.User]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
let _c4 = _4 != nil
|
||||||
|
let _c5 = _5 != nil
|
||||||
|
let _c6 = _6 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||||
|
return Api.account.AutoSaveSettings.autoSaveSettings(usersSettings: _1!, chatsSettings: _2!, broadcastsSettings: _3!, exceptions: _4!, chats: _5!, users: _6!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.account {
|
public extension Api.account {
|
||||||
enum ContentSettings: TypeConstructorDescription {
|
enum ContentSettings: TypeConstructorDescription {
|
||||||
case contentSettings(flags: Int32)
|
case contentSettings(flags: Int32)
|
||||||
|
|||||||
@ -187,6 +187,21 @@ public extension Api.functions.account {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.account {
|
||||||
|
static func deleteAutoSaveExceptions() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(1404829728)
|
||||||
|
|
||||||
|
return (FunctionDescription(name: "account.deleteAutoSaveExceptions", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Bool?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.account {
|
public extension Api.functions.account {
|
||||||
static func deleteSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
static func deleteSecureValue(types: [Api.SecureValueType]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
@ -298,6 +313,21 @@ public extension Api.functions.account {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.account {
|
||||||
|
static func getAutoSaveSettings() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.AutoSaveSettings>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-1379156774)
|
||||||
|
|
||||||
|
return (FunctionDescription(name: "account.getAutoSaveSettings", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.account.AutoSaveSettings? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.account.AutoSaveSettings?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.account.AutoSaveSettings
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.account {
|
public extension Api.functions.account {
|
||||||
static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.Themes>) {
|
static func getChatThemes(hash: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.Themes>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
@ -889,6 +919,23 @@ public extension Api.functions.account {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.account {
|
||||||
|
static func saveAutoSaveSettings(flags: Int32, peer: Api.InputPeer?, settings: Api.AutoSaveSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(-694451359)
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 3) != 0 {peer!.serialize(buffer, true)}
|
||||||
|
settings.serialize(buffer, true)
|
||||||
|
return (FunctionDescription(name: "account.saveAutoSaveSettings", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Bool?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.account {
|
public extension Api.functions.account {
|
||||||
static func saveRingtone(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.SavedRingtone>) {
|
static func saveRingtone(id: Api.InputDocument, unsave: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.account.SavedRingtone>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
|||||||
@ -272,7 +272,7 @@ public extension Api {
|
|||||||
enum InputChatPhoto: TypeConstructorDescription {
|
enum InputChatPhoto: TypeConstructorDescription {
|
||||||
case inputChatPhoto(id: Api.InputPhoto)
|
case inputChatPhoto(id: Api.InputPhoto)
|
||||||
case inputChatPhotoEmpty
|
case inputChatPhotoEmpty
|
||||||
case inputChatUploadedPhoto(flags: Int32, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?)
|
case inputChatUploadedPhoto(flags: Int32, file: Api.InputFile?, video: Api.InputFile?, videoStartTs: Double?, videoEmojiMarkup: Api.VideoSize?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -288,14 +288,15 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
case .inputChatUploadedPhoto(let flags, let file, let video, let videoStartTs):
|
case .inputChatUploadedPhoto(let flags, let file, let video, let videoStartTs, let videoEmojiMarkup):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-968723890)
|
buffer.appendInt32(-1110593856)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 0) != 0 {file!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 1) != 0 {video!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 2) != 0 {serializeDouble(videoStartTs!, buffer: buffer, boxed: false)}
|
||||||
|
if Int(flags) & Int(1 << 3) != 0 {videoEmojiMarkup!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -306,8 +307,8 @@ public extension Api {
|
|||||||
return ("inputChatPhoto", [("id", id as Any)])
|
return ("inputChatPhoto", [("id", id as Any)])
|
||||||
case .inputChatPhotoEmpty:
|
case .inputChatPhotoEmpty:
|
||||||
return ("inputChatPhotoEmpty", [])
|
return ("inputChatPhotoEmpty", [])
|
||||||
case .inputChatUploadedPhoto(let flags, let file, let video, let videoStartTs):
|
case .inputChatUploadedPhoto(let flags, let file, let video, let videoStartTs, let videoEmojiMarkup):
|
||||||
return ("inputChatUploadedPhoto", [("flags", flags as Any), ("file", file as Any), ("video", video as Any), ("videoStartTs", videoStartTs as Any)])
|
return ("inputChatUploadedPhoto", [("flags", flags as Any), ("file", file as Any), ("video", video as Any), ("videoStartTs", videoStartTs as Any), ("videoEmojiMarkup", videoEmojiMarkup as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,12 +341,17 @@ public extension Api {
|
|||||||
} }
|
} }
|
||||||
var _4: Double?
|
var _4: Double?
|
||||||
if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readDouble() }
|
if Int(_1!) & Int(1 << 2) != 0 {_4 = reader.readDouble() }
|
||||||
|
var _5: Api.VideoSize?
|
||||||
|
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_5 = Api.parse(reader, signature: signature) as? Api.VideoSize
|
||||||
|
} }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||||
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 {
|
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
||||||
return Api.InputChatPhoto.inputChatUploadedPhoto(flags: _1!, file: _2, video: _3, videoStartTs: _4)
|
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||||
|
return Api.InputChatPhoto.inputChatUploadedPhoto(flags: _1!, file: _2, video: _3, videoStartTs: _4, videoEmojiMarkup: _5)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -172,7 +172,7 @@ func telegramMediaFileFromApiDocument(_ document: Api.Document) -> TelegramMedia
|
|||||||
videoThumbnails.append(TelegramMediaFile.VideoThumbnail(
|
videoThumbnails.append(TelegramMediaFile.VideoThumbnail(
|
||||||
dimensions: PixelDimensions(width: w, height: h),
|
dimensions: PixelDimensions(width: w, height: h),
|
||||||
resource: resource))
|
resource: resource))
|
||||||
case .videoSizeEmojiMarkup:
|
case .videoSizeEmojiMarkup, .videoSizeStickerMarkup:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,8 +50,10 @@ func telegramMediaImageFromApiPhoto(_ photo: Api.Photo) -> TelegramMediaImage? {
|
|||||||
resource = CloudPhotoSizeMediaResource(datacenterId: dcId, photoId: id, accessHash: accessHash, sizeSpec: type, size: Int64(size), fileReference: fileReference.makeData())
|
resource = CloudPhotoSizeMediaResource(datacenterId: dcId, photoId: id, accessHash: accessHash, sizeSpec: type, size: Int64(size), fileReference: fileReference.makeData())
|
||||||
|
|
||||||
videoRepresentations.append(TelegramMediaImage.VideoRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, startTimestamp: videoStartTs))
|
videoRepresentations.append(TelegramMediaImage.VideoRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, startTimestamp: videoStartTs))
|
||||||
case let .videoSizeEmojiMarkup(_, emojiId, backgroundColors):
|
case let .videoSizeEmojiMarkup(emojiId, backgroundColors):
|
||||||
emojiMarkup = TelegramMediaImage.EmojiMarkup(fileId: emojiId, backgroundColors: backgroundColors)
|
emojiMarkup = TelegramMediaImage.EmojiMarkup(fileId: emojiId, backgroundColors: backgroundColors)
|
||||||
|
case .videoSizeStickerMarkup:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,7 +79,6 @@ private extension PremiumPromoConfiguration {
|
|||||||
|
|
||||||
var productOptions: [PremiumProductOption] = []
|
var productOptions: [PremiumProductOption] = []
|
||||||
for option in options {
|
for option in options {
|
||||||
|
|
||||||
if case let .premiumSubscriptionOption(flags, transaction, months, currency, amount, botUrl, storeProduct) = option {
|
if case let .premiumSubscriptionOption(flags, transaction, months, currency, amount, botUrl, storeProduct) = option {
|
||||||
productOptions.append(PremiumProductOption(isCurrent: (flags & (1 << 1)) != 0, months: months, currency: currency, amount: amount, botUrl: botUrl, transactionId: transaction, availableForUpgrade: (flags & (1 << 2)) != 0, storeProductId: storeProduct))
|
productOptions.append(PremiumProductOption(isCurrent: (flags & (1 << 1)) != 0, months: months, currency: currency, amount: amount, botUrl: botUrl, transactionId: transaction, availableForUpgrade: (flags & (1 << 2)) != 0, storeProductId: storeProduct))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ public enum ServerProvidedSuggestion: String {
|
|||||||
case validatePhoneNumber = "VALIDATE_PHONE_NUMBER"
|
case validatePhoneNumber = "VALIDATE_PHONE_NUMBER"
|
||||||
case validatePassword = "VALIDATE_PASSWORD"
|
case validatePassword = "VALIDATE_PASSWORD"
|
||||||
case setupPassword = "SETUP_PASSWORD"
|
case setupPassword = "SETUP_PASSWORD"
|
||||||
|
case upgradePremium = "PREMIUM_UPGRADE"
|
||||||
|
case annualPremium = "PREMIUM_ANNUAL"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
||||||
|
|||||||
@ -69,8 +69,13 @@ func _internal_canPurchasePremium(account: Account, purpose: AppStoreTransaction
|
|||||||
switch purpose {
|
switch purpose {
|
||||||
case .subscription, .restore, .upgrade:
|
case .subscription, .restore, .upgrade:
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
if case .restore = purpose {
|
switch purpose {
|
||||||
|
case .upgrade:
|
||||||
|
flags |= (1 << 1)
|
||||||
|
case .restore:
|
||||||
flags |= (1 << 0)
|
flags |= (1 << 0)
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
purposeSignal = .single(.inputStorePaymentPremiumSubscription(flags: flags))
|
purposeSignal = .single(.inputStorePaymentPremiumSubscription(flags: flags))
|
||||||
case let .gift(peerId, currency, amount):
|
case let .gift(peerId, currency, amount):
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import SwiftSignalKit
|
|||||||
import MtProtoKit
|
import MtProtoKit
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
|
|
||||||
|
|
||||||
public enum UpdatePeerPhotoStatus {
|
public enum UpdatePeerPhotoStatus {
|
||||||
case progress(Float)
|
case progress(Float)
|
||||||
case complete([TelegramMediaImageRepresentation])
|
case complete([TelegramMediaImageRepresentation])
|
||||||
@ -153,7 +152,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
|
|||||||
|
|
||||||
var videoEmojiMarkup: Api.VideoSize?
|
var videoEmojiMarkup: Api.VideoSize?
|
||||||
if let fileId, let backgroundColors {
|
if let fileId, let backgroundColors {
|
||||||
videoEmojiMarkup = .videoSizeEmojiMarkup(type: "e", emojiId: fileId, backgroundColors: backgroundColors)
|
videoEmojiMarkup = .videoSizeEmojiMarkup(emojiId: fileId, backgroundColors: backgroundColors)
|
||||||
flags |= (1 << 4)
|
flags |= (1 << 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +216,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
|
|||||||
resource = CloudPhotoSizeMediaResource(datacenterId: dcId, photoId: id, accessHash: accessHash, sizeSpec: type, size: Int64(size), fileReference: fileReference.makeData())
|
resource = CloudPhotoSizeMediaResource(datacenterId: dcId, photoId: id, accessHash: accessHash, sizeSpec: type, size: Int64(size), fileReference: fileReference.makeData())
|
||||||
|
|
||||||
videoRepresentations.append(TelegramMediaImage.VideoRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, startTimestamp: videoStartTs))
|
videoRepresentations.append(TelegramMediaImage.VideoRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, startTimestamp: videoStartTs))
|
||||||
case .videoSizeEmojiMarkup:
|
case .videoSizeEmojiMarkup, .videoSizeStickerMarkup:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,9 +279,9 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
|
|||||||
|
|
||||||
let request: Signal<Api.Updates, MTRpcError>
|
let request: Signal<Api.Updates, MTRpcError>
|
||||||
if let peer = peer as? TelegramGroup {
|
if let peer = peer as? TelegramGroup {
|
||||||
request = network.request(Api.functions.messages.editChatPhoto(chatId: peer.id.id._internalGetInt64Value(), photo: .inputChatUploadedPhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp)))
|
request = network.request(Api.functions.messages.editChatPhoto(chatId: peer.id.id._internalGetInt64Value(), photo: .inputChatUploadedPhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: nil)))
|
||||||
} else if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
|
} else if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) {
|
||||||
request = network.request(Api.functions.channels.editPhoto(channel: inputChannel, photo: .inputChatUploadedPhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp)))
|
request = network.request(Api.functions.channels.editPhoto(channel: inputChannel, photo: .inputChatUploadedPhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp, videoEmojiMarkup: nil)))
|
||||||
} else {
|
} else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
request = .complete()
|
request = .complete()
|
||||||
|
|||||||
@ -36,6 +36,8 @@ swift_library(
|
|||||||
"//submodules/LegacyComponents:LegacyComponents",
|
"//submodules/LegacyComponents:LegacyComponents",
|
||||||
"//submodules/DrawingUI:DrawingUI",
|
"//submodules/DrawingUI:DrawingUI",
|
||||||
"//submodules/StickerResources:StickerResources",
|
"//submodules/StickerResources:StickerResources",
|
||||||
|
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||||
|
"//submodules/TelegramUI/Components/EmojiTextAttachmentView:EmojiTextAttachmentView",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import GradientBackground
|
|||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
import DrawingUI
|
import DrawingUI
|
||||||
import SolidRoundedButtonComponent
|
import SolidRoundedButtonComponent
|
||||||
|
import AnimationCache
|
||||||
|
import EmojiTextAttachmentView
|
||||||
|
|
||||||
enum AvatarBackground: Equatable {
|
enum AvatarBackground: Equatable {
|
||||||
case gradient([UInt32])
|
case gradient([UInt32])
|
||||||
@ -56,7 +58,7 @@ private let defaultBackgrounds: [AvatarBackground] = [
|
|||||||
.gradient([0x82b1ff, 0x665fff]),
|
.gradient([0x82b1ff, 0x665fff]),
|
||||||
]
|
]
|
||||||
|
|
||||||
private struct KeyboardInputData: Equatable {
|
public struct AvatarKeyboardInputData: Equatable {
|
||||||
var emoji: EmojiPagerContentComponent
|
var emoji: EmojiPagerContentComponent
|
||||||
var stickers: EmojiPagerContentComponent?
|
var stickers: EmojiPagerContentComponent?
|
||||||
|
|
||||||
@ -110,7 +112,8 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
|
|
||||||
final class State: ComponentState {
|
final class State: ComponentState {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
|
let ready: Promise<Bool>
|
||||||
|
|
||||||
var selectedBackground: AvatarBackground
|
var selectedBackground: AvatarBackground
|
||||||
var selectedFile: TelegramMediaFile?
|
var selectedFile: TelegramMediaFile?
|
||||||
|
|
||||||
@ -124,8 +127,9 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
|
|
||||||
var isSearchActive: Bool = false
|
var isSearchActive: Bool = false
|
||||||
|
|
||||||
init(context: AccountContext, initialFileId: Int64?, initialBackgroundColors: [Int32]?) {
|
init(context: AccountContext, ready: Promise<Bool>, initialFileId: Int64?, initialBackgroundColors: [Int32]?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.ready = ready
|
||||||
|
|
||||||
self.selectedBackground = defaultBackgrounds.first!
|
self.selectedBackground = defaultBackgrounds.first!
|
||||||
self.previousColor = self.selectedBackground
|
self.previousColor = self.selectedBackground
|
||||||
@ -153,6 +157,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
func makeState() -> State {
|
func makeState() -> State {
|
||||||
return State(
|
return State(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
|
ready: self.ready,
|
||||||
initialFileId: self.initialFileId,
|
initialFileId: self.initialFileId,
|
||||||
initialBackgroundColors: self.initialBackgroundColors
|
initialBackgroundColors: self.initialBackgroundColors
|
||||||
)
|
)
|
||||||
@ -187,7 +192,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
private var controller: (() -> AvatarEditorScreen?)?
|
private var controller: (() -> AvatarEditorScreen?)?
|
||||||
|
|
||||||
private var dataDisposable: Disposable?
|
private var dataDisposable: Disposable?
|
||||||
private var data: KeyboardInputData?
|
private var data: AvatarKeyboardInputData?
|
||||||
|
|
||||||
private let emojiSearchDisposable = MetaDisposable()
|
private let emojiSearchDisposable = MetaDisposable()
|
||||||
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
|
private let emojiSearchResult = Promise<(groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?>(nil)
|
||||||
@ -232,7 +237,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
self.emojiSearchDisposable.dispose()
|
self.emojiSearchDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateData(_ data: KeyboardInputData) {
|
private func updateData(_ data: AvatarKeyboardInputData) {
|
||||||
let wasEmpty = self.data == nil
|
let wasEmpty = self.data == nil
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
@ -240,6 +245,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
self.state?.selectedFile = data.emoji.panelItemGroups.first?.items.first?.itemFile
|
self.state?.selectedFile = data.emoji.panelItemGroups.first?.items.first?.itemFile
|
||||||
}
|
}
|
||||||
self.state?.updated(transition: .immediate)
|
self.state?.updated(transition: .immediate)
|
||||||
|
self.state?.ready.set(.single(true))
|
||||||
|
|
||||||
let updateSearchQuery: (String, String) -> Void = { [weak self] rawQuery, languageCode in
|
let updateSearchQuery: (String, String) -> Void = { [weak self] rawQuery, languageCode in
|
||||||
guard let strongSelf = self, let context = strongSelf.state?.context else {
|
guard let strongSelf = self, let context = strongSelf.state?.context else {
|
||||||
@ -424,7 +430,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let context = controller.context
|
let context = controller.context
|
||||||
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedStickerPacks)
|
let viewKey = PostboxViewKey.orderedItemList(id: Namespaces.OrderedItemList.CloudFeaturedEmojiPacks)
|
||||||
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|
let _ = (context.account.postbox.combinedView(keys: [viewKey])
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { views in
|
|> deliverOnMainQueue).start(next: { views in
|
||||||
@ -664,7 +670,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
func update(component: AvatarEditorScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
func update(component: AvatarEditorScreenComponent, availableSize: CGSize, state: State, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||||
self.component = component
|
self.component = component
|
||||||
self.state = state
|
self.state = state
|
||||||
|
|
||||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
let strings = environment.strings
|
let strings = environment.strings
|
||||||
|
|
||||||
@ -738,45 +744,12 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
self.keyboardContainerView.backgroundColor = environment.theme.list.itemBlocksBackgroundColor
|
self.keyboardContainerView.backgroundColor = environment.theme.list.itemBlocksBackgroundColor
|
||||||
self.panelSeparatorView.backgroundColor = environment.theme.list.itemPlainSeparatorColor
|
self.panelSeparatorView.backgroundColor = environment.theme.list.itemPlainSeparatorColor
|
||||||
|
|
||||||
if self.dataDisposable == nil {
|
if self.dataDisposable == nil, let controller = controller() as? AvatarEditorScreen {
|
||||||
let context = component.context
|
let context = component.context
|
||||||
let emojiItems = EmojiPagerContentComponent.emojiInputData(
|
|
||||||
context: context,
|
|
||||||
animationCache: context.animationCache,
|
|
||||||
animationRenderer: context.animationRenderer,
|
|
||||||
isStandalone: false,
|
|
||||||
isStatusSelection: false,
|
|
||||||
isReactionSelection: false,
|
|
||||||
isEmojiSelection: false,
|
|
||||||
isProfilePhotoEmojiSelection: true,
|
|
||||||
topReactionItems: [],
|
|
||||||
areUnicodeEmojiEnabled: false,
|
|
||||||
areCustomEmojiEnabled: true,
|
|
||||||
chatPeerId: context.account.peerId,
|
|
||||||
hasSearch: true,
|
|
||||||
forceHasPremium: true
|
|
||||||
)
|
|
||||||
|
|
||||||
let stickerItems = EmojiPagerContentComponent.stickerInputData(
|
|
||||||
context: context,
|
|
||||||
animationCache: context.animationCache,
|
|
||||||
animationRenderer: context.animationRenderer,
|
|
||||||
stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks],
|
|
||||||
stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers],
|
|
||||||
chatPeerId: context.account.peerId,
|
|
||||||
hasSearch: true,
|
|
||||||
hasTrending: false,
|
|
||||||
forceHasPremium: true,
|
|
||||||
searchIsPlaceholderOnly: false
|
|
||||||
)
|
|
||||||
|
|
||||||
let signal = combineLatest(queue: .mainQueue(),
|
let signal = combineLatest(queue: .mainQueue(),
|
||||||
emojiItems,
|
controller.inputData |> delay(0.01, queue: .mainQueue()),
|
||||||
stickerItems,
|
|
||||||
self.emojiSearchResult.get()
|
self.emojiSearchResult.get()
|
||||||
) |> map { emoji, stickers, searchResult -> (KeyboardInputData, (groups: [EmojiPagerContentComponent.ItemGroup], id: AnyHashable)?) in
|
)
|
||||||
return (KeyboardInputData(emoji: emoji, stickers: stickers), searchResult)
|
|
||||||
}
|
|
||||||
self.dataDisposable = (signal
|
self.dataDisposable = (signal
|
||||||
|> deliverOnMainQueue
|
|> deliverOnMainQueue
|
||||||
).start(next: { [weak self, weak state] data, searchResult in
|
).start(next: { [weak self, weak state] data, searchResult in
|
||||||
@ -819,7 +792,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
tapped: { [weak state] in
|
tapped: { [weak state] in
|
||||||
if let state, !state.editingColor {
|
if let state, !state.editingColor {
|
||||||
state.expanded = !state.expanded
|
state.expanded = !state.expanded
|
||||||
state.updated(transition: .easeInOut(duration: 0.3))
|
state.updated(transition: Transition(animation: .curve(duration: 0.35, curve: .spring)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -833,7 +806,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let previewScale = effectiveIsExpanded ? 1.0 : collapsedAvatarSize.width / avatarPreviewSize.width
|
let previewScale = effectiveIsExpanded ? 1.0 : collapsedAvatarSize.width / avatarPreviewSize.width
|
||||||
let cornerRadius = effectiveIsExpanded ? 0.0 : availableSize.width / 2.0
|
let cornerRadius = effectiveIsExpanded ? 0.0 : availableSize.width / (component.peerType == .forum ? 4.0 : 2.0)
|
||||||
let position = effectiveIsExpanded ? avatarPreviewSize.height / 2.0 : environment.navigationHeight + 10.0
|
let position = effectiveIsExpanded ? avatarPreviewSize.height / 2.0 : environment.navigationHeight + 10.0
|
||||||
|
|
||||||
transition.setBounds(view: previewView, bounds: CGRect(origin: .zero, size: avatarPreviewSize))
|
transition.setBounds(view: previewView, bounds: CGRect(origin: .zero, size: avatarPreviewSize))
|
||||||
@ -1095,6 +1068,9 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
|
hideTopPanelUpdated: { [weak self] hideTopPanel, transition in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.state?.isSearchActive = hideTopPanel
|
strongSelf.state?.isSearchActive = hideTopPanel
|
||||||
|
if hideTopPanel {
|
||||||
|
strongSelf.state?.expanded = false
|
||||||
|
}
|
||||||
strongSelf.state?.updated(transition: transition)
|
strongSelf.state?.updated(transition: transition)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1147,7 +1123,7 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
switch component.peerType {
|
switch component.peerType {
|
||||||
case .user:
|
case .user:
|
||||||
buttonText = strings.AvatarEditor_SetProfilePhoto
|
buttonText = strings.AvatarEditor_SetProfilePhoto
|
||||||
case .group:
|
case .group, .forum:
|
||||||
buttonText = strings.AvatarEditor_SetGroupPhoto
|
buttonText = strings.AvatarEditor_SetGroupPhoto
|
||||||
case .channel:
|
case .channel:
|
||||||
buttonText = strings.AvatarEditor_SetChannelPhoto
|
buttonText = strings.AvatarEditor_SetChannelPhoto
|
||||||
@ -1180,66 +1156,106 @@ final class AvatarEditorScreenComponent: Component {
|
|||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let queue = Queue()
|
||||||
func complete() {
|
func complete() {
|
||||||
guard let state = self.state, let itemFile = state.selectedFile, let previewView = self.previewView.view else {
|
guard let state = self.state, let file = state.selectedFile, let controller = self.controller?() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let size = CGSize(width: 1920.0, height: 1920.0)
|
let context = controller.context
|
||||||
let image = state.selectedBackground.generateImage(size: size)
|
let _ = context.animationCache.getFirstFrame(queue: self.queue, sourceId: file.resource.id.stringRepresentation, size: CGSize(width: 640.0, height: 640.0), fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: .media(media: .standalone(media: file), resource: file.resource), type: AnimationCacheAnimationType(file: file), keyframeOnly: true, customColor: nil), completion: { result in
|
||||||
let tempPath = NSTemporaryDirectory() + "/\(UInt64.random(in: 0 ... UInt64.max)).jpg"
|
guard let item = result.item else {
|
||||||
let tempUrl = NSURL(fileURLWithPath: tempPath) as URL
|
return
|
||||||
try? image.jpegData(compressionQuality: 1.0)?.write(to: tempUrl)
|
}
|
||||||
|
var image: UIImage?
|
||||||
let entity = DrawingStickerEntity(content: .file(itemFile))
|
if let frame = item.advance(advance: .frames(1), requestedFormat: .rgba) {
|
||||||
entity.referenceDrawingSize = size
|
switch frame.frame.format {
|
||||||
entity.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
case let .rgba(data, width, height, bytesPerRow):
|
||||||
entity.scale = 3.3
|
guard let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: false, bytesPerRow: bytesPerRow) else {
|
||||||
|
return
|
||||||
var documentId: Int64 = 0
|
}
|
||||||
if case let .file(file) = entity.content, file.isCustomEmoji {
|
|
||||||
documentId = file.fileId.id
|
data.withUnsafeBytes { bytes -> Void in
|
||||||
}
|
memcpy(context.bytes, bytes.baseAddress!, height * bytesPerRow)
|
||||||
|
}
|
||||||
let colors: [NSNumber] = state.selectedBackground.colors.map { Int32(bitPattern: $0) as NSNumber }
|
|
||||||
|
image = context.generateImage()
|
||||||
let entitiesData = DrawingEntitiesView.encodeEntities([entity])
|
default:
|
||||||
|
return
|
||||||
let paintingData = TGPaintingData(
|
}
|
||||||
drawing: nil,
|
|
||||||
entitiesData: entitiesData,
|
|
||||||
image: nil,
|
|
||||||
stillImage: nil,
|
|
||||||
hasAnimation: entity.isAnimated,
|
|
||||||
stickers: []
|
|
||||||
)
|
|
||||||
|
|
||||||
let adjustments = PGPhotoEditorValues(
|
|
||||||
originalSize: size,
|
|
||||||
cropRect: CGRect(origin: .zero, size: size),
|
|
||||||
cropRotation: 0.0,
|
|
||||||
cropOrientation: .up,
|
|
||||||
cropLockedAspectRatio: 1.0,
|
|
||||||
cropMirrored: false,
|
|
||||||
toolValues: [:],
|
|
||||||
paintingData: paintingData,
|
|
||||||
sendAsGif: true
|
|
||||||
)
|
|
||||||
let preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetProfileHigh
|
|
||||||
|
|
||||||
let combinedImage = generateImage(previewView.bounds.size, contextGenerator: { size, context in
|
|
||||||
let bounds = CGRect(origin: .zero, size: size)
|
|
||||||
if let cgImage = image.cgImage {
|
|
||||||
context.draw(cgImage, in: bounds)
|
|
||||||
}
|
}
|
||||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
|
||||||
context.scaleBy(x: 1.0, y: -1.0)
|
|
||||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
|
||||||
|
|
||||||
previewView.layer.render(in: context)
|
Queue.mainQueue().async {
|
||||||
}, opaque: false)!
|
guard let image else {
|
||||||
|
return
|
||||||
self.controller?()?.completion(combinedImage, tempUrl, TGVideoEditAdjustments(photoEditorValues: adjustments, preset: preset, documentId: documentId, colors: colors), { [weak self] in
|
}
|
||||||
self?.controller?()?.dismiss()
|
|
||||||
|
let size = CGSize(width: 800.0, height: 800.0)
|
||||||
|
let backgroundImage = state.selectedBackground.generateImage(size: size)
|
||||||
|
let tempPath = NSTemporaryDirectory() + "/\(UInt64.random(in: 0 ... UInt64.max)).jpg"
|
||||||
|
let tempUrl = NSURL(fileURLWithPath: tempPath) as URL
|
||||||
|
try? backgroundImage.jpegData(compressionQuality: 0.8)?.write(to: tempUrl)
|
||||||
|
|
||||||
|
let drawingSize = CGSize(width: 1920.0, height: 1920.0)
|
||||||
|
let entity = DrawingStickerEntity(content: .file(file))
|
||||||
|
entity.referenceDrawingSize = drawingSize
|
||||||
|
entity.position = CGPoint(x: drawingSize.width / 2.0, y: drawingSize.height / 2.0)
|
||||||
|
entity.scale = 3.3
|
||||||
|
|
||||||
|
var documentId: Int64 = 0
|
||||||
|
if case let .file(file) = entity.content, file.isCustomEmoji {
|
||||||
|
documentId = file.fileId.id
|
||||||
|
}
|
||||||
|
|
||||||
|
let colors: [NSNumber] = state.selectedBackground.colors.map { Int32(bitPattern: $0) as NSNumber }
|
||||||
|
|
||||||
|
let entitiesData = DrawingEntitiesView.encodeEntities([entity])
|
||||||
|
|
||||||
|
let paintingData = TGPaintingData(
|
||||||
|
drawing: nil,
|
||||||
|
entitiesData: entitiesData,
|
||||||
|
image: nil,
|
||||||
|
stillImage: nil,
|
||||||
|
hasAnimation: entity.isAnimated,
|
||||||
|
stickers: []
|
||||||
|
)
|
||||||
|
|
||||||
|
let adjustments = PGPhotoEditorValues(
|
||||||
|
originalSize: size,
|
||||||
|
cropRect: CGRect(origin: .zero, size: size),
|
||||||
|
cropRotation: 0.0,
|
||||||
|
cropOrientation: .up,
|
||||||
|
cropLockedAspectRatio: 1.0,
|
||||||
|
cropMirrored: false,
|
||||||
|
toolValues: [:],
|
||||||
|
paintingData: paintingData,
|
||||||
|
sendAsGif: true
|
||||||
|
)
|
||||||
|
let preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetProfileHigh
|
||||||
|
|
||||||
|
let combinedImage = generateImage(size, contextGenerator: { size, context in
|
||||||
|
let bounds = CGRect(origin: .zero, size: size)
|
||||||
|
if let cgImage = backgroundImage.cgImage {
|
||||||
|
context.draw(cgImage, in: bounds)
|
||||||
|
}
|
||||||
|
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
|
context.scaleBy(x: 0.67, y: 0.67)
|
||||||
|
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||||
|
|
||||||
|
if let cgImage = image.cgImage {
|
||||||
|
context.draw(cgImage, in: bounds)
|
||||||
|
}
|
||||||
|
}, opaque: false)!
|
||||||
|
|
||||||
|
if entity.isAnimated {
|
||||||
|
controller.videoCompletion(combinedImage, tempUrl, TGVideoEditAdjustments(photoEditorValues: adjustments, preset: preset, documentId: documentId, colors: colors), { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
controller.imageCompletion(combinedImage, { [weak controller] in
|
||||||
|
controller?.dismiss()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1259,18 +1275,65 @@ public final class AvatarEditorScreen: ViewControllerComponentContainer {
|
|||||||
case user
|
case user
|
||||||
case group
|
case group
|
||||||
case channel
|
case channel
|
||||||
|
case forum
|
||||||
}
|
}
|
||||||
fileprivate let context: AccountContext
|
fileprivate let context: AccountContext
|
||||||
|
fileprivate let inputData: Signal<AvatarKeyboardInputData, NoError>
|
||||||
|
|
||||||
private let readyValue = Promise<Bool>()
|
private let readyValue = Promise<Bool>()
|
||||||
override public var ready: Promise<Bool> {
|
override public var ready: Promise<Bool> {
|
||||||
return self.readyValue
|
return self.readyValue
|
||||||
}
|
}
|
||||||
|
|
||||||
public var completion: (UIImage, URL, TGVideoEditAdjustments, @escaping () -> Void) -> Void = { _, _, _, _ in }
|
public var imageCompletion: (UIImage, @escaping () -> Void) -> Void = { _, _ in }
|
||||||
|
public var videoCompletion: (UIImage, URL, TGVideoEditAdjustments, @escaping () -> Void) -> Void = { _, _, _, _ in }
|
||||||
|
|
||||||
public init(context: AccountContext, peerType: PeerType, initialFileId: Int64?, initialBackgroundColors: [Int32]?) {
|
public static func inputData(context: AccountContext, isGroup: Bool) -> Signal<AvatarKeyboardInputData, NoError> {
|
||||||
|
let emojiItems = EmojiPagerContentComponent.emojiInputData(
|
||||||
|
context: context,
|
||||||
|
animationCache: context.animationCache,
|
||||||
|
animationRenderer: context.animationRenderer,
|
||||||
|
isStandalone: false,
|
||||||
|
isStatusSelection: false,
|
||||||
|
isReactionSelection: false,
|
||||||
|
isEmojiSelection: false,
|
||||||
|
isProfilePhotoEmojiSelection: !isGroup,
|
||||||
|
isGroupPhotoEmojiSelection: isGroup,
|
||||||
|
topReactionItems: [],
|
||||||
|
areUnicodeEmojiEnabled: false,
|
||||||
|
areCustomEmojiEnabled: true,
|
||||||
|
chatPeerId: context.account.peerId,
|
||||||
|
hasSearch: true,
|
||||||
|
forceHasPremium: true
|
||||||
|
)
|
||||||
|
|
||||||
|
let stickerItems = EmojiPagerContentComponent.stickerInputData(
|
||||||
|
context: context,
|
||||||
|
animationCache: context.animationCache,
|
||||||
|
animationRenderer: context.animationRenderer,
|
||||||
|
stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks],
|
||||||
|
stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers],
|
||||||
|
chatPeerId: context.account.peerId,
|
||||||
|
hasSearch: true,
|
||||||
|
hasTrending: false,
|
||||||
|
forceHasPremium: true,
|
||||||
|
searchIsPlaceholderOnly: false,
|
||||||
|
isProfilePhotoEmojiSelection: !isGroup,
|
||||||
|
isGroupPhotoEmojiSelection: isGroup
|
||||||
|
)
|
||||||
|
|
||||||
|
let signal = combineLatest(queue: .mainQueue(),
|
||||||
|
emojiItems,
|
||||||
|
stickerItems
|
||||||
|
) |> map { emoji, stickers -> AvatarKeyboardInputData in
|
||||||
|
return AvatarKeyboardInputData(emoji: emoji, stickers: stickers)
|
||||||
|
}
|
||||||
|
return signal
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(context: AccountContext, inputData: Signal<AvatarKeyboardInputData, NoError>, peerType: PeerType, initialFileId: Int64?, initialBackgroundColors: [Int32]?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.inputData = inputData
|
||||||
|
|
||||||
let componentReady = Promise<Bool>()
|
let componentReady = Promise<Bool>()
|
||||||
super.init(context: context, component: AvatarEditorScreenComponent(context: context, ready: componentReady, peerType: peerType, initialFileId: initialFileId, initialBackgroundColors: initialBackgroundColors), navigationBarAppearance: .transparent)
|
super.init(context: context, component: AvatarEditorScreenComponent(context: context, ready: componentReady, peerType: peerType, initialFileId: initialFileId, initialBackgroundColors: initialBackgroundColors), navigationBarAppearance: .transparent)
|
||||||
|
|||||||
@ -7332,7 +7332,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
contentItemGroups: allItemGroups,
|
contentItemGroups: allItemGroups,
|
||||||
itemLayoutType: .compact,
|
itemLayoutType: .compact,
|
||||||
itemContentUniqueId: nil,
|
itemContentUniqueId: nil,
|
||||||
warpContentsOnEdges: isReactionSelection || isStatusSelection,
|
warpContentsOnEdges: isReactionSelection || isStatusSelection || isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection,
|
||||||
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
|
displaySearchWithPlaceholder: displaySearchWithPlaceholder,
|
||||||
searchInitiallyHidden: searchInitiallyHidden,
|
searchInitiallyHidden: searchInitiallyHidden,
|
||||||
searchIsPlaceholderOnly: false,
|
searchIsPlaceholderOnly: false,
|
||||||
@ -7354,7 +7354,9 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
hasSearch: Bool,
|
hasSearch: Bool,
|
||||||
hasTrending: Bool,
|
hasTrending: Bool,
|
||||||
forceHasPremium: Bool,
|
forceHasPremium: Bool,
|
||||||
searchIsPlaceholderOnly: Bool = true
|
searchIsPlaceholderOnly: Bool = true,
|
||||||
|
isProfilePhotoEmojiSelection: Bool = false,
|
||||||
|
isGroupPhotoEmojiSelection: Bool = false
|
||||||
) -> Signal<EmojiPagerContentComponent, NoError> {
|
) -> Signal<EmojiPagerContentComponent, NoError> {
|
||||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||||
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
let isPremiumDisabled = premiumConfiguration.isPremiumDisabled
|
||||||
@ -7846,7 +7848,7 @@ public final class EmojiPagerContentComponent: Component {
|
|||||||
contentItemGroups: allItemGroups,
|
contentItemGroups: allItemGroups,
|
||||||
itemLayoutType: .detailed,
|
itemLayoutType: .detailed,
|
||||||
itemContentUniqueId: nil,
|
itemContentUniqueId: nil,
|
||||||
warpContentsOnEdges: false,
|
warpContentsOnEdges: isProfilePhotoEmojiSelection || isGroupPhotoEmojiSelection,
|
||||||
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
|
displaySearchWithPlaceholder: hasSearch ? strings.StickersSearch_SearchStickersPlaceholder : nil,
|
||||||
searchInitiallyHidden: true,
|
searchInitiallyHidden: true,
|
||||||
searchIsPlaceholderOnly: searchIsPlaceholderOnly,
|
searchIsPlaceholderOnly: searchIsPlaceholderOnly,
|
||||||
|
|||||||
@ -6699,21 +6699,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||||
self.translationStateDisposable = combineLatest(
|
let baseLanguageCode = self.presentationData.strings.baseLanguageCode
|
||||||
queue: .mainQueue(),
|
let isPremium = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||||
chatTranslationState(context: self.context, peerId: peerId),
|
|> map { peer -> Bool in
|
||||||
|
return peer?.isPremium ?? false
|
||||||
|
}
|
||||||
|
self.translationStateDisposable = (combineLatest(
|
||||||
|
queue: .concurrentDefaultQueue(),
|
||||||
|
isPremium,
|
||||||
self.chatDisplayNode.historyNode.cachedPeerDataAndMessages
|
self.chatDisplayNode.historyNode.cachedPeerDataAndMessages
|
||||||
).start(next: { [weak self] translationState, cachedDataAndMessages in
|
) |> mapToSignal { isPremium, cachedDataAndMessages -> Signal<ChatPresentationTranslationState?, NoError> in
|
||||||
|
let (cachedData, _) = cachedDataAndMessages
|
||||||
|
var isHidden = false
|
||||||
|
if let cachedData = cachedData as? CachedChannelData, cachedData.flags.contains(.translationHidden) {
|
||||||
|
isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPremium && !isHidden {
|
||||||
|
return chatTranslationState(context: context, peerId: peerId)
|
||||||
|
|> map { translationState -> ChatPresentationTranslationState? in
|
||||||
|
if let translationState, !translationState.fromLang.isEmpty {
|
||||||
|
return ChatPresentationTranslationState(isEnabled: translationState.isEnabled, fromLang: translationState.fromLang, toLang: translationState.toLang ?? baseLanguageCode)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] chatTranslationState in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let (cachedData, _) = cachedDataAndMessages
|
|
||||||
var isHidden = false
|
|
||||||
if let cachedData = cachedData as? CachedChannelData, cachedData.flags.contains(.translationHidden) {
|
|
||||||
isHidden = true
|
|
||||||
}
|
|
||||||
var chatTranslationState: ChatPresentationTranslationState?
|
|
||||||
if let translationState, !isHidden && !translationState.fromLang.isEmpty {
|
|
||||||
chatTranslationState = ChatPresentationTranslationState(isEnabled: translationState.isEnabled, fromLang: translationState.fromLang, toLang: translationState.toLang ?? strongSelf.presentationData.strings.baseLanguageCode)
|
|
||||||
}
|
|
||||||
strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in
|
strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in
|
||||||
return state.updatedTranslationState(chatTranslationState)
|
return state.updatedTranslationState(chatTranslationState)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1789,8 +1789,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame, beginWithCurrentState: true)
|
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame, beginWithCurrentState: true)
|
||||||
|
|
||||||
if let navigationBarBackgroundContent = self.navigationBarBackgroundContent {
|
if let navigationBarBackgroundContent = self.navigationBarBackgroundContent {
|
||||||
transition.updateFrame(node: navigationBarBackgroundContent, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0))), beginWithCurrentState: true)
|
transition.updateFrame(node: navigationBarBackgroundContent, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0) + (translationPanelHeight ?? 0.0))), beginWithCurrentState: true)
|
||||||
navigationBarBackgroundContent.update(rect: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0))), within: layout.size, transition: transition)
|
navigationBarBackgroundContent.update(rect: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight + (titleAccessoryPanelBackgroundHeight ?? 0.0) + (translationPanelHeight ?? 0.0))), within: layout.size, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let inputPanelBackgroundContent = self.inputPanelBackgroundContent {
|
if let inputPanelBackgroundContent = self.inputPanelBackgroundContent {
|
||||||
|
|||||||
@ -234,7 +234,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
|
|||||||
case .allButLast:
|
case .allButLast:
|
||||||
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId
|
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId
|
||||||
}
|
}
|
||||||
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
||||||
}
|
}
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
||||||
case let .MessageGroupEntry(_, messages, presentationData):
|
case let .MessageGroupEntry(_, messages, presentationData):
|
||||||
@ -279,7 +279,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
|
|||||||
case .allButLast:
|
case .allButLast:
|
||||||
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId
|
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != lastHeaderId
|
||||||
}
|
}
|
||||||
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
item = ListMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
||||||
}
|
}
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
|
||||||
case let .MessageGroupEntry(_, messages, presentationData):
|
case let .MessageGroupEntry(_, messages, presentationData):
|
||||||
@ -1293,7 +1293,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var translateToLanguage: String?
|
var translateToLanguage: String?
|
||||||
if let translationState, translationState.isEnabled {
|
if let translationState, isPremium && translationState.isEnabled {
|
||||||
translateToLanguage = translationState.toLang ?? presentationData.strings.baseLanguageCode
|
translateToLanguage = translationState.toLang ?? presentationData.strings.baseLanguageCode
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3472,7 +3472,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
case .allButLast:
|
case .allButLast:
|
||||||
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId
|
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId
|
||||||
}
|
}
|
||||||
item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
||||||
}
|
}
|
||||||
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
|
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
|
||||||
|
|
||||||
@ -3528,7 +3528,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
case .allButLast:
|
case .allButLast:
|
||||||
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId
|
displayHeader = listMessageDateHeaderId(timestamp: message.timestamp) != historyView.lastHeaderId
|
||||||
}
|
}
|
||||||
item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
item = ListMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, interaction: ListMessageItemInteraction(controllerInteraction: self.controllerInteraction), message: message, translateToLanguage: associatedData.translateToLanguage, selection: selection, displayHeader: displayHeader, hintIsLink: hintLinks, isGlobalSearchResult: isGlobalSearch)
|
||||||
}
|
}
|
||||||
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
|
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
|
||||||
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
|
|||||||
@ -264,6 +264,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
}, openForumThread: { _, _ in
|
}, openForumThread: { _, _ in
|
||||||
}, openStorageManagement: {
|
}, openStorageManagement: {
|
||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
|
}, openPremiumIntro: {
|
||||||
})
|
})
|
||||||
interaction.searchTextHighightState = searchQuery
|
interaction.searchTextHighightState = searchQuery
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import PeerInfoUI
|
|||||||
import MapResourceToAvatarSizes
|
import MapResourceToAvatarSizes
|
||||||
import LegacyMediaPickerUI
|
import LegacyMediaPickerUI
|
||||||
import TextFormat
|
import TextFormat
|
||||||
|
import AvatarEditorScreen
|
||||||
|
|
||||||
private struct CreateChannelArguments {
|
private struct CreateChannelArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -623,6 +624,9 @@ public func createChannelController(context: AccountContext, mode: CreateChannel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let keyboardInputData = Promise<AvatarKeyboardInputData>()
|
||||||
|
keyboardInputData.set(AvatarEditorScreen.inputData(context: context, isGroup: true))
|
||||||
|
|
||||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil, isSuggesting: false)!
|
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil, isSuggesting: false)!
|
||||||
mixin.stickersContext = LegacyPaintStickersContext(context: context)
|
mixin.stickersContext = LegacyPaintStickersContext(context: context)
|
||||||
let _ = currentAvatarMixin.swap(mixin)
|
let _ = currentAvatarMixin.swap(mixin)
|
||||||
@ -633,6 +637,27 @@ public func createChannelController(context: AccountContext, mode: CreateChannel
|
|||||||
}))
|
}))
|
||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
|
mixin.requestAvatarEditor = { imageCompletion, videoCompletion in
|
||||||
|
guard let imageCompletion, let videoCompletion else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let peerType: AvatarEditorScreen.PeerType
|
||||||
|
if case .legacyGroup = peer {
|
||||||
|
peerType = .group
|
||||||
|
} else if case let .channel(channel) = peer {
|
||||||
|
if case .group = channel.info {
|
||||||
|
peerType = channel.flags.contains(.isForum) ? .forum : .group
|
||||||
|
} else {
|
||||||
|
peerType = .channel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peerType = .user
|
||||||
|
}
|
||||||
|
let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: peerType, initialFileId: nil, initialBackgroundColors: nil)
|
||||||
|
controller.imageCompletion = imageCompletion
|
||||||
|
controller.videoCompletion = videoCompletion
|
||||||
|
pushControllerImpl?(controller)
|
||||||
|
}
|
||||||
mixin.didFinishWithImage = { image in
|
mixin.didFinishWithImage = { image in
|
||||||
if let image = image {
|
if let image = image {
|
||||||
completedChannelPhotoImpl(image)
|
completedChannelPhotoImpl(image)
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import ContextUI
|
|||||||
import ChatTimerScreen
|
import ChatTimerScreen
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import TextFormat
|
import TextFormat
|
||||||
|
import AvatarEditorScreen
|
||||||
|
|
||||||
private struct CreateGroupArguments {
|
private struct CreateGroupArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -947,6 +948,9 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let keyboardInputData = Promise<AvatarKeyboardInputData>()
|
||||||
|
keyboardInputData.set(AvatarEditorScreen.inputData(context: context, isGroup: true))
|
||||||
|
|
||||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil, isSuggesting: false)!
|
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: false, title: nil, isSuggesting: false)!
|
||||||
mixin.stickersContext = LegacyPaintStickersContext(context: context)
|
mixin.stickersContext = LegacyPaintStickersContext(context: context)
|
||||||
let _ = currentAvatarMixin.swap(mixin)
|
let _ = currentAvatarMixin.swap(mixin)
|
||||||
@ -957,6 +961,27 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
|||||||
}))
|
}))
|
||||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
}
|
}
|
||||||
|
mixin.requestAvatarEditor = { imageCompletion, videoCompletion in
|
||||||
|
guard let imageCompletion, let videoCompletion else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let peerType: AvatarEditorScreen.PeerType
|
||||||
|
if case .legacyGroup = peer {
|
||||||
|
peerType = .group
|
||||||
|
} else if case let .channel(channel) = peer {
|
||||||
|
if case .group = channel.info {
|
||||||
|
peerType = channel.flags.contains(.isForum) ? .forum : .group
|
||||||
|
} else {
|
||||||
|
peerType = .channel
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peerType = .user
|
||||||
|
}
|
||||||
|
let controller = AvatarEditorScreen(context: context, inputData: keyboardInputData.get(), peerType: peerType, initialFileId: nil, initialBackgroundColors: nil)
|
||||||
|
controller.imageCompletion = imageCompletion
|
||||||
|
controller.videoCompletion = videoCompletion
|
||||||
|
pushImpl?(controller)
|
||||||
|
}
|
||||||
mixin.didFinishWithImage = { image in
|
mixin.didFinishWithImage = { image in
|
||||||
if let image = image {
|
if let image = image {
|
||||||
completedGroupPhotoImpl(image)
|
completedGroupPhotoImpl(image)
|
||||||
|
|||||||
@ -4859,7 +4859,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
self?.openStats()
|
self?.openStats()
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
if cachedData.flags.contains(.translationHidden) {
|
if cachedData.flags.contains(.translationHidden) {
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ContextMenuTranslate, icon: { theme in
|
||||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor)
|
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Translate"), color: theme.contextMenu.primaryColor)
|
||||||
@ -7247,6 +7246,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
confirmationAction = nil
|
confirmationAction = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let keyboardInputData = Promise<AvatarKeyboardInputData>()
|
||||||
|
keyboardInputData.set(AvatarEditorScreen.inputData(context: strongSelf.context, isGroup: peer.id.namespace != Namespaces.Peer.CloudUser))
|
||||||
|
|
||||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))!
|
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))!
|
||||||
mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context)
|
mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context)
|
||||||
let _ = strongSelf.currentAvatarMixin.swap(mixin)
|
let _ = strongSelf.currentAvatarMixin.swap(mixin)
|
||||||
@ -7265,8 +7267,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
completion(nil)
|
completion(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mixin.requestAvatarEditor = { [weak self] completion in
|
mixin.requestAvatarEditor = { [weak self] imageCompletion, videoCompletion in
|
||||||
guard let strongSelf = self, let completion else {
|
guard let strongSelf = self, let imageCompletion, let videoCompletion else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let peerType: AvatarEditorScreen.PeerType
|
let peerType: AvatarEditorScreen.PeerType
|
||||||
@ -7274,15 +7276,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
peerType = .group
|
peerType = .group
|
||||||
} else if case let .channel(channel) = peer {
|
} else if case let .channel(channel) = peer {
|
||||||
if case .group = channel.info {
|
if case .group = channel.info {
|
||||||
peerType = .group
|
peerType = channel.flags.contains(.isForum) ? .forum : .group
|
||||||
} else {
|
} else {
|
||||||
peerType = .channel
|
peerType = .channel
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
peerType = .user
|
peerType = .user
|
||||||
}
|
}
|
||||||
let controller = AvatarEditorScreen(context: strongSelf.context, peerType: peerType, initialFileId: emojiMarkup?.fileId, initialBackgroundColors: emojiMarkup?.backgroundColors)
|
let controller = AvatarEditorScreen(context: strongSelf.context, inputData: keyboardInputData.get(), peerType: peerType, initialFileId: emojiMarkup?.fileId, initialBackgroundColors: emojiMarkup?.backgroundColors)
|
||||||
controller.completion = completion
|
controller.imageCompletion = imageCompletion
|
||||||
|
controller.videoCompletion = videoCompletion
|
||||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
|
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user