mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'db1d4422cb97018bbc7d0d554a63b70361471d4f'
This commit is contained in:
commit
c05c6b8e23
@ -11616,3 +11616,6 @@ Sorry for the inconvenience.";
|
||||
|
||||
"ChannelBoost.NoAds" = "Switch Off Ads";
|
||||
"ChannelBoost.EnableNoAdsLevelText" = "Your channel needs **Level %1$@** to switch off ads.";
|
||||
|
||||
"WebApp.TermsOfUse" = "Terms of Use";
|
||||
"WebApp.TermsOfUse_URL" = "https://telegram.org/tos/mini-apps";
|
||||
|
@ -1001,7 +1001,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
|
||||
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController
|
||||
|
||||
func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile) -> Void) -> ViewController
|
||||
func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, @escaping () -> Void) -> Void) -> ViewController
|
||||
|
||||
func makeStickerMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
|
||||
func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
|
||||
|
@ -72,7 +72,7 @@ public enum ContactMultiselectionControllerMode {
|
||||
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
|
||||
case channelCreation
|
||||
case chatSelection(ChatSelection)
|
||||
case premiumGifting
|
||||
case premiumGifting(topSectionTitle: String?, topSectionPeers: [EnginePeer.Id])
|
||||
case requestedUsersSelection
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ public enum PremiumGiftSource: Equatable {
|
||||
case profile
|
||||
case attachMenu
|
||||
case settings
|
||||
case chatList
|
||||
case chatList([EnginePeer.Id])
|
||||
case channelBoost
|
||||
case deeplink(String?)
|
||||
}
|
||||
|
@ -2308,8 +2308,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
}, openPremiumIntro: {
|
||||
}, openPremiumGift: {
|
||||
}, openPremiumGift: { _ in
|
||||
}, openActiveSessions: {
|
||||
}, openBirthdaySetup: {
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {
|
||||
}, hideChatFolderUpdates: {
|
||||
@ -3685,7 +3686,8 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
|
||||
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
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: {
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {
|
||||
}, openBirthdaySetup: {
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
|
@ -156,7 +156,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
|
||||
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
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, dismissNotice: { _ in
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {}, openBirthdaySetup: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
})
|
||||
interaction.isInlineMode = isInlineMode
|
||||
|
@ -101,8 +101,9 @@ public final class ChatListNodeInteraction {
|
||||
let openStorageManagement: () -> Void
|
||||
let openPasswordSetup: () -> Void
|
||||
let openPremiumIntro: () -> Void
|
||||
let openPremiumGift: () -> Void
|
||||
let openPremiumGift: ([EnginePeer.Id]) -> Void
|
||||
let openActiveSessions: () -> Void
|
||||
let openBirthdaySetup: () -> Void
|
||||
let performActiveSessionAction: (NewSessionReview, Bool) -> Void
|
||||
let openChatFolderUpdates: () -> Void
|
||||
let hideChatFolderUpdates: () -> Void
|
||||
@ -154,8 +155,9 @@ public final class ChatListNodeInteraction {
|
||||
openStorageManagement: @escaping () -> Void,
|
||||
openPasswordSetup: @escaping () -> Void,
|
||||
openPremiumIntro: @escaping () -> Void,
|
||||
openPremiumGift: @escaping () -> Void,
|
||||
openPremiumGift: @escaping ([EnginePeer.Id]) -> Void,
|
||||
openActiveSessions: @escaping () -> Void,
|
||||
openBirthdaySetup: @escaping () -> Void,
|
||||
performActiveSessionAction: @escaping (NewSessionReview, Bool) -> Void,
|
||||
openChatFolderUpdates: @escaping () -> Void,
|
||||
hideChatFolderUpdates: @escaping () -> Void,
|
||||
@ -196,6 +198,7 @@ public final class ChatListNodeInteraction {
|
||||
self.openPremiumIntro = openPremiumIntro
|
||||
self.openPremiumGift = openPremiumGift
|
||||
self.openActiveSessions = openActiveSessions
|
||||
self.openBirthdaySetup = openBirthdaySetup
|
||||
self.performActiveSessionAction = performActiveSessionAction
|
||||
self.openChatFolderUpdates = openChatFolderUpdates
|
||||
self.hideChatFolderUpdates = hideChatFolderUpdates
|
||||
@ -732,7 +735,11 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
|
||||
nodeInteraction?.openPremiumIntro()
|
||||
case .xmasPremiumGift:
|
||||
nodeInteraction?.openPremiumGift()
|
||||
nodeInteraction?.openPremiumGift([])
|
||||
case .setupBirthday:
|
||||
nodeInteraction?.openBirthdaySetup()
|
||||
case let .birthdayPremiumGift(peers):
|
||||
nodeInteraction?.openPremiumGift(peers.map { $0.id })
|
||||
case .reviewLogin:
|
||||
break
|
||||
}
|
||||
@ -1064,7 +1071,11 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
|
||||
nodeInteraction?.openPremiumIntro()
|
||||
case .xmasPremiumGift:
|
||||
nodeInteraction?.openPremiumGift()
|
||||
nodeInteraction?.openPremiumGift([])
|
||||
case .setupBirthday:
|
||||
nodeInteraction?.openBirthdaySetup()
|
||||
case let .birthdayPremiumGift(peers):
|
||||
nodeInteraction?.openPremiumGift(peers.map { $0.id })
|
||||
case .reviewLogin:
|
||||
break
|
||||
}
|
||||
@ -1682,11 +1693,11 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads, forceDark: false, dismissed: nil)
|
||||
self.push?(controller)
|
||||
}, openPremiumGift: { [weak self] in
|
||||
}, openPremiumGift: { [weak self] peerIds in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .chatList, completion: nil)
|
||||
let controller = self.context.sharedContext.makePremiumGiftController(context: self.context, source: .chatList(peerIds), completion: nil)
|
||||
self.push?(controller)
|
||||
}, openActiveSessions: { [weak self] in
|
||||
guard let self else {
|
||||
@ -1707,6 +1718,8 @@ public final class ChatListNode: ListView {
|
||||
let recentSessionsController = self.context.sharedContext.makeRecentSessionsController(context: self.context, activeSessionsContext: activeSessionsContext)
|
||||
self.push?(recentSessionsController)
|
||||
})
|
||||
}, openBirthdaySetup: {
|
||||
|
||||
}, performActiveSessionAction: { [weak self] newSessionReview, isPositive in
|
||||
guard let self else {
|
||||
return
|
||||
@ -1785,6 +1798,12 @@ public final class ChatListNode: ListView {
|
||||
self.present?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, timeout: 5.0, customUndoText: nil), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}))
|
||||
case .setupBirthday:
|
||||
//TODO:localize
|
||||
let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .setupBirthday).startStandalone()
|
||||
self.present?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.ChatList_PremiumGiftInSettingsInfo, timeout: 5.0, customUndoText: nil), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}))
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1899,7 +1918,9 @@ public final class ChatListNode: ListView {
|
||||
return .single(.setupPassword)
|
||||
}
|
||||
}
|
||||
if suggestions.contains(.xmasPremiumGift) {
|
||||
if suggestions.contains(.setupBirthday) {
|
||||
return .single(.setupBirthday)
|
||||
} else if suggestions.contains(.xmasPremiumGift) {
|
||||
return .single(.xmasPremiumGift)
|
||||
} else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||
return inAppPurchaseManager.availableProducts
|
||||
|
@ -86,6 +86,8 @@ public enum ChatListNotice: Equatable {
|
||||
case premiumAnnualDiscount(discount: Int32)
|
||||
case premiumRestore(discount: Int32)
|
||||
case xmasPremiumGift
|
||||
case setupBirthday
|
||||
case birthdayPremiumGift(peers: [EnginePeer])
|
||||
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
|
||||
}
|
||||
|
||||
|
@ -220,6 +220,23 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
||||
case .xmasPremiumGift:
|
||||
titleString = parseMarkdownIntoAttributedString(item.strings.ChatList_PremiumXmasGiftTitle, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.accentTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), linkAttribute: { _ in return nil }))
|
||||
textString = NSAttributedString(string: item.strings.ChatList_PremiumXmasGiftText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
case .setupBirthday:
|
||||
//TODO:localize
|
||||
titleString = NSAttributedString(string: "Add your birthday! 🎂", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)
|
||||
textString = NSAttributedString(string: "Let your contacts know when you're celebrating.", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
case let .birthdayPremiumGift(peers):
|
||||
//TODO:localize
|
||||
let title: String
|
||||
let text: String
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
title = "It's \(peer.compactDisplayTitle)'s [birthday]() today! 🎂"
|
||||
text = "Gift them Telegram Premium."
|
||||
} else {
|
||||
title = "\(peers.count) contacts have [birthdays]() today! 🎂"
|
||||
text = "Gift them Telegram Premium."
|
||||
}
|
||||
titleString = parseMarkdownIntoAttributedString(title, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.accentTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), linkAttribute: { _ in return nil }))
|
||||
textString = NSAttributedString(string: text, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
case let .reviewLogin(newSessionReview, totalCount):
|
||||
spacing = 2.0
|
||||
alignment = .center
|
||||
|
@ -231,52 +231,17 @@ public enum ChatRecordedMediaPreview: Equatable {
|
||||
case video(Video)
|
||||
}
|
||||
|
||||
public final class ChatManagingBot: Equatable {
|
||||
public let bot: EnginePeer
|
||||
public let isPaused: Bool
|
||||
public let canReply: Bool
|
||||
public let settingsUrl: String?
|
||||
|
||||
public init(bot: EnginePeer, isPaused: Bool, canReply: Bool, settingsUrl: String?) {
|
||||
self.bot = bot
|
||||
self.isPaused = isPaused
|
||||
self.canReply = canReply
|
||||
self.settingsUrl = settingsUrl
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatManagingBot, rhs: ChatManagingBot) -> Bool {
|
||||
if lhs === rhs {
|
||||
return true
|
||||
}
|
||||
if lhs.bot != rhs.bot {
|
||||
return false
|
||||
}
|
||||
if lhs.isPaused != rhs.isPaused {
|
||||
return false
|
||||
}
|
||||
if lhs.canReply != rhs.canReply {
|
||||
return false
|
||||
}
|
||||
if lhs.settingsUrl != rhs.settingsUrl {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatContactStatus: Equatable {
|
||||
public var canAddContact: Bool
|
||||
public var canReportIrrelevantLocation: Bool
|
||||
public var peerStatusSettings: PeerStatusSettings?
|
||||
public var invitedBy: Peer?
|
||||
public var managingBot: ChatManagingBot?
|
||||
|
||||
public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?, managingBot: ChatManagingBot?) {
|
||||
public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?) {
|
||||
self.canAddContact = canAddContact
|
||||
self.canReportIrrelevantLocation = canReportIrrelevantLocation
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
self.invitedBy = invitedBy
|
||||
self.managingBot = managingBot
|
||||
}
|
||||
|
||||
public var isEmpty: Bool {
|
||||
@ -305,9 +270,6 @@ public struct ChatContactStatus: Equatable {
|
||||
if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) {
|
||||
return false
|
||||
}
|
||||
if lhs.managingBot != rhs.managingBot {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -369,7 +369,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set<EnginePeer.Id>, peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool], authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool, storySubscriptions: EngineStorySubscriptions?, topPeers: [EnginePeer], interaction: ContactListNodeInteraction) -> [ContactListNodeEntry] {
|
||||
private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactListPeer], presences: [EnginePeer.Id: EnginePeer.Presence], presentation: ContactListPresentation, selectionState: ContactListNodeGroupSelectionState?, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, disabledPeerIds: Set<EnginePeer.Id>, peerRequiresPremiumForMessaging: [EnginePeer.Id: Bool], authorizationStatus: AccessType, warningSuppressed: (Bool, Bool), displaySortOptions: Bool, displayCallIcons: Bool, storySubscriptions: EngineStorySubscriptions?, topPeers: [EnginePeer], topSectionTitle: String?, interaction: ContactListNodeInteraction) -> [ContactListNodeEntry] {
|
||||
var entries: [ContactListNodeEntry] = []
|
||||
|
||||
var commonHeader: ListViewItemHeader?
|
||||
@ -528,7 +528,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
if !topPeers.isEmpty {
|
||||
let hasDeselectAll = !(selectionState?.selectedPeerIndices ?? [:]).isEmpty
|
||||
|
||||
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(strings.Premium_Gift_ContactSelection_FrequentContacts.uppercased(), AnyHashable(hasDeselectAll ? 1 : 0)), theme: theme, strings: strings, actionTitle: hasDeselectAll ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : nil, action: {
|
||||
let header: ListViewItemHeader? = ChatListSearchItemHeader(type: .text(topSectionTitle ?? strings.Premium_Gift_ContactSelection_FrequentContacts.uppercased(), AnyHashable(hasDeselectAll ? 1 : 0)), theme: theme, strings: strings, actionTitle: hasDeselectAll ? strings.Premium_Gift_ContactSelection_DeselectAll.uppercased() : nil, action: {
|
||||
interaction.deselectAll()
|
||||
})
|
||||
|
||||
@ -722,8 +722,14 @@ public enum ContactListPresentation {
|
||||
}
|
||||
}
|
||||
|
||||
public enum TopPeers {
|
||||
case none
|
||||
case recent
|
||||
case custom(title: String, peerIds: [EnginePeer.Id])
|
||||
}
|
||||
|
||||
case orderedByPresence(options: [ContactListAdditionalOption])
|
||||
case natural(options: [ContactListAdditionalOption], includeChatList: Bool, topPeers: Bool)
|
||||
case natural(options: [ContactListAdditionalOption], includeChatList: Bool, topPeers: TopPeers)
|
||||
case search(Search)
|
||||
|
||||
public var sortOrder: ContactsSortOrder? {
|
||||
@ -1110,11 +1116,11 @@ public final class ContactListNode: ASDisplayNode {
|
||||
|> mapToSignal { presentation in
|
||||
var generateSections = false
|
||||
var includeChatList = false
|
||||
var displayTopPeers = false
|
||||
if case let .natural(_, includeChatListValue, displayTopPeersValue) = presentation {
|
||||
var displayTopPeers: ContactListPresentation.TopPeers = .none
|
||||
if case let .natural(_, includeChatListValue, topPeersValue) = presentation {
|
||||
generateSections = true
|
||||
includeChatList = includeChatListValue
|
||||
displayTopPeers = displayTopPeersValue
|
||||
displayTopPeers = topPeersValue
|
||||
}
|
||||
|
||||
if case let .search(search) = presentation {
|
||||
@ -1421,7 +1427,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
peers.append(.deviceContact(stableId, contact.0))
|
||||
}
|
||||
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging, authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil, topPeers: [], interaction: interaction)
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging, authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil, topPeers: [], topSectionTitle: nil, interaction: interaction)
|
||||
let previous = previousEntries.swap(entries)
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch))
|
||||
}
|
||||
@ -1481,11 +1487,36 @@ public final class ContactListNode: ASDisplayNode {
|
||||
chatListSignal = .single([])
|
||||
}
|
||||
|
||||
let recentPeers: Signal<RecentPeers, NoError>
|
||||
if displayTopPeers {
|
||||
recentPeers = context.engine.peers.recentPeers()
|
||||
} else {
|
||||
recentPeers = .single(.disabled)
|
||||
let topPeers: Signal<[EnginePeer], NoError>
|
||||
let topPeersSectionTitle: String?
|
||||
switch displayTopPeers {
|
||||
case .recent:
|
||||
topPeers = context.engine.peers.recentPeers()
|
||||
|> map { recentPeers -> [EnginePeer] in
|
||||
var topPeers: [EnginePeer] = []
|
||||
if case let .peers(peers) = recentPeers {
|
||||
topPeers = peers.map(EnginePeer.init)
|
||||
}
|
||||
return topPeers
|
||||
}
|
||||
topPeersSectionTitle = nil
|
||||
case let .custom(title, peerIds):
|
||||
topPeers = context.engine.data.get(
|
||||
EngineDataMap(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||
)
|
||||
|> map { peers in
|
||||
var result: [EnginePeer] = []
|
||||
for peer in peers.values {
|
||||
if let peer {
|
||||
result.append(peer)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
topPeersSectionTitle = title
|
||||
case .none:
|
||||
topPeers = .single([])
|
||||
topPeersSectionTitle = nil
|
||||
}
|
||||
|
||||
return (combineLatest(
|
||||
@ -1497,9 +1528,9 @@ public final class ContactListNode: ASDisplayNode {
|
||||
contactsAuthorization.get(),
|
||||
contactsWarningSuppressed.get(),
|
||||
self.storySubscriptions.get(),
|
||||
recentPeers
|
||||
topPeers
|
||||
)
|
||||
|> mapToQueue { view, chatListPeers, selectionState, pendingRemovalPeerIds, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, recentPeers -> Signal<ContactsListNodeTransition, NoError> in
|
||||
|> mapToQueue { view, chatListPeers, selectionState, pendingRemovalPeerIds, presentationData, authorizationStatus, warningSuppressed, storySubscriptions, topPeers -> Signal<ContactsListNodeTransition, NoError> in
|
||||
let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
|
||||
if !view.2.isEmpty {
|
||||
context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys))
|
||||
@ -1540,16 +1571,11 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
var topPeers: [EnginePeer] = []
|
||||
if case let .peers(peers) = recentPeers {
|
||||
topPeers = peers.map(EnginePeer.init)
|
||||
}
|
||||
|
||||
var isEmpty = false
|
||||
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty {
|
||||
isEmpty = true
|
||||
}
|
||||
let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers, interaction: interaction)
|
||||
let entries = contactListNodeEntries(accountPeer: view.1, peers: peers, presences: view.0.presences, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: view.2, authorizationStatus: authorizationStatus, warningSuppressed: warningSuppressed, displaySortOptions: displaySortOptions, displayCallIcons: displayCallIcons, storySubscriptions: storySubscriptions, topPeers: topPeers, topSectionTitle: topPeersSectionTitle, interaction: interaction)
|
||||
let previous = previousEntries.swap(entries)
|
||||
let previousSelection = previousSelectionState.swap(selectionState)
|
||||
let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds)
|
||||
|
@ -108,7 +108,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
case .presence:
|
||||
return .orderedByPresence(options: options)
|
||||
case .natural:
|
||||
return .natural(options: options, includeChatList: false, topPeers: false)
|
||||
return .natural(options: options, includeChatList: false, topPeers: .none)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,8 +48,6 @@ public enum ContextMenuActionItemTextColor {
|
||||
public enum ContextMenuActionResult {
|
||||
case `default`
|
||||
case dismissWithoutContent
|
||||
/// Temporary
|
||||
static var safeStreamRecordingDismissWithoutContent: ContextMenuActionResult { .dismissWithoutContent }
|
||||
|
||||
case custom(ContainedViewLayoutTransition)
|
||||
}
|
||||
@ -116,9 +114,11 @@ public final class ContextMenuActionItem {
|
||||
|
||||
public struct IconAnimation: Equatable {
|
||||
public var name: String
|
||||
public var loop: Bool
|
||||
|
||||
public init(name: String) {
|
||||
public init(name: String, loop: Bool = false) {
|
||||
self.name = name
|
||||
self.loop = loop
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ public protocol ContextControllerActionsStackItem: AnyObject {
|
||||
var dismissed: (() -> Void)? { get }
|
||||
}
|
||||
|
||||
protocol ContextControllerActionsListItemNode: ASDisplayNode {
|
||||
public protocol ContextControllerActionsListItemNode: ASDisplayNode {
|
||||
func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void)
|
||||
|
||||
func canBeHighlighted() -> Bool
|
||||
@ -82,7 +82,7 @@ protocol ContextControllerActionsListItemNode: ASDisplayNode {
|
||||
func performAction()
|
||||
}
|
||||
|
||||
private final class ContextControllerActionsListActionItemNode: HighlightTrackingButtonNode, ContextControllerActionsListItemNode {
|
||||
public final class ContextControllerActionsListActionItemNode: HighlightTrackingButtonNode, ContextControllerActionsListItemNode {
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let requestDismiss: (ContextMenuActionResult) -> Void
|
||||
private let requestUpdateAction: (AnyHashable, ContextMenuActionItem) -> Void
|
||||
@ -103,7 +103,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
|
||||
private var iconDisposable: Disposable?
|
||||
|
||||
init(
|
||||
public init(
|
||||
getController: @escaping () -> ContextControllerProtocol?,
|
||||
requestDismiss: @escaping (ContextMenuActionResult) -> Void,
|
||||
requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void,
|
||||
@ -168,7 +168,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
self.iconDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.isExclusiveTouch = true
|
||||
@ -196,19 +196,19 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
))
|
||||
}
|
||||
|
||||
func canBeHighlighted() -> Bool {
|
||||
public func canBeHighlighted() -> Bool {
|
||||
return self.item.action != nil
|
||||
}
|
||||
|
||||
func updateIsHighlighted(isHighlighted: Bool) {
|
||||
public func updateIsHighlighted(isHighlighted: Bool) {
|
||||
self.highlightBackgroundNode.alpha = isHighlighted ? 1.0 : 0.0
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
public func performAction() {
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.titleLabelNode.tapAttributeAction != nil {
|
||||
if let result = self.titleLabelNode.hitTest(self.view.convert(point, to: self.titleLabelNode.view), with: event) {
|
||||
return result
|
||||
@ -223,7 +223,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
self.accessibilityLabel = item.text
|
||||
}
|
||||
|
||||
func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) {
|
||||
public func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let verticalInset: CGFloat = 11.0
|
||||
let titleSubtitleSpacing: CGFloat = 1.0
|
||||
@ -365,8 +365,8 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: iconAnimation.name),
|
||||
color: titleColor,
|
||||
startingPosition: .end,
|
||||
loop: false
|
||||
startingPosition: iconAnimation.loop ? .begin : .end,
|
||||
loop: iconAnimation.loop
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: animatedIconSize
|
||||
|
@ -95,8 +95,9 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
}, openPremiumIntro: {
|
||||
}, openPremiumGift: {
|
||||
}, openPremiumGift: { _ in
|
||||
}, openActiveSessions: {
|
||||
}, openBirthdaySetup: {
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {
|
||||
}, hideChatFolderUpdates: {
|
||||
|
@ -719,7 +719,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
|
||||
@objc private func createActionButtonPressed() {
|
||||
var proceedImpl: ((String, String?) -> Void)?
|
||||
let titleController = stickerPackEditTitleController(context: self.context, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, value: nil, maxLength: 128, apply: { [weak self] title in
|
||||
let titleController = stickerPackEditTitleController(context: self.context, title: self.presentationData.strings.ImportStickerPack_ChooseName, text: self.presentationData.strings.ImportStickerPack_ChooseNameDescription, placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, value: nil, maxLength: 64, apply: { [weak self] title in
|
||||
if let strongSelf = self, let title = title {
|
||||
strongSelf.shortNameSuggestionDisposable.set((strongSelf.context.engine.stickers.getStickerSetShortNameSuggestion(title: title)
|
||||
|> deliverOnMainQueue).start(next: { suggestedShortName in
|
||||
@ -735,7 +735,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let controller = importStickerPackShortNameController(context: strongSelf.context, title: strongSelf.presentationData.strings.ImportStickerPack_ChooseLink, text: strongSelf.presentationData.strings.ImportStickerPack_ChooseLinkDescription, placeholder: "", value: suggestedShortName, maxLength: 60, existingAlertController: titleController, apply: { [weak self] shortName in
|
||||
let controller = importStickerPackShortNameController(context: strongSelf.context, title: strongSelf.presentationData.strings.ImportStickerPack_ChooseLink, text: strongSelf.presentationData.strings.ImportStickerPack_ChooseLinkDescription, placeholder: "", value: suggestedShortName, maxLength: 64, existingAlertController: titleController, apply: { [weak self] shortName in
|
||||
if let shortName = shortName {
|
||||
self?.createStickerSet(title: title, shortName: shortName)
|
||||
}
|
||||
|
@ -48,6 +48,14 @@ public func cachedPrivacyPage(context: AccountContext) -> Signal<ResolvedUrl, No
|
||||
return cachedInternalInstantPage(context: context, url: privacyUrl)
|
||||
}
|
||||
|
||||
public func cachedWebAppTermsPage(context: AccountContext) -> Signal<ResolvedUrl, NoError> {
|
||||
var privacyUrl = context.sharedContext.currentPresentationData.with { $0 }.strings.WebApp_TermsOfUse_URL
|
||||
if privacyUrl == "WebApp.TermsOfUse_URL" || privacyUrl.isEmpty {
|
||||
privacyUrl = "https://telegram.org/tos/mini-apps"
|
||||
}
|
||||
return cachedInternalInstantPage(context: context, url: privacyUrl)
|
||||
}
|
||||
|
||||
private func cachedInternalInstantPage(context: AccountContext, url: String) -> Signal<ResolvedUrl, NoError> {
|
||||
let (cachedUrl, anchor) = extractAnchor(string: url)
|
||||
return cachedInstantPage(engine: context.engine, url: cachedUrl)
|
||||
|
@ -488,8 +488,8 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
var thumbnailItem: StickerPackThumbnailItem?
|
||||
var resourceReference: MediaResourceReference?
|
||||
if let thumbnail = item.packInfo.thumbnail {
|
||||
if item.packInfo.flags.contains(.isAnimated) || item.packInfo.flags.contains(.isVideo) {
|
||||
thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, item.packInfo.flags.contains(.isVideo), item.packInfo.flags.contains(.isCustomTemplateEmoji))
|
||||
if thumbnail.typeHint != .generic {
|
||||
thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, thumbnail.typeHint == .video, item.packInfo.flags.contains(.isCustomTemplateEmoji))
|
||||
} else {
|
||||
thumbnailItem = .still(thumbnail)
|
||||
}
|
||||
@ -844,7 +844,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
var imageSize = PixelDimensions(width: 512, height: 512)
|
||||
var immediateThumbnailData: Data?
|
||||
if let data = item.packInfo.immediateThumbnailData {
|
||||
if item.packInfo.flags.contains(.isVideo) {
|
||||
if item.packInfo.thumbnail?.typeHint == .video || item.topItem?.file.isVideoSticker == true {
|
||||
imageSize = PixelDimensions(width: 100, height: 100)
|
||||
}
|
||||
immediateThumbnailData = data
|
||||
|
@ -21,23 +21,6 @@ public struct LocalAuth {
|
||||
case error(Error)
|
||||
}
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
public final class PrivateKey {
|
||||
public let publicKeyRepresentation: Data
|
||||
|
||||
fileprivate init() {
|
||||
self.publicKeyRepresentation = Data(count: 32)
|
||||
}
|
||||
|
||||
public func encrypt(data: Data) -> Data? {
|
||||
return data
|
||||
}
|
||||
|
||||
public func decrypt(data: Data) -> DecryptionResult {
|
||||
return .result(data)
|
||||
}
|
||||
}
|
||||
#else
|
||||
public final class PrivateKey {
|
||||
private let privateKey: SecKey
|
||||
private let publicKey: SecKey
|
||||
@ -81,7 +64,6 @@ public struct LocalAuth {
|
||||
return .result(result)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static var biometricAuthentication: LocalAuthBiometricAuthentication? {
|
||||
let context = LAContext()
|
||||
@ -175,18 +157,7 @@ public struct LocalAuth {
|
||||
return seedId;
|
||||
}
|
||||
|
||||
public static func getOrCreatePrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
if let key = self.getPrivateKey(baseAppBundleId: baseAppBundleId, keyId: keyId) {
|
||||
return key
|
||||
} else {
|
||||
return self.addPrivateKey(baseAppBundleId: baseAppBundleId, keyId: keyId)
|
||||
}
|
||||
}
|
||||
|
||||
private static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
#if targetEnvironment(simulator)
|
||||
return PrivateKey()
|
||||
#else
|
||||
public static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
guard let bundleSeedId = self.bundleSeedId() else {
|
||||
return nil
|
||||
}
|
||||
@ -225,7 +196,6 @@ public struct LocalAuth {
|
||||
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
||||
|
||||
return result
|
||||
#endif
|
||||
}
|
||||
|
||||
public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool {
|
||||
@ -251,10 +221,7 @@ public struct LocalAuth {
|
||||
return true
|
||||
}
|
||||
|
||||
private static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
#if targetEnvironment(simulator)
|
||||
return PrivateKey()
|
||||
#else
|
||||
public static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
guard let bundleSeedId = self.bundleSeedId() else {
|
||||
return nil
|
||||
}
|
||||
@ -295,6 +262,5 @@ public struct LocalAuth {
|
||||
|
||||
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
||||
return result
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -2248,7 +2248,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
if !items.isEmpty {
|
||||
items.append(.separator)
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, animationName: "anim_spoiler", action: { [weak self] _, f in
|
||||
items.append(.action(ContextMenuActionItem(text: hasGeneric ? strings.Attachment_EnableSpoiler : strings.Attachment_DisableSpoiler, icon: { _ in return nil }, iconAnimation: ContextMenuActionItem.IconAnimation(
|
||||
name: "anim_spoiler",
|
||||
loop: true
|
||||
), action: { [weak self] _, f in
|
||||
f(.default)
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
@property (nonatomic) NSUInteger internalServerErrorCount;
|
||||
@property (nonatomic) NSUInteger floodWaitSeconds;
|
||||
@property (nonatomic, strong) NSString *floodWaitErrorText;
|
||||
|
||||
@property (nonatomic) bool waitingForTokenExport;
|
||||
@property (nonatomic, strong) id waitingForRequestToComplete;
|
||||
|
@ -808,7 +808,7 @@
|
||||
}
|
||||
restartRequest = true;
|
||||
}
|
||||
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
|
||||
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound) {
|
||||
if (request.errorContext == nil)
|
||||
request.errorContext = [[MTRequestErrorContext alloc] init];
|
||||
|
||||
@ -821,32 +821,6 @@
|
||||
if ([scanner scanInt:&errorWaitTime])
|
||||
{
|
||||
request.errorContext.floodWaitSeconds = errorWaitTime;
|
||||
request.errorContext.floodWaitErrorText = rpcError.errorDescription;
|
||||
|
||||
if (request.shouldContinueExecutionWithErrorContext != nil)
|
||||
{
|
||||
if (request.shouldContinueExecutionWithErrorContext(request.errorContext))
|
||||
{
|
||||
restartRequest = true;
|
||||
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
restartRequest = true;
|
||||
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime);
|
||||
}
|
||||
}
|
||||
} else if ([rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
|
||||
int errorWaitTime = 0;
|
||||
|
||||
NSScanner *scanner = [[NSScanner alloc] initWithString:rpcError.errorDescription];
|
||||
[scanner scanUpToString:@"FLOOD_PREMIUM_WAIT_" intoString:nil];
|
||||
[scanner scanString:@"FLOOD_PREMIUM_WAIT_" intoString:nil];
|
||||
if ([scanner scanInt:&errorWaitTime])
|
||||
{
|
||||
request.errorContext.floodWaitSeconds = errorWaitTime;
|
||||
request.errorContext.floodWaitErrorText = rpcError.errorDescription;
|
||||
|
||||
if (request.shouldContinueExecutionWithErrorContext != nil)
|
||||
{
|
||||
|
@ -2999,7 +2999,6 @@ final class PostboxImpl {
|
||||
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
|
||||
self.valueBox.begin()
|
||||
let transaction = Transaction(queue: self.queue, postbox: self)
|
||||
self.afterBegin(transaction: transaction)
|
||||
@ -3014,7 +3013,6 @@ final class PostboxImpl {
|
||||
postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file), on:\(line)")
|
||||
}
|
||||
|
||||
|
||||
let _ = self.isInTransaction.swap(false)
|
||||
|
||||
if let currentUpdatedState = self.currentUpdatedState {
|
||||
|
@ -19,9 +19,8 @@ public func printOpenFiles() {
|
||||
var flags: Int32 = 0
|
||||
var fd: Int32 = 0
|
||||
var buf = Data(count: Int(MAXPATHLEN) + 1)
|
||||
let maxFd = min(1024, FD_SETSIZE)
|
||||
|
||||
while fd < maxFd {
|
||||
while fd < FD_SETSIZE {
|
||||
errno = 0;
|
||||
flags = fcntl(fd, F_GETFD, 0);
|
||||
if flags == -1 && errno != 0 {
|
||||
|
@ -848,7 +848,6 @@ struct PremiumIntroConfiguration {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
var businessPerks: [PremiumPerk] = []
|
||||
if let values = data["business_promo_order"] as? [String] {
|
||||
for value in values {
|
||||
|
@ -332,6 +332,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
public var displayTail: Bool = true
|
||||
public var forceTailToRight: Bool = false
|
||||
public var forceDark: Bool = false
|
||||
public var hideBackground: Bool = false
|
||||
|
||||
private var didAnimateIn: Bool = false
|
||||
public private(set) var isAnimatingOut: Bool = false
|
||||
@ -1900,7 +1901,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
externalExpansionView: self.view,
|
||||
customContentView: nil,
|
||||
useOpaqueTheme: false,
|
||||
hideBackground: false,
|
||||
hideBackground: self.hideBackground,
|
||||
stateContext: nil,
|
||||
addImage: nil
|
||||
)
|
||||
|
@ -222,7 +222,8 @@ 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
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: {
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {
|
||||
}, openBirthdaySetup: {
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
|
@ -371,7 +371,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: {
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {
|
||||
}, openBirthdaySetup: {
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
|
@ -211,7 +211,7 @@ private final class ShareContentInfoView: UIView {
|
||||
|
||||
let accentColor = params.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
|
||||
if self.arrowIcon == nil {
|
||||
if let templateImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") {
|
||||
if let templateImage = UIImage(bundleImageName: "Settings/TextArrowRight") {
|
||||
let scaleFactor: CGFloat = 0.8
|
||||
let imageSize = CGSize(width: floor(templateImage.size.width * scaleFactor), height: floor(templateImage.size.height * scaleFactor))
|
||||
self.arrowIcon = generateImage(imageSize, contextGenerator: { size, context in
|
||||
|
@ -297,8 +297,8 @@ public func preloadedStickerPackThumbnail(account: Account, info: StickerPackCol
|
||||
let signal = Signal<Bool, NoError> { subscriber in
|
||||
let fetched = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)).start()
|
||||
let dataDisposable: Disposable
|
||||
if info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) {
|
||||
dataDisposable = chatMessageAnimationData(mediaBox: account.postbox.mediaBox, resource: thumbnail.resource, isVideo: info.flags.contains(.isVideo), width: 80, height: 80, synchronousLoad: false).start(next: { data in
|
||||
if thumbnail.typeHint != .generic {
|
||||
dataDisposable = chatMessageAnimationData(mediaBox: account.postbox.mediaBox, resource: thumbnail.resource, isVideo: thumbnail.typeHint == .video, width: 80, height: 80, synchronousLoad: false).start(next: { data in
|
||||
if data.complete {
|
||||
subscriber.putNext(true)
|
||||
subscriber.putCompletion()
|
||||
|
@ -1140,6 +1140,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
|
||||
private let stickerPickerInputData = Promise<StickerPickerInput>()
|
||||
private func presentAddStickerOptions() {
|
||||
//TODO:localize
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ActionSheetButtonItem(title: "Create a New Sticker", color: .accent, action: { [weak actionSheet, weak self] in
|
||||
@ -1167,22 +1168,6 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
])])
|
||||
self.presentInGlobalOverlay(actionSheet, nil)
|
||||
|
||||
|
||||
let emojiItems = EmojiPagerContentComponent.emojiInputData(
|
||||
context: self.context,
|
||||
animationCache: self.context.animationCache,
|
||||
animationRenderer: self.context.animationRenderer,
|
||||
isStandalone: false,
|
||||
subject: .emoji,
|
||||
hasTrending: true,
|
||||
topReactionItems: [],
|
||||
areUnicodeEmojiEnabled: true,
|
||||
areCustomEmojiEnabled: true,
|
||||
chatPeerId: self.context.account.peerId,
|
||||
hasSearch: true,
|
||||
forceHasPremium: true
|
||||
)
|
||||
|
||||
let stickerItems = EmojiPagerContentComponent.stickerInputData(
|
||||
context: self.context,
|
||||
animationCache: self.context.animationCache,
|
||||
@ -1191,16 +1176,14 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers],
|
||||
chatPeerId: self.context.account.peerId,
|
||||
hasSearch: true,
|
||||
hasTrending: true,
|
||||
hasTrending: false,
|
||||
forceHasPremium: true
|
||||
)
|
||||
|
||||
let signal = combineLatest(
|
||||
queue: .mainQueue(),
|
||||
emojiItems,
|
||||
stickerItems
|
||||
) |> map { emoji, stickers -> StickerPickerInput in
|
||||
return StickerPickerInputData(emoji: emoji, stickers: stickers, gifs: nil)
|
||||
let signal = stickerItems
|
||||
|> deliverOnMainQueue
|
||||
|> map { stickers -> StickerPickerInput in
|
||||
return StickerPickerInputData(emoji: nil, stickers: stickers, gifs: nil)
|
||||
}
|
||||
|
||||
self.stickerPickerInputData.set(signal)
|
||||
@ -1224,7 +1207,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
context: context,
|
||||
source: result,
|
||||
transitionArguments: (transitionView, transitionRect, transitionImage),
|
||||
completion: { file in
|
||||
completion: { file, commit in
|
||||
dismissImpl?()
|
||||
let sticker = ImportSticker(
|
||||
resource: file.resource,
|
||||
@ -1236,6 +1219,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||
let _ = (context.engine.stickers.addStickerToStickerSet(packReference: packReference, sticker: sticker)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
commit()
|
||||
|
||||
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
|
||||
|
||||
@ -1300,7 +1285,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
context: context,
|
||||
source: initialFile,
|
||||
transitionArguments: nil,
|
||||
completion: { file in
|
||||
completion: { file, commit in
|
||||
let sticker = ImportSticker(
|
||||
resource: file.resource,
|
||||
emojis: ["😀"],
|
||||
@ -1312,6 +1297,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
|
||||
let _ = (context.engine.stickers.replaceSticker(previousSticker: .stickerPack(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), media: initialFile), sticker: sticker)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
commit()
|
||||
|
||||
let packController = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], loadedStickerPacks: [], parentNavigationController: navigationController, sendSticker: nil, sendEmoji: nil, actionPerformed: nil, dismissed: nil, getSourceRect: nil)
|
||||
(navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
|
||||
})
|
||||
@ -1327,7 +1314,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
let context = self.context
|
||||
//TODO:localize
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = stickerPackEditTitleController(context: context, title: "Edit Sticker Set Name", text: "Choose a new name for your set.", placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: self.updatedTitle ?? info.title, maxLength: 128, apply: { [weak self] title in
|
||||
let controller = stickerPackEditTitleController(context: context, title: "Edit Sticker Set Name", text: "Choose a new name for your set.", placeholder: self.presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: self.updatedTitle ?? info.title, maxLength: 64, apply: { [weak self] title in
|
||||
guard let self, let title else {
|
||||
return
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode {
|
||||
items.append(reaction)
|
||||
}
|
||||
|
||||
let selectedItems = ValuePromise<Set<MessageReaction.Reaction>>()
|
||||
let selectedItems = ValuePromise<Set<MessageReaction.Reaction>>(Set())
|
||||
//TODO:localize
|
||||
let reactionContextNode = ReactionContextNode(
|
||||
context: self.context,
|
||||
@ -440,6 +440,7 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode {
|
||||
layoutImpl?(transition)
|
||||
}
|
||||
)
|
||||
reactionContextNode.hideBackground = true
|
||||
reactionContextNode.displayTail = true
|
||||
reactionContextNode.forceTailToRight = true
|
||||
reactionContextNode.forceDark = true
|
||||
|
@ -671,7 +671,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) }
|
||||
dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) }
|
||||
dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) }
|
||||
dict[-1395233698] = { return Api.PeerSettings.parse_peerSettings($0) }
|
||||
dict[-1525149427] = { return Api.PeerSettings.parse_peerSettings($0) }
|
||||
dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) }
|
||||
dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) }
|
||||
dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }
|
||||
|
@ -898,28 +898,26 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum PeerSettings: TypeConstructorDescription {
|
||||
case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?)
|
||||
case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl):
|
||||
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1395233698)
|
||||
buffer.appendInt32(-1525149427)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(geoDistance!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 9) != 0 {serializeString(requestChatTitle!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 9) != 0 {serializeInt32(requestChatDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 13) != 0 {serializeInt64(businessBotId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 13) != 0 {serializeString(businessBotManageUrl!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl):
|
||||
return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any), ("businessBotId", businessBotId as Any), ("businessBotManageUrl", businessBotManageUrl as Any)])
|
||||
case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate):
|
||||
return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -932,18 +930,12 @@ public extension Api {
|
||||
if Int(_1!) & Int(1 << 9) != 0 {_3 = parseString(reader) }
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 9) != 0 {_4 = reader.readInt32() }
|
||||
var _5: Int64?
|
||||
if Int(_1!) & Int(1 << 13) != 0 {_5 = reader.readInt64() }
|
||||
var _6: String?
|
||||
if Int(_1!) & Int(1 << 13) != 0 {_6 = parseString(reader) }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 13) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 13) == 0) || _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6)
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -221,21 +221,6 @@ public extension Api.functions.account {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.account {
|
||||
static func disablePeerConnectedBot(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1581481689)
|
||||
peer.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "account.disablePeerConnectedBot", parameters: [("peer", String(describing: peer))]), 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 {
|
||||
static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
@ -1269,22 +1254,6 @@ public extension Api.functions.account {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.account {
|
||||
static func toggleConnectedBotPaused(peer: Api.InputPeer, paused: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1684934807)
|
||||
peer.serialize(buffer, true)
|
||||
paused.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "account.toggleConnectedBotPaused", parameters: [("peer", String(describing: peer)), ("paused", String(describing: paused))]), 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 {
|
||||
static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -144,13 +144,13 @@ public class UnauthorizedAccount {
|
||||
return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in
|
||||
return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self))
|
||||
}
|
||||
|> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration), NoError> in
|
||||
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration) in
|
||||
return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self), transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue)
|
||||
|> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in
|
||||
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in
|
||||
return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self))
|
||||
}
|
||||
}
|
||||
|> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<UnauthorizedAccount, NoError> in
|
||||
return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false, appConfiguration: appConfiguration)
|
||||
|> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal<UnauthorizedAccount, NoError> in
|
||||
return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false)
|
||||
|> map { network in
|
||||
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
|
||||
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
|
||||
@ -248,7 +248,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
||||
if let accountState = accountState {
|
||||
switch accountState {
|
||||
case let unauthorizedState as UnauthorizedAccountState:
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
|> map { network -> AccountResult in
|
||||
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
||||
}
|
||||
@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
||||
return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
|
||||
}
|
||||
|> mapToSignal { phoneNumber in
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
|> map { network -> AccountResult in
|
||||
return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary))
|
||||
}
|
||||
@ -267,7 +267,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
||||
}
|
||||
}
|
||||
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
|> map { network -> AccountResult in
|
||||
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
||||
}
|
||||
@ -889,11 +889,6 @@ public func accountBackupData(postbox: Postbox) -> Signal<AccountBackupData?, No
|
||||
}
|
||||
}
|
||||
|
||||
public enum NetworkSpeedLimitedEvent {
|
||||
case upload
|
||||
case download
|
||||
}
|
||||
|
||||
public class Account {
|
||||
static let sharedQueue = Queue(name: "Account-Shared")
|
||||
|
||||
@ -1524,8 +1519,7 @@ public func standaloneStateManager(
|
||||
proxySettings: proxySettings,
|
||||
networkSettings: networkSettings,
|
||||
phoneNumber: phoneNumber,
|
||||
useRequestTimeoutTimers: false,
|
||||
appConfiguration: .defaultValue
|
||||
useRequestTimeoutTimers: false
|
||||
)
|
||||
|> map { network -> AccountStateManager? in
|
||||
Logger.shared.log("StandaloneStateManager", "received network")
|
||||
|
@ -103,7 +103,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId)
|
||||
}
|
||||
|
||||
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<Void, UploadPartError> {
|
||||
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> {
|
||||
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
|
||||
if asBigPart {
|
||||
let totalParts: Int32
|
||||
@ -117,7 +117,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
|
||||
}
|
||||
|
||||
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, onFloodWaitError: onFloodWaitError, expectedResponseSize: nil)
|
||||
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, expectedResponseSize: nil)
|
||||
|> mapError { error -> UploadPartError in
|
||||
if error.errorCode == 400 {
|
||||
return .invalidMedia
|
||||
@ -130,7 +130,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<Void, UploadPartError> {
|
||||
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> {
|
||||
return Signal<Void, MTRpcError> { subscriber in
|
||||
let request = MTRequest()
|
||||
|
||||
@ -159,13 +159,6 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
request.dependsOnPasswordEntry = false
|
||||
|
||||
request.shouldContinueExecutionWithErrorContext = { errorContext in
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -302,7 +295,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
|> retryRequest
|
||||
}
|
||||
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<T, MTRpcError> {
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> {
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
request.expectedResponseSize = expectedResponseSize ?? 0
|
||||
@ -321,9 +314,6 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
@ -354,7 +344,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func requestWithAdditionalData<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> {
|
||||
func requestWithAdditionalData<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> {
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
request.expectedResponseSize = expectedResponseSize ?? 0
|
||||
@ -373,9 +363,6 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
@ -409,7 +396,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
let requestService = self.requestService
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
@ -429,9 +416,6 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
|
@ -104,14 +104,14 @@ private struct DownloadWrapper {
|
||||
self.useMainConnection = useMainConnection
|
||||
}
|
||||
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?, onFloodWaitError: @escaping (String) -> Void) -> Signal<(T, NetworkResponseInfo), MTRpcError> {
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), MTRpcError> {
|
||||
let target: MultiplexedRequestTarget
|
||||
if self.isCdn {
|
||||
target = .cdn(Int(self.datacenterId))
|
||||
} else {
|
||||
target = .main(Int(self.datacenterId))
|
||||
}
|
||||
return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize)
|
||||
return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, expectedResponseSize: expectedResponseSize)
|
||||
|> mapError { error, _ -> MTRpcError in
|
||||
return error
|
||||
}
|
||||
@ -192,7 +192,7 @@ private final class MultipartCdnHashSource {
|
||||
clusterContext = ClusterContext(disposable: disposable)
|
||||
self.clusterContexts[offset] = clusterContext
|
||||
|
||||
disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil, onFloodWaitError: { _ in })
|
||||
disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil)
|
||||
|> map { partHashes, _ -> [Int64: Data] in
|
||||
var parsedPartHashes: [Int64: Data] = [:]
|
||||
for part in partHashes {
|
||||
@ -322,7 +322,7 @@ private enum MultipartFetchSource {
|
||||
}
|
||||
}
|
||||
|
||||
func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool, onFloodWaitError: @escaping (String) -> Void) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> {
|
||||
func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> {
|
||||
var resourceReferenceValue: MediaResourceReference?
|
||||
switch resourceReference {
|
||||
case .forceRevalidate:
|
||||
@ -348,9 +348,7 @@ private enum MultipartFetchSource {
|
||||
case .revalidate:
|
||||
return .fail(.revalidateMediaReference)
|
||||
case let .location(parsedLocation):
|
||||
return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in
|
||||
onFloodWaitError(error)
|
||||
})
|
||||
return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit))
|
||||
|> mapError { error -> MultipartFetchDownloadError in
|
||||
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
|
||||
return .revalidateMediaReference
|
||||
@ -382,9 +380,7 @@ private enum MultipartFetchSource {
|
||||
}
|
||||
}
|
||||
case let .web(_, location):
|
||||
return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in
|
||||
onFloodWaitError(error)
|
||||
})
|
||||
return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit))
|
||||
|> mapError { error -> MultipartFetchDownloadError in
|
||||
if error.errorDescription == "WEBFILE_NOT_AVAILABLE" {
|
||||
return .webfileNotAvailable
|
||||
@ -408,9 +404,7 @@ private enum MultipartFetchSource {
|
||||
updatedLength += 1
|
||||
}
|
||||
|
||||
let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength), onFloodWaitError: { error in
|
||||
onFloodWaitError(error)
|
||||
})
|
||||
let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength))
|
||||
|> mapError { _ -> MultipartFetchDownloadError in
|
||||
return .generic
|
||||
}
|
||||
@ -729,13 +723,6 @@ private final class MultipartFetchManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func processFloodWaitError(error: String) {
|
||||
if error.hasPrefix("FLOOD_PREMIUM_WAIT") {
|
||||
self.network.addNetworkSpeedLimitedEvent(event: .download)
|
||||
}
|
||||
}
|
||||
|
||||
func checkState() {
|
||||
guard let currentIntervals = self.currentIntervals else {
|
||||
return
|
||||
@ -849,15 +836,7 @@ private final class MultipartFetchManager {
|
||||
}
|
||||
|
||||
let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound)
|
||||
let queue = self.queue
|
||||
let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground, onFloodWaitError: { [weak self] error in
|
||||
queue.async {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.processFloodWaitError(error: error)
|
||||
}
|
||||
})
|
||||
let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground)
|
||||
|> deliverOn(self.queue)
|
||||
let partDisposable = MetaDisposable()
|
||||
self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable)
|
||||
@ -940,7 +919,7 @@ private final class MultipartFetchManager {
|
||||
case let .cdn(_, _, fileToken, _, _, _, masterDownload, _):
|
||||
if !strongSelf.reuploadingToCdn {
|
||||
strongSelf.reuploadingToCdn = true
|
||||
let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil, onFloodWaitError: { _ in })
|
||||
let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil)
|
||||
|> map { result, _ -> [Api.FileHash] in
|
||||
return result
|
||||
}
|
||||
|
@ -470,21 +470,12 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload
|
||||
fetchedResource = .complete()
|
||||
}
|
||||
|
||||
let onFloodWaitError: (String) -> Void = { [weak network] error in
|
||||
guard let network else {
|
||||
return
|
||||
}
|
||||
if error.hasPrefix("FLOOD_PREMIUM_WAIT") {
|
||||
network.addNetworkSpeedLimitedEvent(event: .upload)
|
||||
}
|
||||
}
|
||||
|
||||
let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in
|
||||
switch uploadInterface {
|
||||
case let .download(download):
|
||||
return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError)
|
||||
return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression)
|
||||
case let .multiplexed(multiplexed, datacenterId, consumerId):
|
||||
return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError)
|
||||
return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression)
|
||||
}
|
||||
}, progress: { progress in
|
||||
subscriber.putNext(.progress(progress))
|
||||
|
@ -33,13 +33,12 @@ private final class RequestData {
|
||||
let tag: MediaResourceFetchTag?
|
||||
let continueInBackground: Bool
|
||||
let automaticFloodWait: Bool
|
||||
let onFloodWaitError: ((String) -> Void)?
|
||||
let expectedResponseSize: Int32?
|
||||
let deserializeResponse: (Buffer) -> Any?
|
||||
let completed: (Any, NetworkResponseInfo) -> Void
|
||||
let error: (MTRpcError, Double) -> Void
|
||||
|
||||
init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, onFloodWaitError: ((String) -> Void)?, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
|
||||
init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
|
||||
self.id = id
|
||||
self.consumerId = consumerId
|
||||
self.resourceId = resourceId
|
||||
@ -48,7 +47,6 @@ private final class RequestData {
|
||||
self.tag = tag
|
||||
self.continueInBackground = continueInBackground
|
||||
self.automaticFloodWait = automaticFloodWait
|
||||
self.onFloodWaitError = onFloodWaitError
|
||||
self.expectedResponseSize = expectedResponseSize
|
||||
self.payload = payload
|
||||
self.deserializeResponse = deserializeResponse
|
||||
@ -157,12 +155,12 @@ private final class MultiplexedRequestManagerContext {
|
||||
}
|
||||
}
|
||||
|
||||
func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
|
||||
func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
|
||||
let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground)
|
||||
|
||||
let requestId = self.nextId
|
||||
self.nextId += 1
|
||||
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in
|
||||
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in
|
||||
return data.2(buffer)
|
||||
}, completed: { result, info in
|
||||
completed(result, info)
|
||||
@ -256,7 +254,7 @@ private final class MultiplexedRequestManagerContext {
|
||||
let requestId = request.id
|
||||
selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable))
|
||||
let queue = self.queue
|
||||
disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, onFloodWaitError: request.onFloodWaitError, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in
|
||||
disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -356,13 +354,13 @@ final class MultiplexedRequestManager {
|
||||
return disposable
|
||||
}
|
||||
|
||||
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<T, MTRpcError> {
|
||||
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<T, MTRpcError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.context.with { context in
|
||||
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
||||
return data.2.parse(buffer)
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, _ in
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, _ in
|
||||
if let result = result as? T {
|
||||
subscriber.putNext(result)
|
||||
subscriber.putCompletion()
|
||||
@ -377,13 +375,13 @@ final class MultiplexedRequestManager {
|
||||
}
|
||||
}
|
||||
|
||||
func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.context.with { context in
|
||||
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
||||
return data.2.parse(buffer)
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, info in
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, info in
|
||||
if let result = result as? T {
|
||||
subscriber.putNext((result, info))
|
||||
subscriber.putCompletion()
|
||||
|
@ -459,7 +459,7 @@ public struct NetworkInitializationArguments {
|
||||
private let cloudDataContext = Atomic<CloudDataContext?>(value: nil)
|
||||
#endif
|
||||
|
||||
func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool, appConfiguration: AppConfiguration) -> Signal<Network, NoError> {
|
||||
func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool) -> Signal<Network, NoError> {
|
||||
return Signal { subscriber in
|
||||
let queue = Queue()
|
||||
queue.async {
|
||||
@ -612,11 +612,6 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
|
||||
let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false
|
||||
|
||||
let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures, useExperimentalFeatures: useExperimentalFeatures)
|
||||
|
||||
if let data = appConfiguration.data, let notifyInterval = data["upload_premium_speedup_notify_period"] as? Double {
|
||||
network.updateNetworkSpeedLimitedEventNotifyInterval(value: notifyInterval)
|
||||
}
|
||||
|
||||
appDataUpdatedImpl = { [weak network] data in
|
||||
guard let data = data else {
|
||||
return
|
||||
@ -739,22 +734,6 @@ public enum NetworkRequestResult<T> {
|
||||
case progress(Float, Int32)
|
||||
}
|
||||
|
||||
private final class NetworkSpeedLimitedEventState {
|
||||
var notifyInterval: Double = 60.0 * 60.0
|
||||
var lastNotifyTimestamp: Double = 0.0
|
||||
|
||||
func add(event: NetworkSpeedLimitedEvent) -> Bool {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
if self.lastNotifyTimestamp + self.notifyInterval < timestamp {
|
||||
self.lastNotifyTimestamp = timestamp
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
public let encryptionProvider: EncryptionProvider
|
||||
|
||||
@ -787,12 +766,6 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
return self._connectionStatus.get() |> distinctUntilChanged
|
||||
}
|
||||
|
||||
public var networkSpeedLimitedEvents: Signal<NetworkSpeedLimitedEvent, NoError> {
|
||||
return self.networkSpeedLimitedEventPipe.signal()
|
||||
}
|
||||
private let networkSpeedLimitedEventPipe = ValuePipe<NetworkSpeedLimitedEvent>()
|
||||
private let networkSpeedLimitedEventState = Atomic<NetworkSpeedLimitedEventState>(value: NetworkSpeedLimitedEventState())
|
||||
|
||||
public func dropConnectionStatus() {
|
||||
_connectionStatus.set(.single(.waitingForNetwork))
|
||||
}
|
||||
@ -853,18 +826,18 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
let array = NSMutableArray()
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .cdnConfig(publicKeys):
|
||||
for key in publicKeys {
|
||||
switch key {
|
||||
case let .cdnPublicKey(dcId, publicKey):
|
||||
if id == Int(dcId) {
|
||||
let dict = NSMutableDictionary()
|
||||
dict["key"] = publicKey
|
||||
dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey)
|
||||
array.add(dict)
|
||||
case let .cdnConfig(publicKeys):
|
||||
for key in publicKeys {
|
||||
switch key {
|
||||
case let .cdnPublicKey(dcId, publicKey):
|
||||
if id == Int(dcId) {
|
||||
let dict = NSMutableDictionary()
|
||||
dict["key"] = publicKey
|
||||
dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey)
|
||||
array.add(dict)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return array
|
||||
@ -894,12 +867,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
let isCdn: Bool
|
||||
let isMedia: Bool = true
|
||||
switch target {
|
||||
case let .main(id):
|
||||
datacenterId = id
|
||||
isCdn = false
|
||||
case let .cdn(id):
|
||||
datacenterId = id
|
||||
isCdn = true
|
||||
case let .main(id):
|
||||
datacenterId = id
|
||||
isCdn = false
|
||||
case let .cdn(id):
|
||||
datacenterId = id
|
||||
isCdn = true
|
||||
}
|
||||
return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground)
|
||||
}
|
||||
@ -907,7 +880,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
})
|
||||
|
||||
let shouldKeepConnectionSignal = self.shouldKeepConnection.get()
|
||||
|> distinctUntilChanged |> deliverOn(queue)
|
||||
|> distinctUntilChanged |> deliverOn(queue)
|
||||
self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if value {
|
||||
@ -994,11 +967,11 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address)
|
||||
|
||||
/*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false)
|
||||
if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) {
|
||||
} else {
|
||||
let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false)
|
||||
self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false)
|
||||
}*/
|
||||
if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) {
|
||||
} else {
|
||||
let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false)
|
||||
self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false)
|
||||
}*/
|
||||
|
||||
let currentSchemes = self.context.transportSchemesForDatacenter(withId: Int(datacenterId), media: false, enforceMedia: false, isProxy: false)
|
||||
var found = false
|
||||
@ -1015,7 +988,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public func requestWithAdditionalInfo<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<NetworkRequestResult<T>, MTRpcError> {
|
||||
public func requestWithAdditionalInfo<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal<NetworkRequestResult<T>, MTRpcError> {
|
||||
let requestService = self.requestService
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
@ -1033,9 +1006,6 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
@ -1086,8 +1056,8 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<T, MTRpcError> {
|
||||
|
||||
public func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> {
|
||||
let requestService = self.requestService
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
@ -1105,9 +1075,6 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
@ -1146,21 +1113,6 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNetworkSpeedLimitedEventNotifyInterval(value: Double) {
|
||||
let _ = self.networkSpeedLimitedEventState.with { state in
|
||||
state.notifyInterval = value
|
||||
}
|
||||
}
|
||||
|
||||
func addNetworkSpeedLimitedEvent(event: NetworkSpeedLimitedEvent) {
|
||||
let notify = self.networkSpeedLimitedEventState.with { state in
|
||||
return state.add(event: event)
|
||||
}
|
||||
if notify {
|
||||
self.networkSpeedLimitedEventPipe.putNext(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func retryRequest<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError> {
|
||||
|
@ -6,44 +6,33 @@ import SwiftSignalKit
|
||||
extension PeerStatusSettings {
|
||||
init(apiSettings: Api.PeerSettings) {
|
||||
switch apiSettings {
|
||||
case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl):
|
||||
var result = PeerStatusSettings.Flags()
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
result.insert(.canAddContact)
|
||||
}
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
result.insert(.canReport)
|
||||
}
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
result.insert(.canBlock)
|
||||
}
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
result.insert(.canShareContact)
|
||||
}
|
||||
if (flags & (1 << 4)) != 0 {
|
||||
result.insert(.addExceptionWhenAddingContact)
|
||||
}
|
||||
if (flags & (1 << 5)) != 0 {
|
||||
result.insert(.canReportIrrelevantGeoLocation)
|
||||
}
|
||||
if (flags & (1 << 7)) != 0 {
|
||||
result.insert(.autoArchived)
|
||||
}
|
||||
if (flags & (1 << 8)) != 0 {
|
||||
result.insert(.suggestAddMembers)
|
||||
}
|
||||
|
||||
var managingBot: ManagingBot?
|
||||
if let businessBotId {
|
||||
managingBot = ManagingBot(
|
||||
id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(businessBotId)),
|
||||
manageUrl: businessBotManageUrl,
|
||||
isPaused: (flags & (1 << 11)) != 0,
|
||||
canReply: (flags & (1 << 12)) != 0
|
||||
)
|
||||
}
|
||||
|
||||
self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0, managingBot: managingBot)
|
||||
case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate):
|
||||
var result = PeerStatusSettings.Flags()
|
||||
if (flags & (1 << 1)) != 0 {
|
||||
result.insert(.canAddContact)
|
||||
}
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
result.insert(.canReport)
|
||||
}
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
result.insert(.canBlock)
|
||||
}
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
result.insert(.canShareContact)
|
||||
}
|
||||
if (flags & (1 << 4)) != 0 {
|
||||
result.insert(.addExceptionWhenAddingContact)
|
||||
}
|
||||
if (flags & (1 << 5)) != 0 {
|
||||
result.insert(.canReportIrrelevantGeoLocation)
|
||||
}
|
||||
if (flags & (1 << 7)) != 0 {
|
||||
result.insert(.autoArchived)
|
||||
}
|
||||
if (flags & (1 << 8)) != 0 {
|
||||
result.insert(.suggestAddMembers)
|
||||
}
|
||||
self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -360,11 +360,11 @@ final class ChatHistoryPreloadManager {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
/*#if DEBUG
|
||||
#if DEBUG
|
||||
if "".isEmpty {
|
||||
return
|
||||
}
|
||||
#endif*/
|
||||
#endif
|
||||
|
||||
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
|
||||
for item in loadItems {
|
||||
|
@ -13,6 +13,7 @@ public enum ServerProvidedSuggestion: String {
|
||||
case annualPremium = "PREMIUM_ANNUAL"
|
||||
case restorePremium = "PREMIUM_RESTORE"
|
||||
case xmasPremiumGift = "PREMIUM_CHRISTMAS"
|
||||
case setupBirthday = "BIRTHDAY_SETUP"
|
||||
}
|
||||
|
||||
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
||||
|
@ -551,7 +551,7 @@ public final class CachedChannelData: CachedPeerData {
|
||||
var peerIds = Set<PeerId>()
|
||||
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil)
|
||||
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
} else {
|
||||
|
@ -203,7 +203,7 @@ public final class CachedGroupData: CachedPeerData {
|
||||
self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i")
|
||||
self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo]
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil)
|
||||
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
} else {
|
||||
|
@ -558,7 +558,7 @@ public final class CachedUserData: CachedPeerData {
|
||||
self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo
|
||||
self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil)
|
||||
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
} else {
|
||||
|
@ -16,20 +16,7 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
||||
public static let canReportIrrelevantGeoLocation = Flags(rawValue: 1 << 6)
|
||||
public static let autoArchived = Flags(rawValue: 1 << 7)
|
||||
public static let suggestAddMembers = Flags(rawValue: 1 << 8)
|
||||
}
|
||||
|
||||
public struct ManagingBot: Codable, Equatable {
|
||||
public var id: PeerId
|
||||
public var manageUrl: String?
|
||||
public var isPaused: Bool
|
||||
public var canReply: Bool
|
||||
|
||||
public init(id: PeerId, manageUrl: String?, isPaused: Bool, canReply: Bool) {
|
||||
self.id = id
|
||||
self.manageUrl = manageUrl
|
||||
self.isPaused = isPaused
|
||||
self.canReply = canReply
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public var flags: PeerStatusSettings.Flags
|
||||
@ -37,23 +24,20 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
||||
public var requestChatTitle: String?
|
||||
public var requestChatDate: Int32?
|
||||
public var requestChatIsChannel: Bool?
|
||||
public var managingBot: ManagingBot?
|
||||
|
||||
public init() {
|
||||
self.flags = PeerStatusSettings.Flags()
|
||||
self.geoDistance = nil
|
||||
self.requestChatTitle = nil
|
||||
self.requestChatDate = nil
|
||||
self.managingBot = nil
|
||||
}
|
||||
|
||||
public init(flags: PeerStatusSettings.Flags, geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil, managingBot: ManagingBot?) {
|
||||
public init(flags: PeerStatusSettings.Flags, geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil) {
|
||||
self.flags = flags
|
||||
self.geoDistance = geoDistance
|
||||
self.requestChatTitle = requestChatTitle
|
||||
self.requestChatDate = requestChatDate
|
||||
self.requestChatIsChannel = requestChatIsChannel
|
||||
self.managingBot = managingBot
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
@ -62,7 +46,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
||||
self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle")
|
||||
self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate")
|
||||
self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel")
|
||||
self.managingBot = decoder.decodeCodable(ManagingBot.self, forKey: "managingBot")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -87,11 +70,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "requestChatIsChannel")
|
||||
}
|
||||
if let managingBot = self.managingBot {
|
||||
encoder.encodeCodable(managingBot, forKey: "managingBot")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "managingBot")
|
||||
}
|
||||
}
|
||||
|
||||
public func contains(_ member: PeerStatusSettings.Flags) -> Bool {
|
||||
|
@ -21,12 +21,6 @@ public struct StickerPackCollectionInfoFlags: OptionSet {
|
||||
if flags.contains(StickerPackCollectionInfoFlags.isOfficial) {
|
||||
rawValue |= StickerPackCollectionInfoFlags.isOfficial.rawValue
|
||||
}
|
||||
if flags.contains(StickerPackCollectionInfoFlags.isAnimated) {
|
||||
rawValue |= StickerPackCollectionInfoFlags.isAnimated.rawValue
|
||||
}
|
||||
if flags.contains(StickerPackCollectionInfoFlags.isVideo) {
|
||||
rawValue |= StickerPackCollectionInfoFlags.isVideo.rawValue
|
||||
}
|
||||
if flags.contains(StickerPackCollectionInfoFlags.isEmoji) {
|
||||
rawValue |= StickerPackCollectionInfoFlags.isEmoji.rawValue
|
||||
}
|
||||
@ -39,8 +33,6 @@ public struct StickerPackCollectionInfoFlags: OptionSet {
|
||||
|
||||
public static let isMasks = StickerPackCollectionInfoFlags(rawValue: 1 << 0)
|
||||
public static let isOfficial = StickerPackCollectionInfoFlags(rawValue: 1 << 1)
|
||||
public static let isAnimated = StickerPackCollectionInfoFlags(rawValue: 1 << 2)
|
||||
public static let isVideo = StickerPackCollectionInfoFlags(rawValue: 1 << 3)
|
||||
public static let isEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 4)
|
||||
public static let isAvailableAsChannelStatus = StickerPackCollectionInfoFlags(rawValue: 1 << 5)
|
||||
public static let isCustomTemplateEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 6)
|
||||
|
@ -360,20 +360,36 @@ public final class TelegramMediaImage: Media, Equatable, Codable {
|
||||
}
|
||||
|
||||
public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, CustomStringConvertible {
|
||||
public enum TypeHint: Int32 {
|
||||
case generic
|
||||
case animated
|
||||
case video
|
||||
}
|
||||
|
||||
public let dimensions: PixelDimensions
|
||||
public let resource: TelegramMediaResource
|
||||
public let progressiveSizes: [Int32]
|
||||
public let immediateThumbnailData: Data?
|
||||
public let hasVideo: Bool
|
||||
public let isPersonal: Bool
|
||||
public let typeHint: TypeHint
|
||||
|
||||
public init(dimensions: PixelDimensions, resource: TelegramMediaResource, progressiveSizes: [Int32], immediateThumbnailData: Data?, hasVideo: Bool, isPersonal: Bool) {
|
||||
public init(
|
||||
dimensions: PixelDimensions,
|
||||
resource: TelegramMediaResource,
|
||||
progressiveSizes: [Int32],
|
||||
immediateThumbnailData: Data?,
|
||||
hasVideo: Bool = false,
|
||||
isPersonal: Bool = false,
|
||||
typeHint: TypeHint = .generic
|
||||
) {
|
||||
self.dimensions = dimensions
|
||||
self.resource = resource
|
||||
self.progressiveSizes = progressiveSizes
|
||||
self.immediateThumbnailData = immediateThumbnailData
|
||||
self.hasVideo = hasVideo
|
||||
self.isPersonal = isPersonal
|
||||
self.typeHint = typeHint
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
@ -383,6 +399,7 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C
|
||||
self.immediateThumbnailData = decoder.decodeDataForKey("th")
|
||||
self.hasVideo = decoder.decodeBoolForKey("hv", orElse: false)
|
||||
self.isPersonal = decoder.decodeBoolForKey("ip", orElse: false)
|
||||
self.typeHint = TypeHint(rawValue: decoder.decodeInt32ForKey("th", orElse: 0)) ?? .generic
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
@ -397,6 +414,7 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C
|
||||
}
|
||||
encoder.encodeBool(self.hasVideo, forKey: "hv")
|
||||
encoder.encodeBool(self.isPersonal, forKey: "ip")
|
||||
encoder.encodeInt32(self.typeHint.rawValue, forKey: "th")
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
@ -422,6 +440,9 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C
|
||||
if self.isPersonal != other.isPersonal {
|
||||
return false
|
||||
}
|
||||
if self.typeHint != other.typeHint {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ public final class CachedSecretChatData: CachedPeerData {
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil)
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil)
|
||||
} else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
} else {
|
||||
|
@ -90,19 +90,15 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId:
|
||||
var items: [RenderedChannelParticipant] = []
|
||||
switch result {
|
||||
case let .channelParticipants(_, participants, chats, users):
|
||||
postboxLog("channel users insertion started, count: \(participants.count)")
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||
postboxLog("channel users parsed")
|
||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||
postboxLog("channel users postbox updated, started mapping ids")
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
for id in parsedPeers.allIds {
|
||||
if let peer = transaction.getPeer(id) {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
}
|
||||
postboxLog("channel users finish mapping, started updating participants")
|
||||
|
||||
|
||||
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
|
||||
if let peer = parsedPeers.get(participant.peerId) {
|
||||
var renderedPresences: [PeerId: PeerPresence] = [:]
|
||||
@ -112,7 +108,6 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId:
|
||||
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: renderedPresences))
|
||||
}
|
||||
}
|
||||
postboxLog("channel participants finish updating")
|
||||
case .channelParticipantsNotModified:
|
||||
return nil
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ func _internal_dismissPeerStatusOptions(account: Account, peerId: PeerId) -> Sig
|
||||
if let current = current as? CachedUserData {
|
||||
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
|
||||
peerStatusSettings.flags = []
|
||||
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
|
||||
return current.withUpdatedPeerStatusSettings(PeerStatusSettings(flags: []))
|
||||
} else if let current = current as? CachedGroupData {
|
||||
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
|
||||
peerStatusSettings.flags = []
|
||||
|
@ -56,12 +56,12 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, accountP
|
||||
var peerStatusSettings: PeerStatusSettings
|
||||
if let peer = transaction.getPeer(peer.id), let associatedPeerId = peer.associatedPeerId, !transaction.isPeerContact(peerId: associatedPeerId) {
|
||||
if let peer = peer as? TelegramSecretChat, case .creator = peer.role {
|
||||
peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil)
|
||||
peerStatusSettings = PeerStatusSettings(flags: [])
|
||||
} else {
|
||||
peerStatusSettings = PeerStatusSettings(flags: [.canReport], managingBot: nil)
|
||||
peerStatusSettings = PeerStatusSettings(flags: [.canReport])
|
||||
}
|
||||
} else {
|
||||
peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil)
|
||||
peerStatusSettings = PeerStatusSettings(flags: [])
|
||||
}
|
||||
|
||||
transaction.updatePeerCachedData(peerIds: [peer.id], update: { peerId, current in
|
||||
|
@ -194,7 +194,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
|
||||
inputStickers.append(.inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords))
|
||||
inputStickers.append(.inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords))
|
||||
}
|
||||
var thumbnailDocument: Api.InputDocument?
|
||||
if thumbnail != nil, let resource = resources.last {
|
||||
@ -307,7 +307,7 @@ func _internal_addStickerToStickerSet(account: Account, packReference: StickerPa
|
||||
if sticker.keywords.count > 0 {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords)
|
||||
let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords)
|
||||
|
||||
return account.network.request(Api.functions.stickers.addStickerToSet(stickerset: packReference.apiInputStickerSet, sticker: inputSticker))
|
||||
|> mapError { error -> AddStickerToSetError in
|
||||
@ -416,7 +416,7 @@ func _internal_replaceSticker(account: Account, previousSticker: FileMediaRefere
|
||||
if sticker.keywords.count > 0 {
|
||||
flags |= (1 << 1)
|
||||
}
|
||||
let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords)
|
||||
let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.joined(), maskCoords: nil, keywords: sticker.keywords)
|
||||
|
||||
return account.network.request(Api.functions.stickers.replaceSticker(sticker: .inputDocument(id: previousResource.fileId, accessHash: previousResource.accessHash, fileReference: Buffer(data: previousResource.fileReference ?? Data())), newSticker: inputSticker))
|
||||
|> mapError { error -> ReplaceStickerError in
|
||||
|
@ -5,19 +5,31 @@ import SwiftSignalKit
|
||||
import MtProtoKit
|
||||
|
||||
func telegramStickerPackThumbnailRepresentationFromApiSizes(datacenterId: Int32, thumbVersion: Int32?, sizes: [Api.PhotoSize]) -> (immediateThumbnail: Data?, representations: [TelegramMediaImageRepresentation]) {
|
||||
func stickerTypeHint(for type: String) -> TelegramMediaImageRepresentation.TypeHint {
|
||||
switch type {
|
||||
case "s":
|
||||
return .generic
|
||||
case "a":
|
||||
return .animated
|
||||
case "v":
|
||||
return .video
|
||||
default:
|
||||
return .generic
|
||||
}
|
||||
}
|
||||
var immediateThumbnailData: Data?
|
||||
var representations: [TelegramMediaImageRepresentation] = []
|
||||
for size in sizes {
|
||||
switch size {
|
||||
case let .photoCachedSize(_, w, h, _):
|
||||
case let .photoCachedSize(type, w, h, _):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil)
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
|
||||
case let .photoSize(_, w, h, _):
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, typeHint: stickerTypeHint(for: type)))
|
||||
case let .photoSize(type, w, h, _):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil)
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
|
||||
case let .photoSizeProgressive(_, w, h, sizes):
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, typeHint: stickerTypeHint(for: type)))
|
||||
case let .photoSizeProgressive(type, w, h, sizes):
|
||||
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil)
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes, immediateThumbnailData: nil, hasVideo: false, isPersonal: false))
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: sizes, immediateThumbnailData: nil, typeHint: stickerTypeHint(for: type)))
|
||||
case let .photoPathSize(_, data):
|
||||
immediateThumbnailData = data.makeData()
|
||||
case .photoStrippedSize:
|
||||
@ -40,12 +52,6 @@ extension StickerPackCollectionInfo {
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
setFlags.insert(.isMasks)
|
||||
}
|
||||
if (flags & (1 << 5)) != 0 {
|
||||
setFlags.insert(.isAnimated)
|
||||
}
|
||||
if (flags & (1 << 6)) != 0 {
|
||||
setFlags.insert(.isVideo)
|
||||
}
|
||||
if (flags & (1 << 7)) != 0 {
|
||||
setFlags.insert(.isEmoji)
|
||||
}
|
||||
|
@ -620,10 +620,12 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
},
|
||||
openPremiumIntro: {
|
||||
},
|
||||
openPremiumGift: {
|
||||
openPremiumGift: { _ in
|
||||
},
|
||||
openActiveSessions: {
|
||||
},
|
||||
openBirthdaySetup: {
|
||||
},
|
||||
performActiveSessionAction: { _, _ in
|
||||
},
|
||||
openChatFolderUpdates: {
|
||||
|
@ -156,18 +156,6 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
|
||||
}
|
||||
}
|
||||
|
||||
if authorTitle == nil {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? InlineBusinessBotMessageAttribute {
|
||||
if let title = attribute.title {
|
||||
authorTitle = title
|
||||
} else if let peerId = attribute.peerId, let peer = message.peers[peerId] {
|
||||
authorTitle = peer.debugDisplayTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
|
||||
authorTitle = nil
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
|
||||
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings
|
||||
|
||||
let stickerItems = EmojiPagerContentComponent.stickerInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, stickerNamespaces: stickerNamespaces, stickerOrderedItemListCollectionIds: stickerOrderedItemListCollectionIds, chatPeerId: chatPeerId, hasSearch: hasSearch, hasTrending: hasTrending, forceHasPremium: false, hideBackground: hideBackground)
|
||||
let stickerItems = EmojiPagerContentComponent.stickerInputData(context: context, animationCache: animationCache, animationRenderer: animationRenderer, stickerNamespaces: stickerNamespaces, stickerOrderedItemListCollectionIds: stickerOrderedItemListCollectionIds, chatPeerId: chatPeerId, hasSearch: hasSearch, hasTrending: hasTrending, forceHasPremium: false, hasEdit: true, hideBackground: hideBackground)
|
||||
|
||||
let reactions: Signal<[String], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|
||||
|> map { appConfiguration -> [String] in
|
||||
|
@ -7540,12 +7540,12 @@ private final class FadingMaskLayer: SimpleLayer {
|
||||
}
|
||||
|
||||
public struct StickerPickerInputData: StickerPickerInput, Equatable {
|
||||
public var emoji: EmojiPagerContentComponent
|
||||
public var emoji: EmojiPagerContentComponent?
|
||||
public var stickers: EmojiPagerContentComponent?
|
||||
public var gifs: GifPagerContentComponent?
|
||||
|
||||
public init(
|
||||
emoji: EmojiPagerContentComponent,
|
||||
emoji: EmojiPagerContentComponent?,
|
||||
stickers: EmojiPagerContentComponent?,
|
||||
gifs: GifPagerContentComponent?
|
||||
) {
|
||||
|
@ -1162,11 +1162,6 @@ public extension EmojiPagerContentComponent {
|
||||
}
|
||||
} else if case .stickerAlt = subject {
|
||||
for reactionItem in topReactionItems {
|
||||
// if existingIds.contains(reactionItem.reaction) {
|
||||
// continue
|
||||
// }
|
||||
// existingIds.insert(reactionItem.reaction)
|
||||
|
||||
let icon: EmojiPagerContentComponent.Item.Icon
|
||||
if case .reaction(onlyTop: true) = subject {
|
||||
icon = .none
|
||||
@ -1612,6 +1607,7 @@ public extension EmojiPagerContentComponent {
|
||||
hasSearch: Bool,
|
||||
hasTrending: Bool,
|
||||
forceHasPremium: Bool,
|
||||
hasEdit: Bool = false,
|
||||
searchIsPlaceholderOnly: Bool = true,
|
||||
isProfilePhotoEmojiSelection: Bool = false,
|
||||
isGroupPhotoEmojiSelection: Bool = false,
|
||||
@ -1944,11 +1940,11 @@ public extension EmojiPagerContentComponent {
|
||||
|
||||
var title = ""
|
||||
var headerItem: EntityKeyboardAnimationData?
|
||||
var hasEdit = false
|
||||
var groupHasEdit = false
|
||||
inner: for (id, info, _) in view.collectionInfos {
|
||||
if id == groupId, let info = info as? StickerPackCollectionInfo {
|
||||
title = info.title
|
||||
hasEdit = info.flags.contains(.isCreator)
|
||||
groupHasEdit = info.flags.contains(.isCreator)
|
||||
|
||||
if let thumbnail = info.thumbnail {
|
||||
let type: EntityKeyboardAnimationData.ItemType
|
||||
@ -1974,7 +1970,7 @@ public extension EmojiPagerContentComponent {
|
||||
break inner
|
||||
}
|
||||
}
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, hasEdit: hasEdit, headerItem: headerItem, items: [resultItem]))
|
||||
itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: title, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: true, hasEdit: hasEdit && groupHasEdit, headerItem: headerItem, items: [resultItem]))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5676,11 +5676,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
if let navigationController = self.navigationController as? NavigationController {
|
||||
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
||||
}
|
||||
|
||||
// mediaEditor.stop()
|
||||
// mediaEditor.invalidate()
|
||||
// self.node.entitiesView.invalidate()
|
||||
|
||||
|
||||
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||
@ -5772,36 +5768,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
})))
|
||||
|
||||
let thumbSize = CGSize(width: 24.0, height: 24.0)
|
||||
for (pack, firstItem) in self.myStickerPacks {
|
||||
let thumbnailResource = pack.thumbnail?.resource ?? firstItem?.file.resource
|
||||
let thumbnailIconSource: ContextMenuActionItemIconSource?
|
||||
if let thumbnailResource {
|
||||
var resourceId: Int64 = 0
|
||||
if let resource = thumbnailResource as? CloudDocumentMediaResource {
|
||||
resourceId = resource.fileId
|
||||
}
|
||||
let thumbnailFile = firstItem?.file ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: [])
|
||||
|
||||
let _ = freeMediaFileInteractiveFetched(account: self.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start()
|
||||
thumbnailIconSource = ContextMenuActionItemIconSource(
|
||||
size: thumbSize,
|
||||
signal: chatMessageStickerPackThumbnail(postbox: self.context.account.postbox, resource: thumbnailResource)
|
||||
|> map { generator -> UIImage? in
|
||||
return generator(TransformImageArguments(corners: ImageCorners(), imageSize: thumbSize, boundingSize: thumbSize, intrinsicInsets: .zero))?.generateImage()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
thumbnailIconSource = nil
|
||||
contextItems.append(.custom(StickerPackListContextItem(context: self.context, packs: self.myStickerPacks, packSelected: { [weak self] pack in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
contextItems.append(.action(ContextMenuActionItem(text: pack.title, icon: { _ in return nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { [weak self] _, f in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
f(.default)
|
||||
self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title))
|
||||
})))
|
||||
}
|
||||
self.uploadSticker(file, action: .addToStickerPack(pack: .id(id: pack.id.id, accessHash: pack.accessHash), title: pack.title))
|
||||
}), false))
|
||||
|
||||
let items = ContextController.Items(
|
||||
id: 1,
|
||||
@ -5877,7 +5849,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = stickerPackEditTitleController(context: self.context, forceDark: true, title: "New Sticker Set", text: "Choose a name for your sticker set.", placeholder: presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: nil, maxLength: 128, apply: { [weak self] title in
|
||||
let controller = stickerPackEditTitleController(context: self.context, forceDark: true, title: "New Sticker Set", text: "Choose a name for your sticker set.", placeholder: presentationData.strings.ImportStickerPack_NamePlaceholder, actionTitle: presentationData.strings.Common_Done, value: nil, maxLength: 64, apply: { [weak self] title in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -5972,7 +5944,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
case let .createStickerPack(title):
|
||||
let sticker = ImportSticker(
|
||||
resource: resource,
|
||||
emojis: ["😀"],
|
||||
emojis: ["😀😂"],
|
||||
dimensions: dimensions,
|
||||
mimeType: "image/webp",
|
||||
keywords: ""
|
||||
@ -5991,7 +5963,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
case let .addToStickerPack(pack, _):
|
||||
let sticker = ImportSticker(
|
||||
resource: resource,
|
||||
emojis: ["😀"],
|
||||
emojis: ["😀😂"],
|
||||
dimensions: dimensions,
|
||||
mimeType: "image/webp",
|
||||
keywords: ""
|
||||
|
@ -0,0 +1,191 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import StickerResources
|
||||
import ContextUI
|
||||
|
||||
final class StickerPackListContextItem: ContextMenuCustomItem {
|
||||
let context: AccountContext
|
||||
let packs: [(StickerPackCollectionInfo, StickerPackItem?)]
|
||||
let packSelected: (StickerPackCollectionInfo) -> Void
|
||||
|
||||
init(context: AccountContext, packs: [(StickerPackCollectionInfo, StickerPackItem?)], packSelected: @escaping (StickerPackCollectionInfo) -> Void) {
|
||||
self.context = context
|
||||
self.packs = packs
|
||||
self.packSelected = packSelected
|
||||
}
|
||||
|
||||
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||
return StickerPackListContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||
}
|
||||
}
|
||||
|
||||
private final class StickerPackListContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol, UIScrollViewDelegate {
|
||||
private let item: StickerPackListContextItem
|
||||
private let presentationData: PresentationData
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
private let actionNodes: [ContextControllerActionsListActionItemNode]
|
||||
private let separatorNodes: [ASDisplayNode]
|
||||
|
||||
init(presentationData: PresentationData, item: StickerPackListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||
self.item = item
|
||||
self.presentationData = presentationData
|
||||
self.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
var actionNodes: [ContextControllerActionsListActionItemNode] = []
|
||||
var separatorNodes: [ASDisplayNode] = []
|
||||
|
||||
var i = 0
|
||||
for (pack, topItem) in item.packs {
|
||||
let thumbSize = CGSize(width: 24.0, height: 24.0)
|
||||
let thumbnailResource = pack.thumbnail?.resource ?? topItem?.file.resource
|
||||
let thumbnailIconSource: ContextMenuActionItemIconSource?
|
||||
if let thumbnailResource {
|
||||
var resourceId: Int64 = 0
|
||||
if let resource = thumbnailResource as? CloudDocumentMediaResource {
|
||||
resourceId = resource.fileId
|
||||
}
|
||||
let thumbnailFile = topItem?.file ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: [])
|
||||
|
||||
let _ = freeMediaFileInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start()
|
||||
thumbnailIconSource = ContextMenuActionItemIconSource(
|
||||
size: thumbSize,
|
||||
signal: chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: thumbnailResource)
|
||||
|> map { generator -> UIImage? in
|
||||
return generator(TransformImageArguments(corners: ImageCorners(), imageSize: thumbSize, boundingSize: thumbSize, intrinsicInsets: .zero))?.generateImage()
|
||||
}
|
||||
)
|
||||
} else {
|
||||
thumbnailIconSource = nil
|
||||
}
|
||||
|
||||
let action = ContextMenuActionItem(text: pack.title, textLayout: .singleLine, icon: { _ in nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
item.packSelected(pack)
|
||||
})
|
||||
let actionNode = ContextControllerActionsListActionItemNode(getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: action)
|
||||
actionNodes.append(actionNode)
|
||||
if actionNodes.count != item.packs.count {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
separatorNodes.append(separatorNode)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
self.actionNodes = actionNodes
|
||||
self.separatorNodes = separatorNodes
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
for separatorNode in self.separatorNodes {
|
||||
self.scrollNode.addSubnode(separatorNode)
|
||||
}
|
||||
for actionNode in self.actionNodes {
|
||||
self.scrollNode.addSubnode(actionNode)
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.scrollNode.view.delegate = self
|
||||
self.scrollNode.view.alwaysBounceVertical = false
|
||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let minActionsWidth: CGFloat = 250.0
|
||||
let maxActionsWidth: CGFloat = 300.0
|
||||
let constrainedWidth = min(constrainedWidth, maxActionsWidth)
|
||||
var maxWidth: CGFloat = 0.0
|
||||
var contentHeight: CGFloat = 0.0
|
||||
var heightsAndCompletions: [(CGFloat, (CGSize, ContainedViewLayoutTransition) -> Void)?] = []
|
||||
for i in 0 ..< self.actionNodes.count {
|
||||
let itemNode = self.actionNodes[i]
|
||||
let (minSize, complete) = itemNode.update(presentationData: self.presentationData, constrainedSize: CGSize(width: constrainedWidth, height: constrainedHeight))
|
||||
maxWidth = max(maxWidth, minSize.width)
|
||||
heightsAndCompletions.append((minSize.height, complete))
|
||||
contentHeight += minSize.height
|
||||
}
|
||||
|
||||
maxWidth = max(maxWidth, minActionsWidth)
|
||||
|
||||
let maxHeight: CGFloat = min(155.0, constrainedHeight - 108.0)
|
||||
|
||||
return (CGSize(width: maxWidth, height: min(maxHeight, contentHeight)), { size, transition in
|
||||
var verticalOffset: CGFloat = 0.0
|
||||
for i in 0 ..< heightsAndCompletions.count {
|
||||
let itemNode = self.actionNodes[i]
|
||||
if let (itemHeight, itemCompletion) = heightsAndCompletions[i] {
|
||||
let itemSize = CGSize(width: maxWidth, height: itemHeight)
|
||||
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: itemSize))
|
||||
itemCompletion(itemSize, transition)
|
||||
verticalOffset += itemHeight
|
||||
}
|
||||
|
||||
if i < self.actionNodes.count - 1 {
|
||||
let separatorNode = self.separatorNodes[i]
|
||||
separatorNode.frame = CGRect(x: 0, y: verticalOffset, width: size.width, height: UIScreenPixel)
|
||||
}
|
||||
}
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
// for actionNode in self.actionNodes {
|
||||
// actionNode.updateTheme(presentationData: presentationData)
|
||||
// }
|
||||
}
|
||||
|
||||
var isActionEnabled: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
}
|
||||
|
||||
func setIsHighlighted(_ value: Bool) {
|
||||
}
|
||||
|
||||
func canBeHighlighted() -> Bool {
|
||||
return self.isActionEnabled
|
||||
}
|
||||
|
||||
func updateIsHighlighted(isHighlighted: Bool) {
|
||||
self.setIsHighlighted(isHighlighted)
|
||||
}
|
||||
|
||||
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||
// for actionNode in self.actionNodes {
|
||||
// let frame = actionNode.convert(actionNode.bounds, to: self)
|
||||
// if frame.contains(point) {
|
||||
// return actionNode
|
||||
// }
|
||||
// }
|
||||
return self
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateIsHighlighted(isHighlighted: false)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
//
|
||||
// PeerInfoScreenBirthdatePickerItem.swift
|
||||
// MediaEditorScreen
|
||||
//
|
||||
// Created by Ilya Laktyushin on 15.03.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
@ -38,9 +38,10 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
|
||||
let text: String
|
||||
let icon: UIImage?
|
||||
let iconSignal: Signal<UIImage?, NoError>?
|
||||
let hasArrow: Bool
|
||||
let action: (() -> Void)?
|
||||
|
||||
init(id: AnyHashable, label: Label = .none, additionalBadgeLabel: String? = nil, additionalBadgeIcon: UIImage? = nil, text: String, icon: UIImage? = nil, iconSignal: Signal<UIImage?, NoError>? = nil, action: (() -> Void)?) {
|
||||
init(id: AnyHashable, label: Label = .none, additionalBadgeLabel: String? = nil, additionalBadgeIcon: UIImage? = nil, text: String, icon: UIImage? = nil, iconSignal: Signal<UIImage?, NoError>? = nil, hasArrow: Bool = true, action: (() -> Void)?) {
|
||||
self.id = id
|
||||
self.label = label
|
||||
self.additionalBadgeLabel = additionalBadgeLabel
|
||||
@ -48,6 +49,7 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
|
||||
self.text = text
|
||||
self.icon = icon
|
||||
self.iconSignal = iconSignal
|
||||
self.hasArrow = hasArrow
|
||||
self.action = action
|
||||
}
|
||||
|
||||
@ -139,7 +141,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
||||
|
||||
let sideInset: CGFloat = 16.0 + safeInsets.left
|
||||
let leftInset = (item.icon == nil && item.iconSignal == nil ? sideInset : sideInset + 29.0 + 16.0)
|
||||
let rightInset = sideInset + 18.0
|
||||
let rightInset = sideInset + (item.hasArrow ? 18.0 : 0.0)
|
||||
let separatorInset = item.icon == nil && item.iconSignal == nil ? sideInset : leftInset - 1.0
|
||||
let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
|
||||
|
||||
@ -206,7 +208,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
||||
self.iconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) {
|
||||
if item.hasArrow, let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) {
|
||||
self.arrowNode.image = arrowImage
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: width - 7.0 - arrowImage.size.width - safeInsets.right, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||
|
@ -1354,7 +1354,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
self.controller?.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
} else {
|
||||
let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false, topPeers: false)), onlyWriteable: self.filter.contains(.onlyWriteable))
|
||||
let contactListNode = ContactListNode(context: self.context, updatedPresentationData: self.updatedPresentationData, presentation: .single(.natural(options: [], includeChatList: false, topPeers: .none)), onlyWriteable: self.filter.contains(.onlyWriteable))
|
||||
self.contactListNode = contactListNode
|
||||
contactListNode.enableUpdates = true
|
||||
contactListNode.selectionStateUpdated = { [weak self] selectionState in
|
||||
|
@ -186,10 +186,12 @@ final class GreetingMessageListItemComponent: Component {
|
||||
},
|
||||
openPremiumIntro: {
|
||||
},
|
||||
openPremiumGift: {
|
||||
openPremiumGift: { _ in
|
||||
},
|
||||
openActiveSessions: {
|
||||
},
|
||||
openBirthdaySetup: {
|
||||
},
|
||||
performActiveSessionAction: { _, _ in
|
||||
},
|
||||
openChatFolderUpdates: {
|
||||
|
@ -201,10 +201,12 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
},
|
||||
openPremiumIntro: {
|
||||
},
|
||||
openPremiumGift: {
|
||||
openPremiumGift: { _ in
|
||||
},
|
||||
openActiveSessions: {
|
||||
},
|
||||
openBirthdaySetup: {
|
||||
},
|
||||
performActiveSessionAction: { _, _ in
|
||||
},
|
||||
openChatFolderUpdates: {
|
||||
|
@ -865,7 +865,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: {
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {
|
||||
}, openBirthdaySetup: {
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||
}, openStories: { _, _ in
|
||||
|
@ -30,6 +30,7 @@ private final class StickerSelectionComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let deviceMetrics: DeviceMetrics
|
||||
let topInset: CGFloat
|
||||
let bottomInset: CGFloat
|
||||
let content: StickerPickerInputData
|
||||
let backgroundColor: UIColor
|
||||
@ -41,6 +42,7 @@ private final class StickerSelectionComponent: Component {
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
deviceMetrics: DeviceMetrics,
|
||||
topInset: CGFloat,
|
||||
bottomInset: CGFloat,
|
||||
content: StickerPickerInputData,
|
||||
backgroundColor: UIColor,
|
||||
@ -51,6 +53,7 @@ private final class StickerSelectionComponent: Component {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.deviceMetrics = deviceMetrics
|
||||
self.topInset = topInset
|
||||
self.bottomInset = bottomInset
|
||||
self.content = content
|
||||
self.backgroundColor = backgroundColor
|
||||
@ -68,6 +71,9 @@ private final class StickerSelectionComponent: Component {
|
||||
if lhs.deviceMetrics != rhs.deviceMetrics {
|
||||
return false
|
||||
}
|
||||
if lhs.topInset != rhs.topInset {
|
||||
return false
|
||||
}
|
||||
if lhs.bottomInset != rhs.bottomInset {
|
||||
return false
|
||||
}
|
||||
@ -235,12 +241,13 @@ private final class StickerSelectionComponent: Component {
|
||||
self.backgroundColor = component.backgroundColor
|
||||
let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85)
|
||||
self.panelBackgroundView.updateColor(color: panelBackgroundColor, transition: .immediate)
|
||||
self.panelSeparatorView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.1)
|
||||
self.panelSeparatorView.backgroundColor = component.separatorColor
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let topPanelHeight: CGFloat = 42.0
|
||||
let topInset = component.topInset
|
||||
|
||||
let controller = component.getController()
|
||||
let defaultToEmoji = controller?.defaultToEmoji ?? false
|
||||
@ -248,7 +255,7 @@ private final class StickerSelectionComponent: Component {
|
||||
let context = component.context
|
||||
let stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
|
||||
context: context,
|
||||
forceTheme: defaultDarkColorPresentationTheme,
|
||||
forceTheme: controller?.forceDark == true ? defaultDarkColorPresentationTheme : nil,
|
||||
interaction: nil,
|
||||
chatPeerId: nil,
|
||||
present: { c, a in
|
||||
@ -258,19 +265,20 @@ private final class StickerSelectionComponent: Component {
|
||||
}
|
||||
)
|
||||
|
||||
let isFullscreen = controller?.isFullscreen == true
|
||||
let keyboardSize = self.keyboardView.update(
|
||||
transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
|
||||
component: AnyComponent(EntityKeyboardComponent(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
isContentInFocus: true,
|
||||
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0, left: 0.0, bottom: component.bottomInset, right: 0.0),
|
||||
containerInsets: UIEdgeInsets(top: topPanelHeight - 34.0 + topInset, left: 0.0, bottom: component.bottomInset, right: 0.0),
|
||||
topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
|
||||
emojiContent: component.content.emoji,
|
||||
stickerContent: component.content.stickers,
|
||||
maskContent: nil,
|
||||
gifContent: component.content.gifs,
|
||||
hasRecentGifs: true,
|
||||
hasRecentGifs: !isFullscreen,
|
||||
availableGifSearchEmojies: [],
|
||||
defaultToEmojiTab: defaultToEmoji,
|
||||
externalTopPanelContainer: self.panelHostView,
|
||||
@ -313,7 +321,10 @@ private final class StickerSelectionComponent: Component {
|
||||
mappedMode = .gif
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
if controller?.forceDark == true {
|
||||
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
}
|
||||
let searchContainerNode = PaneSearchContainerNode(
|
||||
context: context,
|
||||
theme: presentationData.theme,
|
||||
@ -344,7 +355,7 @@ private final class StickerSelectionComponent: Component {
|
||||
deviceMetrics: component.deviceMetrics,
|
||||
hiddenInputHeight: 0.0,
|
||||
inputHeight: 0.0,
|
||||
displayBottomPanel: controller?.isFullscreen == false,
|
||||
displayBottomPanel: !isFullscreen,
|
||||
isExpanded: true,
|
||||
clipContentToTopPanel: false,
|
||||
useExternalSearchContainer: false
|
||||
@ -365,26 +376,34 @@ private final class StickerSelectionComponent: Component {
|
||||
self.keyboardClippingView.clipsToBounds = false
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight)))
|
||||
self.keyboardClippingView.hitEdgeInsets = UIEdgeInsets(top: -topPanelHeight, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
transition.setFrame(view: self.keyboardClippingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset), size: CGSize(width: availableSize.width, height: availableSize.height - topPanelHeight - topInset)))
|
||||
self.keyboardClippingView.hitEdgeInsets = UIEdgeInsets(top: -topPanelHeight - topInset, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
|
||||
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight), size: keyboardSize))
|
||||
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
|
||||
transition.setFrame(view: keyboardComponentView, frame: CGRect(origin: CGPoint(x: 0.0, y: -topPanelHeight - topInset), size: keyboardSize))
|
||||
transition.setFrame(view: self.panelHostView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0)))
|
||||
|
||||
transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: topPanelHeight)))
|
||||
transition.setFrame(view: self.panelBackgroundView, frame: CGRect(origin: CGPoint(), size: CGSize(width: keyboardSize.width, height: topPanelHeight + topInset)))
|
||||
self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
|
||||
|
||||
let topPanelAlpha: CGFloat
|
||||
if self.searchVisible || self.keyboardContentId == AnyHashable("gifs") {
|
||||
topPanelAlpha = 0.0
|
||||
if isFullscreen, let navigationBar = controller?.navigationBar, navigationBar.alpha > 0.0 {
|
||||
transition.setAlpha(view: navigationBar.view, alpha: 0.0)
|
||||
}
|
||||
} else if isFullscreen {
|
||||
topPanelAlpha = 1.0
|
||||
if let navigationBar = controller?.navigationBar, navigationBar.alpha < 1.0 {
|
||||
transition.setAlpha(view: navigationBar.view, alpha: 1.0)
|
||||
}
|
||||
} else {
|
||||
topPanelAlpha = max(0.0, min(1.0, (self.topPanelScrollingOffset / 20.0)))
|
||||
}
|
||||
|
||||
|
||||
transition.setAlpha(view: self.panelBackgroundView, alpha: topPanelAlpha)
|
||||
transition.setAlpha(view: self.panelSeparatorView, alpha: topPanelAlpha)
|
||||
|
||||
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
|
||||
transition.setFrame(view: self.panelSeparatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: topPanelHeight + topInset - UIScreenPixel), size: CGSize(width: keyboardSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
|
||||
return availableSize
|
||||
@ -642,35 +661,36 @@ public class StickerPickerScreen: ViewController {
|
||||
|
||||
inputData.gifs = gifData?.component
|
||||
|
||||
let emoji = inputData.emoji
|
||||
if let emojiSearchResult = emojiSearchState.result {
|
||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
|
||||
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
||||
text: presentationData.strings.EmojiSearch_SearchEmojiEmptyResult,
|
||||
iconFile: nil
|
||||
)
|
||||
if let emoji = inputData.emoji {
|
||||
if let emojiSearchResult = emojiSearchState.result {
|
||||
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
|
||||
emptySearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
||||
text: presentationData.strings.EmojiSearch_SearchEmojiEmptyResult,
|
||||
iconFile: nil
|
||||
)
|
||||
}
|
||||
|
||||
let defaultSearchState: EmojiPagerContentComponent.SearchState = emojiSearchResult.isPreset ? .active : .empty(hasResults: true)
|
||||
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState)
|
||||
} else if emojiSearchState.isSearching {
|
||||
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emoji.contentItemGroups, itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: .searching)
|
||||
}
|
||||
let defaultSearchState: EmojiPagerContentComponent.SearchState = emojiSearchResult.isPreset ? .active : .empty(hasResults: true)
|
||||
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emojiSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: emojiSearchResult.id, version: emojiSearchResult.version), emptySearchResults: emptySearchResults, searchState: emojiSearchState.isSearching ? .searching : defaultSearchState)
|
||||
} else if emojiSearchState.isSearching {
|
||||
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emoji.contentItemGroups, itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: .searching)
|
||||
}
|
||||
|
||||
if let stickerSearchResult = stickerSearchState.result {
|
||||
var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||
if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
|
||||
stickerSearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
||||
text: presentationData.strings.EmojiSearch_SearchStickersEmptyResult,
|
||||
iconFile: nil
|
||||
)
|
||||
}
|
||||
if let stickers = inputData.stickers {
|
||||
if let stickers = inputData.stickers {
|
||||
if let stickerSearchResult = stickerSearchState.result {
|
||||
var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults?
|
||||
if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
|
||||
stickerSearchResults = EmojiPagerContentComponent.EmptySearchResults(
|
||||
text: presentationData.strings.EmojiSearch_SearchStickersEmptyResult,
|
||||
iconFile: nil
|
||||
)
|
||||
}
|
||||
|
||||
let defaultSearchState: EmojiPagerContentComponent.SearchState = stickerSearchResult.isPreset ? .active : .empty(hasResults: true)
|
||||
inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickerSearchResult.groups, itemContentUniqueId: EmojiPagerContentComponent.ContentId(id: stickerSearchResult.id, version: stickerSearchResult.version), emptySearchResults: stickerSearchResults, searchState: stickerSearchState.isSearching ? .searching : defaultSearchState)
|
||||
}
|
||||
} else if stickerSearchState.isSearching {
|
||||
if let stickers = inputData.stickers {
|
||||
} else if stickerSearchState.isSearching {
|
||||
inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickers.contentItemGroups, itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: stickers.emptySearchResults, searchState: .searching)
|
||||
}
|
||||
}
|
||||
@ -778,9 +798,9 @@ public class StickerPickerScreen: ViewController {
|
||||
text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string
|
||||
}
|
||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { [weak controller] action in
|
||||
if case .info = action {
|
||||
let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs, forceDark: true, dismissed: nil)
|
||||
controller?.push(premiumController)
|
||||
if case .info = action, let controller {
|
||||
let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs, forceDark: controller.forceDark, dismissed: nil)
|
||||
controller.push(premiumController)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@ -802,7 +822,7 @@ public class StickerPickerScreen: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
content.emoji.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
content.emoji?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
|
||||
performItemAction: { [weak self] groupId, item, _, _, _, _ in
|
||||
guard let strongSelf = self, let controller = strongSelf.controller else {
|
||||
return
|
||||
@ -846,10 +866,10 @@ public class StickerPickerScreen: ViewController {
|
||||
for featuredStickerPack in stickerPacks {
|
||||
if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) {
|
||||
if let componentView = self.hostView.componentView as? StickerSelectionComponent.View {
|
||||
if let pagerView = componentView.keyboardView.view as? EntityKeyboardComponent.View, let emojiInputInteraction = self.content?.emoji.inputInteractionHolder.inputInteraction {
|
||||
if let pagerView = componentView.keyboardView.view as? EntityKeyboardComponent.View, let emojiInputInteraction = self.content?.emoji?.inputInteractionHolder.inputInteraction, let controller = self.controller {
|
||||
pagerView.openCustomSearch(content: EmojiSearchContent(
|
||||
context: context,
|
||||
forceTheme: defaultDarkPresentationTheme,
|
||||
forceTheme: controller.forceDark ? defaultDarkPresentationTheme : nil,
|
||||
items: stickerPacks,
|
||||
initialFocusId: featuredStickerPack.info.id,
|
||||
hasPremiumForUse: hasPremium,
|
||||
@ -858,7 +878,6 @@ public class StickerPickerScreen: ViewController {
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -943,7 +962,10 @@ public class StickerPickerScreen: ViewController {
|
||||
guard let strongSelf = self, let controller = strongSelf.controller else {
|
||||
return
|
||||
}
|
||||
let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
var presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
|
||||
if controller.forceDark {
|
||||
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
}
|
||||
let context = controller.context
|
||||
if groupId == AnyHashable("recent") {
|
||||
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
|
||||
@ -1243,7 +1265,7 @@ public class StickerPickerScreen: ViewController {
|
||||
if let controller = self.controller {
|
||||
stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
|
||||
context: controller.context,
|
||||
forceTheme: defaultDarkColorPresentationTheme,
|
||||
forceTheme: controller.forceDark ? defaultDarkColorPresentationTheme : nil,
|
||||
interaction: nil,
|
||||
chatPeerId: nil,
|
||||
present: { [weak controller] c, a in
|
||||
@ -1348,7 +1370,10 @@ public class StickerPickerScreen: ViewController {
|
||||
}
|
||||
let context = controller.context
|
||||
if groupId == AnyHashable("recent") {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
var presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
if controller.forceDark {
|
||||
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
}
|
||||
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in
|
||||
@ -1532,7 +1557,7 @@ public class StickerPickerScreen: ViewController {
|
||||
|
||||
self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
|
||||
if let controller = self.controller, !controller.isFullscreen {
|
||||
if let controller = self.controller {
|
||||
controller.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
}
|
||||
}
|
||||
@ -1711,10 +1736,11 @@ public class StickerPickerScreen: ViewController {
|
||||
theme: self.theme,
|
||||
strings: self.presentationData.strings,
|
||||
deviceMetrics: layout.deviceMetrics,
|
||||
topInset: controller.isFullscreen ? navigationHeight : 0.0,
|
||||
bottomInset: bottomInset,
|
||||
content: content,
|
||||
backgroundColor: self.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.85),
|
||||
separatorColor: self.theme.list.blocksBackgroundColor,
|
||||
separatorColor: self.theme.rootController.navigationBar.separatorColor,
|
||||
getController: { [weak self] in
|
||||
if let self {
|
||||
return self.controller
|
||||
@ -1752,12 +1778,12 @@ public class StickerPickerScreen: ViewController {
|
||||
}
|
||||
|
||||
private var defaultTopInset: CGFloat {
|
||||
guard let (layout, navigationBarHeight) = self.currentLayout else {
|
||||
guard let (layout, _) = self.currentLayout else {
|
||||
return 210.0
|
||||
}
|
||||
|
||||
if let controller = self.controller, controller.isFullscreen {
|
||||
return navigationBarHeight
|
||||
return 0.0
|
||||
}
|
||||
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
@ -1978,6 +2004,7 @@ public class StickerPickerScreen: ViewController {
|
||||
|
||||
private let context: AccountContext
|
||||
private let theme: PresentationTheme
|
||||
fileprivate let forceDark: Bool
|
||||
private let inputData: Signal<StickerPickerInput, NoError>
|
||||
fileprivate let defaultToEmoji: Bool
|
||||
let isFullscreen: Bool
|
||||
@ -2002,6 +2029,7 @@ public class StickerPickerScreen: ViewController {
|
||||
self.context = context
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.theme = forceDark ? defaultDarkColorPresentationTheme : presentationData.theme
|
||||
self.forceDark = forceDark
|
||||
self.inputData = inputData
|
||||
self.isFullscreen = expanded
|
||||
self.defaultToEmoji = defaultToEmoji
|
||||
|
@ -255,7 +255,7 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF
|
||||
|
||||
private let maxLength: Int
|
||||
|
||||
init(theme: PresentationTheme, placeholder: String, maxLength: Int, keyboardType: UIKeyboardType = .default, returnKeyType: UIReturnKeyType = .done) {
|
||||
init(theme: PresentationTheme, placeholder: String, maxLength: Int, keyboardType: UIKeyboardType = .default, returnKeyType: UIReturnKeyType = .done, hasClearButton: Bool = false) {
|
||||
self.theme = theme
|
||||
self.maxLength = maxLength
|
||||
|
||||
@ -370,6 +370,10 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF
|
||||
return false
|
||||
}
|
||||
|
||||
if string == " " && updatedText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.textInputNode.keyboardType == .asciiCapable {
|
||||
var cleanString = string.folding(options: .diacriticInsensitive, locale: .current).replacingOccurrences(of: " ", with: "_")
|
||||
|
||||
@ -506,7 +510,7 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int, asciiOnly: Bool = false) {
|
||||
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, text: String, placeholder: String, value: String?, maxLength: Int, asciiOnly: Bool = false, hasClearButton: Bool) {
|
||||
self.strings = strings
|
||||
self.alertTheme = theme
|
||||
self.theme = ptheme
|
||||
@ -524,7 +528,7 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
|
||||
self.activityIndicator = ActivityIndicator(type: .custom(ptheme.rootController.navigationBar.secondaryTextColor, 20.0, 1.5, false), speed: .slow)
|
||||
self.activityIndicator.isHidden = true
|
||||
|
||||
self.inputFieldNode = ImportStickerPackTitleInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength, keyboardType: asciiOnly ? .asciiCapable : .default, returnKeyType: asciiOnly ? .done : .next)
|
||||
self.inputFieldNode = ImportStickerPackTitleInputFieldNode(theme: ptheme, placeholder: placeholder, maxLength: maxLength, keyboardType: asciiOnly ? .asciiCapable : .default, returnKeyType: asciiOnly ? .done : .next, hasClearButton: hasClearButton)
|
||||
if asciiOnly {
|
||||
self.inputFieldNode.prefix = "t.me/addstickers/"
|
||||
}
|
||||
@ -743,7 +747,7 @@ public func stickerPackEditTitleController(context: AccountContext, forceDark: B
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength)
|
||||
let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength, hasClearButton: false)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
@ -805,7 +809,7 @@ public func importStickerPackShortNameController(context: AccountContext, title:
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength, asciiOnly: true)
|
||||
let contentNode = ImportStickerPackTitleAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, text: text, placeholder: placeholder, value: value, maxLength: maxLength, asciiOnly: true, hasClearButton: true)
|
||||
contentNode.complete = {
|
||||
applyImpl?()
|
||||
}
|
||||
|
@ -209,31 +209,8 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.shadowGradientView = UIImageView()
|
||||
if let _ = StoryContentCaptionComponent.View.shadowImage {
|
||||
let height: CGFloat = 128.0
|
||||
let baseGradientAlpha: CGFloat = 0.8
|
||||
let numSteps = 8
|
||||
let firstStep = 0
|
||||
let firstLocation = 0.0
|
||||
let colors = (0 ..< numSteps).map { i -> UIColor in
|
||||
if i < firstStep {
|
||||
return UIColor(white: 1.0, alpha: 1.0)
|
||||
} else {
|
||||
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
|
||||
return UIColor(white: 0.0, alpha: baseGradientAlpha * value)
|
||||
}
|
||||
}
|
||||
let locations = (0 ..< numSteps).map { i -> CGFloat in
|
||||
if i < firstStep {
|
||||
return 0.0
|
||||
} else {
|
||||
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||
return (firstLocation + (1.0 - firstLocation) * step)
|
||||
}
|
||||
}
|
||||
|
||||
self.shadowGradientView.image = generateGradientImage(size: CGSize(width: 8.0, height: height), colors: colors.reversed(), locations: locations.reversed().map { 1.0 - $0 })!.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(height - 1.0))
|
||||
if let image = StoryContentCaptionComponent.View.shadowImage {
|
||||
self.shadowGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0))
|
||||
}
|
||||
|
||||
self.scrollViewContainer = UIView()
|
||||
@ -409,8 +386,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
transition.setBounds(view: self.textSelectionKnobContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.scrollView.bounds.minY), size: CGSize()))
|
||||
|
||||
let shadowHeight: CGFloat = self.shadowGradientView.image?.size.height ?? 100.0
|
||||
let shadowOverflow: CGFloat = floor(shadowHeight * 0.6)
|
||||
let shadowOverflow: CGFloat = 58.0
|
||||
let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow))
|
||||
|
||||
let shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0))
|
||||
|
@ -2690,7 +2690,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.bottomContentGradientLayer.colors = colors
|
||||
self.bottomContentGradientLayer.type = .axial
|
||||
|
||||
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3)
|
||||
}
|
||||
|
||||
let wasPanning = self.component?.isPanning ?? false
|
||||
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "more.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 1.500000 1.335754 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
5.252930 4.662109 m
|
||||
5.252930 4.527832 5.199219 4.409668 5.097168 4.307617 c
|
||||
0.843262 0.145020 l
|
||||
0.746582 0.048340 0.628418 0.000000 0.488770 0.000000 c
|
||||
0.214844 0.000000 0.000000 0.209473 0.000000 0.488770 c
|
||||
0.000000 0.628418 0.053711 0.746582 0.139648 0.837891 c
|
||||
4.049805 4.662109 l
|
||||
0.139648 8.486328 l
|
||||
0.053711 8.577637 0.000000 8.701172 0.000000 8.835449 c
|
||||
0.000000 9.114746 0.214844 9.324219 0.488770 9.324219 c
|
||||
0.628418 9.324219 0.746582 9.275879 0.843262 9.184570 c
|
||||
5.097168 5.016602 l
|
||||
5.199219 4.919922 5.252930 4.796387 5.252930 4.662109 c
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
675
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 8.000000 12.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000000765 00000 n
|
||||
0000000787 00000 n
|
||||
0000000959 00000 n
|
||||
0000001033 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1092
|
||||
%%EOF
|
Binary file not shown.
@ -453,6 +453,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
weak var checksTooltipController: TooltipController?
|
||||
weak var copyProtectionTooltipController: TooltipController?
|
||||
weak var emojiPackTooltipController: TooltipScreen?
|
||||
weak var birthdayTooltipController: TooltipScreen?
|
||||
|
||||
var currentMessageTooltipScreens: [(TooltipScreen, ListViewItemNode)] = []
|
||||
|
||||
@ -587,8 +588,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
|
||||
var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
|
||||
|
||||
var networkSpeedEventsDisposable: Disposable?
|
||||
|
||||
public var alwaysShowSearchResultsAsList: Bool = false {
|
||||
didSet {
|
||||
self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList)
|
||||
@ -4910,43 +4909,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
|
||||
let managingBot: Signal<ChatManagingBot?, NoError>
|
||||
if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser, !"".isEmpty {
|
||||
managingBot = self.context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: self.context.account.peerId)
|
||||
)
|
||||
|> mapToSignal { result -> Signal<ChatManagingBot?, NoError> in
|
||||
guard let result else {
|
||||
return .single(nil)
|
||||
}
|
||||
return context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: result.id)
|
||||
)
|
||||
|> map { botPeer -> ChatManagingBot? in
|
||||
guard let botPeer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var isPaused = false
|
||||
if result.recipients.exclude {
|
||||
isPaused = result.recipients.additionalPeers.contains(peerId)
|
||||
} else {
|
||||
isPaused = !result.recipients.additionalPeers.contains(peerId)
|
||||
}
|
||||
|
||||
var settingsUrl: String?
|
||||
if let username = botPeer.addressName {
|
||||
settingsUrl = "https://t.me/\(username)"
|
||||
}
|
||||
|
||||
return ChatManagingBot(bot: botPeer, isPaused: isPaused, canReply: result.canReply, settingsUrl: settingsUrl)
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
managingBot = .single(nil)
|
||||
}
|
||||
|
||||
do {
|
||||
let peerId = chatLocationPeerId
|
||||
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
|
||||
@ -5335,11 +5297,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
threadInfo,
|
||||
hasSearchTags,
|
||||
hasSavedChats,
|
||||
isPremiumRequiredForMessaging,
|
||||
managingBot
|
||||
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
|
||||
isPremiumRequiredForMessaging
|
||||
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging && managingBot == strongSelf.presentationInterfaceState.contactStatus?.managingBot {
|
||||
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging {
|
||||
return
|
||||
}
|
||||
|
||||
@ -5434,7 +5395,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var contactStatus: ChatContactStatus?
|
||||
if let peer = peerView.peers[peerView.peerId] {
|
||||
if let cachedData = peerView.cachedData as? CachedUserData {
|
||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
|
||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil)
|
||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||
var invitedBy: Peer?
|
||||
if let invitedByPeerId = cachedData.invitedBy {
|
||||
@ -5442,7 +5403,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
invitedBy = peer
|
||||
}
|
||||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy)
|
||||
} else if let cachedData = peerView.cachedData as? CachedChannelData {
|
||||
var canReportIrrelevantLocation = true
|
||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
|
||||
@ -5457,7 +5418,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
invitedBy = peer
|
||||
}
|
||||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy)
|
||||
}
|
||||
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -5560,9 +5521,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}
|
||||
if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
|
||||
didDisplayActionsPanel = true
|
||||
}
|
||||
if strongSelf.presentationInterfaceState.search != nil && strongSelf.presentationInterfaceState.hasSearchTags {
|
||||
didDisplayActionsPanel = true
|
||||
}
|
||||
@ -5583,9 +5541,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}
|
||||
if let contactStatus, contactStatus.managingBot != nil {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
if strongSelf.presentationInterfaceState.search != nil && hasSearchTags {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
@ -5919,10 +5874,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
hasScheduledMessages,
|
||||
hasSearchTags,
|
||||
hasSavedChats,
|
||||
isPremiumRequiredForMessaging,
|
||||
managingBot
|
||||
isPremiumRequiredForMessaging
|
||||
)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in
|
||||
if let strongSelf = self {
|
||||
strongSelf.hasScheduledMessages = hasScheduledMessages
|
||||
|
||||
@ -5932,7 +5886,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let peer = peerView.peers[peerView.peerId] {
|
||||
copyProtectionEnabled = peer.isCopyProtectionEnabled
|
||||
if let cachedData = peerView.cachedData as? CachedUserData {
|
||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
|
||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil)
|
||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||
var invitedBy: Peer?
|
||||
if let invitedByPeerId = cachedData.invitedBy {
|
||||
@ -5940,7 +5894,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
invitedBy = peer
|
||||
}
|
||||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy)
|
||||
} else if let cachedData = peerView.cachedData as? CachedChannelData {
|
||||
var canReportIrrelevantLocation = true
|
||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
|
||||
@ -5953,7 +5907,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
invitedBy = peer
|
||||
}
|
||||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy)
|
||||
}
|
||||
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -6157,9 +6111,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}
|
||||
if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
|
||||
didDisplayActionsPanel = true
|
||||
}
|
||||
|
||||
var displayActionsPanel = false
|
||||
if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
|
||||
@ -6177,9 +6128,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}
|
||||
if let contactStatus, contactStatus.managingBot != nil {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
|
||||
if displayActionsPanel != didDisplayActionsPanel {
|
||||
animated = true
|
||||
@ -6851,7 +6799,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.preloadSavedMessagesChatsDisposable?.dispose()
|
||||
self.recorderDataDisposable.dispose()
|
||||
self.displaySendWhenOnlineTipDisposable.dispose()
|
||||
self.networkSpeedEventsDisposable?.dispose()
|
||||
}
|
||||
deallocate()
|
||||
}
|
||||
@ -11741,57 +11688,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
var lastEventTimestamp: Double = 0.0
|
||||
self.networkSpeedEventsDisposable = (self.context.account.network.networkSpeedLimitedEvents
|
||||
|> deliverOnMainQueue).start(next: { [weak self] event in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
if lastEventTimestamp + 10.0 < timestamp {
|
||||
lastEventTimestamp = timestamp
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let title: String
|
||||
let text: String
|
||||
switch event {
|
||||
case .download:
|
||||
var speedIncreaseFactor = 10
|
||||
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_download"] as? Double {
|
||||
speedIncreaseFactor = Int(value)
|
||||
}
|
||||
title = "Download speed limited"
|
||||
text = "Subscribe to [Telegram Premium]() and increase download speeds \(speedIncreaseFactor) times."
|
||||
case .upload:
|
||||
var speedIncreaseFactor = 10
|
||||
if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_upload"] as? Double {
|
||||
speedIncreaseFactor = Int(value)
|
||||
}
|
||||
title = "Upload speed limited"
|
||||
text = "Subscribe to [Telegram Premium]() and increase upload speeds \(speedIncreaseFactor) times."
|
||||
}
|
||||
let content: UndoOverlayContent = .universal(animation: "anim_speed_low", scale: 0.066, colors: [:], title: title, text: text, customUndoText: nil, timeout: 5.0)
|
||||
|
||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, position: .top, action: { [weak self] action in
|
||||
guard let self else {
|
||||
return false
|
||||
}
|
||||
switch action {
|
||||
case .info:
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .reactions, forceDark: false, dismissed: nil)
|
||||
self.push(controller)
|
||||
return true
|
||||
default:
|
||||
break
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
})
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
@ -12346,6 +12242,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// #if DEBUG
|
||||
// Queue.mainQueue().after(0.5) {
|
||||
// self.displayBirthdayTooltip()
|
||||
// }
|
||||
// #endif
|
||||
}
|
||||
|
||||
override public func viewWillDisappear(_ animated: Bool) {
|
||||
@ -16074,6 +15976,30 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
}
|
||||
|
||||
func displayBirthdayTooltip() {
|
||||
guard let rect = self.chatDisplayNode.frameForGiftButton(), self.effectiveNavigationController?.topViewController === self, let peer = self.presentationInterfaceState.renderedPeer?.peer.flatMap({ EnginePeer($0) }) else {
|
||||
return
|
||||
}
|
||||
|
||||
let peerName = peer.compactDisplayTitle
|
||||
let text = "🎂 \(peerName) is having a birthday today. You can give \(peerName) **Telegram Premium** as a birthday gift."
|
||||
|
||||
let tooltipScreen = TooltipScreen(
|
||||
context: self.context,
|
||||
account: self.context.account,
|
||||
sharedContext: self.context.sharedContext,
|
||||
text: .markdown(text: text),
|
||||
location: .point(rect.offsetBy(dx: 0.0, dy: -3.0), .bottom),
|
||||
displayDuration: .default,
|
||||
cornerRadius: 10.0,
|
||||
shouldDismissOnTouch: { _, _ in
|
||||
return .ignore
|
||||
}
|
||||
)
|
||||
self.present(tooltipScreen, in: .current)
|
||||
self.birthdayTooltipController = tooltipScreen
|
||||
}
|
||||
|
||||
func displayChecksTooltip() {
|
||||
self.checksTooltipController?.dismiss()
|
||||
|
||||
|
@ -3412,6 +3412,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
return nil
|
||||
}
|
||||
|
||||
func frameForGiftButton() -> CGRect? {
|
||||
if let textInputPanelNode = self.textInputPanelNode, self.inputPanelNode === textInputPanelNode {
|
||||
return textInputPanelNode.frameForGiftButton().flatMap {
|
||||
return $0.offsetBy(dx: textInputPanelNode.frame.minX, dy: textInputPanelNode.frame.minY)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var isTextInputPanelActive: Bool {
|
||||
return self.inputPanelNode is ChatTextInputPanelNode
|
||||
}
|
||||
|
@ -167,7 +167,11 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
if case .scheduledMessages = chatPresentationInterfaceState.subject {
|
||||
} else {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
|
||||
let giftIsEnabled = !premiumConfiguration.isPremiumDisabled && premiumConfiguration.showPremiumGiftInAttachMenu && premiumConfiguration.showPremiumGiftInTextField
|
||||
var giftIsEnabled = false
|
||||
giftIsEnabled = !premiumConfiguration.isPremiumDisabled && premiumConfiguration.showPremiumGiftInAttachMenu && premiumConfiguration.showPremiumGiftInTextField
|
||||
#if DEBUG
|
||||
giftIsEnabled = true
|
||||
#endif
|
||||
if isTextEmpty, giftIsEnabled, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, !peer.isDeleted && peer.botInfo == nil && !peer.flags.contains(.isSupport) && !peer.isPremium && !chatPresentationInterfaceState.premiumGiftOptions.isEmpty && chatPresentationInterfaceState.suggestPremiumGift {
|
||||
accessoryItems.append(.gift)
|
||||
}
|
||||
|
@ -117,44 +117,32 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
||||
}
|
||||
|
||||
var displayActionsPanel = false
|
||||
if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus {
|
||||
if let peerStatusSettings = contactStatus.peerStatusSettings {
|
||||
if !peerStatusSettings.flags.isEmpty {
|
||||
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.canShareContact) {
|
||||
displayActionsPanel = true
|
||||
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.suggestAddMembers) {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
}
|
||||
if peerStatusSettings.requestChatTitle != nil {
|
||||
if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings {
|
||||
if !peerStatusSettings.flags.isEmpty {
|
||||
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.canShareContact) {
|
||||
displayActionsPanel = true
|
||||
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.suggestAddMembers) {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
}
|
||||
if peerStatusSettings.requestChatTitle != nil {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedContext == nil || selectedContext! <= .pinnedMessage) {
|
||||
if displayActionsPanel {
|
||||
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
|
||||
return currentPanel
|
||||
} else if let controllerInteraction = controllerInteraction {
|
||||
let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
} else if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
|
||||
if let currentPanel = currentPanel as? ChatManagingBotTitlePanelNode {
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatManagingBotTitlePanelNode(context: context)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) {
|
||||
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
|
||||
return currentPanel
|
||||
} else if let controllerInteraction = controllerInteraction {
|
||||
let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,472 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import ChatPresentationInterfaceState
|
||||
import ComponentFlow
|
||||
import AvatarNode
|
||||
import MultilineTextComponent
|
||||
import PlainButtonComponent
|
||||
import ComponentDisplayAdapters
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import BundleIconComponent
|
||||
import ContextUI
|
||||
import SwiftSignalKit
|
||||
|
||||
private final class ChatManagingBotTitlePanelComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let insets: UIEdgeInsets
|
||||
let peer: EnginePeer
|
||||
let managesChat: Bool
|
||||
let isPaused: Bool
|
||||
let toggleIsPaused: () -> Void
|
||||
let openSettings: (UIView) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
insets: UIEdgeInsets,
|
||||
peer: EnginePeer,
|
||||
managesChat: Bool,
|
||||
isPaused: Bool,
|
||||
toggleIsPaused: @escaping () -> Void,
|
||||
openSettings: @escaping (UIView) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.insets = insets
|
||||
self.peer = peer
|
||||
self.managesChat = managesChat
|
||||
self.isPaused = isPaused
|
||||
self.toggleIsPaused = toggleIsPaused
|
||||
self.openSettings = openSettings
|
||||
}
|
||||
|
||||
static func ==(lhs: ChatManagingBotTitlePanelComponent, rhs: ChatManagingBotTitlePanelComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings != rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.insets != rhs.insets {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.managesChat != rhs.managesChat {
|
||||
return false
|
||||
}
|
||||
if lhs.isPaused != rhs.isPaused {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let text = ComponentView<Empty>()
|
||||
private var avatarNode: AvatarNode?
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
private let settingsButton = ComponentView<Empty>()
|
||||
|
||||
private var component: ChatManagingBotTitlePanelComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ChatManagingBotTitlePanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let topInset: CGFloat = 6.0
|
||||
let bottomInset: CGFloat = 6.0
|
||||
let avatarDiameter: CGFloat = 36.0
|
||||
let avatarTextSpacing: CGFloat = 10.0
|
||||
let titleTextSpacing: CGFloat = 1.0
|
||||
let leftInset: CGFloat = component.insets.left + 12.0
|
||||
let rightInset: CGFloat = component.insets.right + 10.0
|
||||
let actionAndSettingsButtonsSpacing: CGFloat = 8.0
|
||||
|
||||
//TODO:localize
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.isPaused ? "START" : "STOP", font: Font.semibold(15.0), textColor: component.theme.list.itemCheckColors.foregroundColor))
|
||||
)),
|
||||
background: AnyComponent(RoundedRectangle(
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
cornerRadius: nil
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
contentInsets: UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.toggleIsPaused()
|
||||
},
|
||||
animateAlpha: true,
|
||||
animateScale: false,
|
||||
animateContents: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 150.0, height: 100.0)
|
||||
)
|
||||
|
||||
let settingsButtonSize = self.settingsButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Context Menu/Customize",
|
||||
tintColor: component.theme.rootController.navigationBar.controlColor
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
minSize: CGSize(width: 1.0, height: 40.0),
|
||||
contentInsets: UIEdgeInsets(top: 0.0, left: 2.0, bottom: 0.0, right: 2.0),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard let settingsButtonView = self.settingsButton.view else {
|
||||
return
|
||||
}
|
||||
component.openSettings(settingsButtonView)
|
||||
},
|
||||
animateAlpha: true,
|
||||
animateScale: false,
|
||||
animateContents: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 150.0, height: 100.0)
|
||||
)
|
||||
|
||||
let maxTextWidth: CGFloat = availableSize.width - leftInset - avatarDiameter - avatarTextSpacing - rightInset - actionButtonSize.width - actionAndSettingsButtonsSpacing - settingsButtonSize.width - 8.0
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.semibold(16.0), textColor: component.theme.rootController.navigationBar.primaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
||||
)
|
||||
//TODO:localize
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "bot has access to this chat", font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
||||
)
|
||||
|
||||
let size = CGSize(width: availableSize.width, height: topInset + titleSize.height + titleTextSpacing + textSize.height + bottomInset)
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset + avatarDiameter + avatarTextSpacing, y: topInset), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.layer.anchorPoint = CGPoint()
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
||||
}
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleTextSpacing), size: textSize)
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
textView.layer.anchorPoint = CGPoint()
|
||||
self.addSubview(textView)
|
||||
}
|
||||
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||
transition.setPosition(view: textView, position: textFrame.origin)
|
||||
}
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - avatarDiameter) * 0.5)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
|
||||
let avatarNode: AvatarNode
|
||||
if let current = self.avatarNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
|
||||
self.avatarNode = avatarNode
|
||||
self.addSubview(avatarNode.view)
|
||||
}
|
||||
avatarNode.frame = avatarFrame
|
||||
avatarNode.updateSize(size: avatarFrame.size)
|
||||
avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer)
|
||||
|
||||
let settingsButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - settingsButtonSize.width, y: floor((size.height - settingsButtonSize.height) * 0.5)), size: settingsButtonSize)
|
||||
if let settingsButtonView = self.settingsButton.view {
|
||||
if settingsButtonView.superview == nil {
|
||||
self.addSubview(settingsButtonView)
|
||||
}
|
||||
transition.setFrame(view: settingsButtonView, frame: settingsButtonFrame)
|
||||
}
|
||||
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: settingsButtonFrame.minX - actionAndSettingsButtonsSpacing - actionButtonSize.width, y: floor((size.height - actionButtonSize.height) * 0.5)), size: actionButtonSize)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
if actionButtonView.superview == nil {
|
||||
self.addSubview(actionButtonView)
|
||||
}
|
||||
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let context: AccountContext
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let content = ComponentView<Empty>()
|
||||
|
||||
private var chatLocation: ChatLocation?
|
||||
private var theme: PresentationTheme?
|
||||
private var managingBot: ChatManagingBot?
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
}
|
||||
|
||||
private func toggleIsPaused() {
|
||||
guard let chatPeerId = self.chatLocation?.peerId else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (self.context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: self.context.account.peerId)
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] bot in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let bot else {
|
||||
return
|
||||
}
|
||||
|
||||
var recipients = bot.recipients
|
||||
var additionalPeers = recipients.additionalPeers
|
||||
if additionalPeers.contains(chatPeerId) {
|
||||
additionalPeers.remove(chatPeerId)
|
||||
} else {
|
||||
additionalPeers.insert(chatPeerId)
|
||||
}
|
||||
recipients = TelegramBusinessRecipients(
|
||||
categories: recipients.categories,
|
||||
additionalPeers: additionalPeers,
|
||||
exclude: recipients.exclude
|
||||
)
|
||||
|
||||
let _ = self.context.engine.accountData.setAccountConnectedBot(bot: TelegramAccountConnectedBot(
|
||||
id: bot.id,
|
||||
recipients: recipients,
|
||||
canReply: bot.canReply
|
||||
)).startStandalone()
|
||||
})
|
||||
}
|
||||
|
||||
private func openSettingsMenu(sourceView: UIView) {
|
||||
guard let interfaceInteraction = self.interfaceInteraction else {
|
||||
return
|
||||
}
|
||||
guard let chatController = interfaceInteraction.chatController() else {
|
||||
return
|
||||
}
|
||||
guard let managingBot = self.managingBot else {
|
||||
return
|
||||
}
|
||||
let _ = managingBot
|
||||
|
||||
let strings = self.context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||
let _ = strings
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Remove bot from this chat", textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
})))
|
||||
if let url = managingBot.settingsUrl {
|
||||
items.append(.action(ContextMenuActionItem(text: "Manage Bot", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url, skipUrlAuth: false)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let chatController = interfaceInteraction.chatController() else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.openResolvedUrl(
|
||||
result,
|
||||
context: self.context,
|
||||
urlContext: .generic,
|
||||
navigationController: chatController.navigationController as? NavigationController,
|
||||
forceExternal: false,
|
||||
openPeer: { [weak self] peer, navigation in
|
||||
guard let self, let chatController = interfaceInteraction.chatController() else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = chatController.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
switch navigation {
|
||||
case let .chat(_, subject, peekData):
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: subject, peekData: peekData))
|
||||
case let .withBotStartPayload(botStart):
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botStart: botStart, keepStack: .always))
|
||||
case let .withAttachBot(attachBotStart):
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
|
||||
case let .withBotApp(botAppStart):
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botAppStart: botAppStart))
|
||||
case .info:
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer, let chatController = interfaceInteraction.chatController() else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = chatController.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
if let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
sendFile: nil,
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
joinVoiceChat: nil,
|
||||
present: { [weak chatController] c, a in
|
||||
chatController?.present(c, in: .window(.root), with: a)
|
||||
},
|
||||
dismissInput: {
|
||||
},
|
||||
contentContext: nil,
|
||||
progress: nil,
|
||||
completion: nil
|
||||
)
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: chatController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
||||
interfaceInteraction.presentController(contextController, nil)
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
|
||||
self.chatLocation = interfaceState.chatLocation
|
||||
self.managingBot = interfaceState.contactStatus?.managingBot
|
||||
|
||||
if interfaceState.theme !== self.theme {
|
||||
self.theme = interfaceState.theme
|
||||
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
|
||||
if let managingBot = interfaceState.contactStatus?.managingBot {
|
||||
let contentSize = self.content.update(
|
||||
transition: Transition(transition),
|
||||
component: AnyComponent(ChatManagingBotTitlePanelComponent(
|
||||
context: self.context,
|
||||
theme: interfaceState.theme,
|
||||
strings: interfaceState.strings,
|
||||
insets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: 0.0, right: rightInset),
|
||||
peer: managingBot.bot,
|
||||
managesChat: managingBot.canReply,
|
||||
isPaused: managingBot.isPaused,
|
||||
toggleIsPaused: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.toggleIsPaused()
|
||||
},
|
||||
openSettings: { [weak self] sourceView in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openSettingsMenu(sourceView: sourceView)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width, height: 1000.0)
|
||||
)
|
||||
if let contentView = self.content.view {
|
||||
if contentView.superview == nil {
|
||||
self.view.addSubview(contentView)
|
||||
}
|
||||
transition.updateFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: contentSize))
|
||||
}
|
||||
|
||||
return LayoutResult(backgroundHeight: contentSize.height, insetHeight: contentSize.height, hitTestSlop: 0.0)
|
||||
} else {
|
||||
return LayoutResult(backgroundHeight: 0.0, insetHeight: 0.0, hitTestSlop: 0.0)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceView: UIView
|
||||
|
||||
init(controller: ViewController, sourceView: UIView) {
|
||||
self.controller = controller
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
@ -282,8 +282,9 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
}, openPremiumIntro: {
|
||||
}, openPremiumGift: {
|
||||
}, openPremiumGift: { _ in
|
||||
}, openActiveSessions: {
|
||||
}, openBirthdaySetup: {
|
||||
}, performActiveSessionAction: { _, _ in
|
||||
}, openChatFolderUpdates: {
|
||||
}, hideChatFolderUpdates: {
|
||||
|
@ -4594,6 +4594,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func frameForGiftButton() -> CGRect? {
|
||||
for (item, button) in self.accessoryItemButtons {
|
||||
if case .gift = item {
|
||||
return button.frame.insetBy(dx: 0.0, dy: 6.0)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeSnapshotForTransition() -> ChatMessageTransitionNodeImpl.Source.TextInput? {
|
||||
guard let backgroundImage = self.transparentTextInputBackgroundImage else {
|
||||
|
@ -156,10 +156,12 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
|
||||
},
|
||||
openPremiumIntro: {
|
||||
},
|
||||
openPremiumGift: {
|
||||
openPremiumGift: { _ in
|
||||
},
|
||||
openActiveSessions: {
|
||||
},
|
||||
openBirthdaySetup: {
|
||||
},
|
||||
performActiveSessionAction: { _, _ in
|
||||
},
|
||||
openChatFolderUpdates: {
|
||||
|
@ -52,7 +52,7 @@ final class ComposeControllerNode: ASDisplayNode {
|
||||
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: {
|
||||
openCreateNewChannelImpl?()
|
||||
})
|
||||
], includeChatList: false, topPeers: false)), onlyWriteable: false, displayPermissionPlaceholder: false)
|
||||
], includeChatList: false, topPeers: .none)), onlyWriteable: false, displayPermissionPlaceholder: false)
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -169,11 +169,17 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
}
|
||||
self.contentNode = .chats(chatListNode)
|
||||
} else {
|
||||
var displayTopPeers = false
|
||||
if case .premiumGifting = mode {
|
||||
displayTopPeers = true
|
||||
let displayTopPeers: ContactListPresentation.TopPeers
|
||||
if case let .premiumGifting(topSectionTitle, topSectionPeers) = mode {
|
||||
if let topSectionTitle {
|
||||
displayTopPeers = .custom(title: topSectionTitle, peerIds: topSectionPeers)
|
||||
} else {
|
||||
displayTopPeers = .recent
|
||||
}
|
||||
} else if case .requestedUsersSelection = mode {
|
||||
displayTopPeers = true
|
||||
displayTopPeers = .recent
|
||||
} else {
|
||||
displayTopPeers = .none
|
||||
}
|
||||
let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, onlyWriteable: onlyWriteable, selectionState: ContactListNodeGroupSelectionState())
|
||||
self.contentNode = .contacts(contactListNode)
|
||||
|
@ -68,7 +68,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
||||
self.filters = filters
|
||||
|
||||
var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
|
||||
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false, topPeers: false)), filters: filters, onlyWriteable: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
|
||||
self.contactListNode = ContactListNode(context: context, updatedPresentationData: (presentationData, self.presentationDataPromise.get()), presentation: .single(.natural(options: options, includeChatList: false, topPeers: .none)), filters: filters, onlyWriteable: false, displayCallIcons: displayCallIcons, contextAction: multipleSelection ? { peer, node, gesture, _, _ in
|
||||
contextActionImpl?(peer, node, gesture, nil)
|
||||
} : nil, multipleSelection: multipleSelection)
|
||||
|
||||
|
@ -2115,7 +2115,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
|
||||
let limit: Int32 = 10
|
||||
var reachedLimitImpl: ((Int32) -> Void)?
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .premiumGifting, options: [], isPeerEnabled: { peer in
|
||||
|
||||
let mode: ContactMultiselectionControllerMode
|
||||
if case let .chatList(peerIds) = source {
|
||||
mode = .premiumGifting(topSectionTitle: "🎂 BIRTHDAY TODAY", topSectionPeers: peerIds)
|
||||
} else {
|
||||
mode = .premiumGifting(topSectionTitle: nil, topSectionPeers: [])
|
||||
}
|
||||
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: mode, options: [], isPeerEnabled: { peer in
|
||||
if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) {
|
||||
return true
|
||||
} else {
|
||||
@ -2314,7 +2322,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, isEditing: isEditing, parentNavigationController: parentNavigationController, sendSticker: sendSticker)
|
||||
}
|
||||
|
||||
public func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile) -> Void) -> ViewController {
|
||||
public func makeStickerEditorScreen(context: AccountContext, source: Any, transitionArguments: (UIView, CGRect, UIImage?)?, completion: @escaping (TelegramMediaFile, @escaping () -> Void) -> Void) -> ViewController {
|
||||
let subject: MediaEditorScreen.Subject
|
||||
let mode: MediaEditorScreen.Mode.StickerEditorMode
|
||||
if let file = source as? TelegramMediaFile {
|
||||
@ -2347,9 +2355,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
return nil
|
||||
}, completion: { result, commit in
|
||||
commit({})
|
||||
if case let .sticker(file) = result.media {
|
||||
completion(file)
|
||||
completion(file, {
|
||||
commit({})
|
||||
})
|
||||
}
|
||||
} as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
|
||||
)
|
||||
|
@ -441,8 +441,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
var resourceReference: MediaResourceReference?
|
||||
|
||||
if let thumbnail = info.thumbnail {
|
||||
if info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) {
|
||||
thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, info.flags.contains(.isVideo))
|
||||
if thumbnail.typeHint != .generic {
|
||||
thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, thumbnail.typeHint == .video)
|
||||
} else {
|
||||
thumbnailItem = .still(thumbnail)
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ swift_library(
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/LocalAuth",
|
||||
"//submodules/InstantPageCache"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -23,6 +23,7 @@ import PromptUI
|
||||
import PhoneNumberFormat
|
||||
import QrCodeUI
|
||||
import InstantPageUI
|
||||
import InstantPageCache
|
||||
import LocalAuth
|
||||
|
||||
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
|
||||
@ -1073,8 +1074,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.requestBiometryAuth()
|
||||
case "web_app_biometry_update_token":
|
||||
var tokenData: Data?
|
||||
if let json, let tokenDataValue = json["token"] as? String, !tokenDataValue.isEmpty {
|
||||
tokenData = tokenDataValue.data(using: .utf8)
|
||||
if let json, let tokenDataValue = json["token"] as? Data {
|
||||
tokenData = tokenDataValue
|
||||
}
|
||||
self.requestBiometryUpdateToken(tokenData: tokenData)
|
||||
default:
|
||||
@ -1513,7 +1514,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
||||
|
||||
Thread { [weak self] in
|
||||
let key = LocalAuth.getOrCreatePrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
if key == nil {
|
||||
key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
}
|
||||
|
||||
let decryptedData: LocalAuth.DecryptionResult
|
||||
if let key {
|
||||
@ -1563,9 +1567,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
data["status"] = isAuthorized ? "authorized" : "failed"
|
||||
if isAuthorized {
|
||||
if let tokenData {
|
||||
data["token"] = String(data: tokenData, encoding: .utf8) ?? ""
|
||||
data["token"] = tokenData
|
||||
} else {
|
||||
data["token"] = ""
|
||||
data["token"] = Data()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1589,7 +1593,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
if let tokenData {
|
||||
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
||||
Thread { [weak self] in
|
||||
let key = LocalAuth.getOrCreatePrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
if key == nil {
|
||||
key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
}
|
||||
|
||||
var encryptedData: TelegramBotBiometricsState.OpaqueToken?
|
||||
if let key {
|
||||
@ -1612,28 +1619,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
state.opaqueToken = encryptedData
|
||||
return state
|
||||
})
|
||||
|
||||
var data: [String: Any] = [:]
|
||||
data["status"] = "updated"
|
||||
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||
return
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||
} else {
|
||||
var data: [String: Any] = [:]
|
||||
data["status"] = "failed"
|
||||
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||
return
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
@ -1643,17 +1628,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
state.opaqueToken = nil
|
||||
return state
|
||||
})
|
||||
|
||||
var data: [String: Any] = [:]
|
||||
data["status"] = "removed"
|
||||
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||
return
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1876,6 +1850,25 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self?.controllerNode.webView?.reload()
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_TermsOfUse, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
c.dismiss(completion: nil)
|
||||
|
||||
guard let self, let navigationController = self.getNavigationController() else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let _ = (cachedWebAppTermsPage(context: context)
|
||||
|> deliverOnMainQueue).startStandalone(next: { resolvedUrl in
|
||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: true, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak self] c, arguments in
|
||||
self?.push(c)
|
||||
}, dismissInput: {}, contentContext: nil, progress: nil, completion: nil)
|
||||
})
|
||||
})))
|
||||
|
||||
if let _ = attachMenuBot, [.attachMenu, .settings, .generic].contains(source) {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
|
@ -159,7 +159,7 @@ final class WebAppWebView: WKWebView {
|
||||
self.interactiveTransitionGestureRecognizerTest = { point -> Bool in
|
||||
return point.x > 30.0
|
||||
}
|
||||
self.allowsBackForwardNavigationGestures = true
|
||||
self.allowsBackForwardNavigationGestures = false
|
||||
if #available(iOS 16.4, *) {
|
||||
self.isInspectable = true
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user