Merge commit 'db1d4422cb97018bbc7d0d554a63b70361471d4f'

This commit is contained in:
Isaac 2024-03-15 15:11:02 +04:00
commit c05c6b8e23
92 changed files with 795 additions and 1426 deletions

View File

@ -11616,3 +11616,6 @@ Sorry for the inconvenience.";
"ChannelBoost.NoAds" = "Switch Off Ads"; "ChannelBoost.NoAds" = "Switch Off Ads";
"ChannelBoost.EnableNoAdsLevelText" = "Your channel needs **Level %1$@** to 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";

View File

@ -1001,7 +1001,7 @@ public protocol SharedAccountContext: AnyObject {
func makeMediaPickerScreen(context: AccountContext, hasSearch: Bool, completion: @escaping (Any) -> Void) -> ViewController 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 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 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

View File

@ -72,7 +72,7 @@ public enum ContactMultiselectionControllerMode {
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool) case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
case channelCreation case channelCreation
case chatSelection(ChatSelection) case chatSelection(ChatSelection)
case premiumGifting case premiumGifting(topSectionTitle: String?, topSectionPeers: [EnginePeer.Id])
case requestedUsersSelection case requestedUsersSelection
} }

View File

@ -45,7 +45,7 @@ public enum PremiumGiftSource: Equatable {
case profile case profile
case attachMenu case attachMenu
case settings case settings
case chatList case chatList([EnginePeer.Id])
case channelBoost case channelBoost
case deeplink(String?) case deeplink(String?)
} }

View File

@ -2308,8 +2308,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openStorageManagement: { }, openStorageManagement: {
}, openPasswordSetup: { }, openPasswordSetup: {
}, openPremiumIntro: { }, openPremiumIntro: {
}, openPremiumGift: { }, openPremiumGift: { _ in
}, openActiveSessions: { }, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in }, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: { }, openChatFolderUpdates: {
}, hideChatFolderUpdates: { }, 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 let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: { }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in }, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in }, openStories: { _, _ in

View File

@ -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 let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, 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 }, editPeer: { _ in
}) })
interaction.isInlineMode = isInlineMode interaction.isInlineMode = isInlineMode

View File

@ -101,8 +101,9 @@ public final class ChatListNodeInteraction {
let openStorageManagement: () -> Void let openStorageManagement: () -> Void
let openPasswordSetup: () -> Void let openPasswordSetup: () -> Void
let openPremiumIntro: () -> Void let openPremiumIntro: () -> Void
let openPremiumGift: () -> Void let openPremiumGift: ([EnginePeer.Id]) -> Void
let openActiveSessions: () -> Void let openActiveSessions: () -> Void
let openBirthdaySetup: () -> Void
let performActiveSessionAction: (NewSessionReview, Bool) -> Void let performActiveSessionAction: (NewSessionReview, Bool) -> Void
let openChatFolderUpdates: () -> Void let openChatFolderUpdates: () -> Void
let hideChatFolderUpdates: () -> Void let hideChatFolderUpdates: () -> Void
@ -154,8 +155,9 @@ public final class ChatListNodeInteraction {
openStorageManagement: @escaping () -> Void, openStorageManagement: @escaping () -> Void,
openPasswordSetup: @escaping () -> Void, openPasswordSetup: @escaping () -> Void,
openPremiumIntro: @escaping () -> Void, openPremiumIntro: @escaping () -> Void,
openPremiumGift: @escaping () -> Void, openPremiumGift: @escaping ([EnginePeer.Id]) -> Void,
openActiveSessions: @escaping () -> Void, openActiveSessions: @escaping () -> Void,
openBirthdaySetup: @escaping () -> Void,
performActiveSessionAction: @escaping (NewSessionReview, Bool) -> Void, performActiveSessionAction: @escaping (NewSessionReview, Bool) -> Void,
openChatFolderUpdates: @escaping () -> Void, openChatFolderUpdates: @escaping () -> Void,
hideChatFolderUpdates: @escaping () -> Void, hideChatFolderUpdates: @escaping () -> Void,
@ -196,6 +198,7 @@ public final class ChatListNodeInteraction {
self.openPremiumIntro = openPremiumIntro self.openPremiumIntro = openPremiumIntro
self.openPremiumGift = openPremiumGift self.openPremiumGift = openPremiumGift
self.openActiveSessions = openActiveSessions self.openActiveSessions = openActiveSessions
self.openBirthdaySetup = openBirthdaySetup
self.performActiveSessionAction = performActiveSessionAction self.performActiveSessionAction = performActiveSessionAction
self.openChatFolderUpdates = openChatFolderUpdates self.openChatFolderUpdates = openChatFolderUpdates
self.hideChatFolderUpdates = hideChatFolderUpdates self.hideChatFolderUpdates = hideChatFolderUpdates
@ -732,7 +735,11 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore: case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
nodeInteraction?.openPremiumIntro() nodeInteraction?.openPremiumIntro()
case .xmasPremiumGift: case .xmasPremiumGift:
nodeInteraction?.openPremiumGift() nodeInteraction?.openPremiumGift([])
case .setupBirthday:
nodeInteraction?.openBirthdaySetup()
case let .birthdayPremiumGift(peers):
nodeInteraction?.openPremiumGift(peers.map { $0.id })
case .reviewLogin: case .reviewLogin:
break break
} }
@ -1064,7 +1071,11 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore: case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore:
nodeInteraction?.openPremiumIntro() nodeInteraction?.openPremiumIntro()
case .xmasPremiumGift: case .xmasPremiumGift:
nodeInteraction?.openPremiumGift() nodeInteraction?.openPremiumGift([])
case .setupBirthday:
nodeInteraction?.openBirthdaySetup()
case let .birthdayPremiumGift(peers):
nodeInteraction?.openPremiumGift(peers.map { $0.id })
case .reviewLogin: case .reviewLogin:
break 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) let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads, forceDark: false, dismissed: nil)
self.push?(controller) self.push?(controller)
}, openPremiumGift: { [weak self] in }, openPremiumGift: { [weak self] peerIds in
guard let self else { guard let self else {
return 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) self.push?(controller)
}, openActiveSessions: { [weak self] in }, openActiveSessions: { [weak self] in
guard let self else { guard let self else {
@ -1707,6 +1718,8 @@ public final class ChatListNode: ListView {
let recentSessionsController = self.context.sharedContext.makeRecentSessionsController(context: self.context, activeSessionsContext: activeSessionsContext) let recentSessionsController = self.context.sharedContext.makeRecentSessionsController(context: self.context, activeSessionsContext: activeSessionsContext)
self.push?(recentSessionsController) self.push?(recentSessionsController)
}) })
}, openBirthdaySetup: {
}, performActiveSessionAction: { [weak self] newSessionReview, isPositive in }, performActiveSessionAction: { [weak self] newSessionReview, isPositive in
guard let self else { guard let self else {
return 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 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 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: default:
break break
} }
@ -1899,7 +1918,9 @@ public final class ChatListNode: ListView {
return .single(.setupPassword) return .single(.setupPassword)
} }
} }
if suggestions.contains(.xmasPremiumGift) { if suggestions.contains(.setupBirthday) {
return .single(.setupBirthday)
} else if suggestions.contains(.xmasPremiumGift) {
return .single(.xmasPremiumGift) return .single(.xmasPremiumGift)
} else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager { } else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager {
return inAppPurchaseManager.availableProducts return inAppPurchaseManager.availableProducts

View File

@ -86,6 +86,8 @@ public enum ChatListNotice: Equatable {
case premiumAnnualDiscount(discount: Int32) case premiumAnnualDiscount(discount: Int32)
case premiumRestore(discount: Int32) case premiumRestore(discount: Int32)
case xmasPremiumGift case xmasPremiumGift
case setupBirthday
case birthdayPremiumGift(peers: [EnginePeer])
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int) case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
} }

View File

@ -220,6 +220,23 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
case .xmasPremiumGift: 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 })) 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) 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): case let .reviewLogin(newSessionReview, totalCount):
spacing = 2.0 spacing = 2.0
alignment = .center alignment = .center

View File

@ -231,52 +231,17 @@ public enum ChatRecordedMediaPreview: Equatable {
case video(Video) 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 struct ChatContactStatus: Equatable {
public var canAddContact: Bool public var canAddContact: Bool
public var canReportIrrelevantLocation: Bool public var canReportIrrelevantLocation: Bool
public var peerStatusSettings: PeerStatusSettings? public var peerStatusSettings: PeerStatusSettings?
public var invitedBy: Peer? 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.canAddContact = canAddContact
self.canReportIrrelevantLocation = canReportIrrelevantLocation self.canReportIrrelevantLocation = canReportIrrelevantLocation
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
self.invitedBy = invitedBy self.invitedBy = invitedBy
self.managingBot = managingBot
} }
public var isEmpty: Bool { public var isEmpty: Bool {
@ -305,9 +270,6 @@ public struct ChatContactStatus: Equatable {
if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) { if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) {
return false return false
} }
if lhs.managingBot != rhs.managingBot {
return false
}
return true return true
} }
} }

View File

@ -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 entries: [ContactListNodeEntry] = []
var commonHeader: ListViewItemHeader? var commonHeader: ListViewItemHeader?
@ -528,7 +528,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
if !topPeers.isEmpty { if !topPeers.isEmpty {
let hasDeselectAll = !(selectionState?.selectedPeerIndices ?? [:]).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() 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 orderedByPresence(options: [ContactListAdditionalOption])
case natural(options: [ContactListAdditionalOption], includeChatList: Bool, topPeers: Bool) case natural(options: [ContactListAdditionalOption], includeChatList: Bool, topPeers: TopPeers)
case search(Search) case search(Search)
public var sortOrder: ContactsSortOrder? { public var sortOrder: ContactsSortOrder? {
@ -1110,11 +1116,11 @@ public final class ContactListNode: ASDisplayNode {
|> mapToSignal { presentation in |> mapToSignal { presentation in
var generateSections = false var generateSections = false
var includeChatList = false var includeChatList = false
var displayTopPeers = false var displayTopPeers: ContactListPresentation.TopPeers = .none
if case let .natural(_, includeChatListValue, displayTopPeersValue) = presentation { if case let .natural(_, includeChatListValue, topPeersValue) = presentation {
generateSections = true generateSections = true
includeChatList = includeChatListValue includeChatList = includeChatListValue
displayTopPeers = displayTopPeersValue displayTopPeers = topPeersValue
} }
if case let .search(search) = presentation { if case let .search(search) = presentation {
@ -1421,7 +1427,7 @@ public final class ContactListNode: ASDisplayNode {
peers.append(.deviceContact(stableId, contact.0)) 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) 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)) 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([]) chatListSignal = .single([])
} }
let recentPeers: Signal<RecentPeers, NoError> let topPeers: Signal<[EnginePeer], NoError>
if displayTopPeers { let topPeersSectionTitle: String?
recentPeers = context.engine.peers.recentPeers() switch displayTopPeers {
} else { case .recent:
recentPeers = .single(.disabled) 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( return (combineLatest(
@ -1497,9 +1528,9 @@ public final class ContactListNode: ASDisplayNode {
contactsAuthorization.get(), contactsAuthorization.get(),
contactsWarningSuppressed.get(), contactsWarningSuppressed.get(),
self.storySubscriptions.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 let signal = deferred { () -> Signal<ContactsListNodeTransition, NoError> in
if !view.2.isEmpty { if !view.2.isEmpty {
context.account.viewTracker.refreshCanSendMessagesForPeerIds(peerIds: Array(view.2.keys)) 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 var isEmpty = false
if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty { if (authorizationStatus == .notDetermined || authorizationStatus == .denied) && peers.isEmpty {
isEmpty = true 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 previous = previousEntries.swap(entries)
let previousSelection = previousSelectionState.swap(selectionState) let previousSelection = previousSelectionState.swap(selectionState)
let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds) let previousPendingRemovalPeerIds = previousPendingRemovalPeerIds.swap(pendingRemovalPeerIds)

View File

@ -108,7 +108,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
case .presence: case .presence:
return .orderedByPresence(options: options) return .orderedByPresence(options: options)
case .natural: case .natural:
return .natural(options: options, includeChatList: false, topPeers: false) return .natural(options: options, includeChatList: false, topPeers: .none)
} }
} }

View File

@ -48,8 +48,6 @@ public enum ContextMenuActionItemTextColor {
public enum ContextMenuActionResult { public enum ContextMenuActionResult {
case `default` case `default`
case dismissWithoutContent case dismissWithoutContent
/// Temporary
static var safeStreamRecordingDismissWithoutContent: ContextMenuActionResult { .dismissWithoutContent }
case custom(ContainedViewLayoutTransition) case custom(ContainedViewLayoutTransition)
} }
@ -116,9 +114,11 @@ public final class ContextMenuActionItem {
public struct IconAnimation: Equatable { public struct IconAnimation: Equatable {
public var name: String public var name: String
public var loop: Bool
public init(name: String) { public init(name: String, loop: Bool = false) {
self.name = name self.name = name
self.loop = loop
} }
} }

View File

@ -74,7 +74,7 @@ public protocol ContextControllerActionsStackItem: AnyObject {
var dismissed: (() -> Void)? { get } 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 update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void)
func canBeHighlighted() -> Bool func canBeHighlighted() -> Bool
@ -82,7 +82,7 @@ protocol ContextControllerActionsListItemNode: ASDisplayNode {
func performAction() func performAction()
} }
private final class ContextControllerActionsListActionItemNode: HighlightTrackingButtonNode, ContextControllerActionsListItemNode { public final class ContextControllerActionsListActionItemNode: HighlightTrackingButtonNode, ContextControllerActionsListItemNode {
private let getController: () -> ContextControllerProtocol? private let getController: () -> ContextControllerProtocol?
private let requestDismiss: (ContextMenuActionResult) -> Void private let requestDismiss: (ContextMenuActionResult) -> Void
private let requestUpdateAction: (AnyHashable, ContextMenuActionItem) -> Void private let requestUpdateAction: (AnyHashable, ContextMenuActionItem) -> Void
@ -103,7 +103,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
private var iconDisposable: Disposable? private var iconDisposable: Disposable?
init( public init(
getController: @escaping () -> ContextControllerProtocol?, getController: @escaping () -> ContextControllerProtocol?,
requestDismiss: @escaping (ContextMenuActionResult) -> Void, requestDismiss: @escaping (ContextMenuActionResult) -> Void,
requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void, requestUpdateAction: @escaping (AnyHashable, ContextMenuActionItem) -> Void,
@ -168,7 +168,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
self.iconDisposable?.dispose() self.iconDisposable?.dispose()
} }
override func didLoad() { public override func didLoad() {
super.didLoad() super.didLoad()
self.view.isExclusiveTouch = true 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 return self.item.action != nil
} }
func updateIsHighlighted(isHighlighted: Bool) { public func updateIsHighlighted(isHighlighted: Bool) {
self.highlightBackgroundNode.alpha = isHighlighted ? 1.0 : 0.0 self.highlightBackgroundNode.alpha = isHighlighted ? 1.0 : 0.0
} }
func performAction() { public func performAction() {
self.pressed() 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 self.titleLabelNode.tapAttributeAction != nil {
if let result = self.titleLabelNode.hitTest(self.view.convert(point, to: self.titleLabelNode.view), with: event) { if let result = self.titleLabelNode.hitTest(self.view.convert(point, to: self.titleLabelNode.view), with: event) {
return result return result
@ -223,7 +223,7 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
self.accessibilityLabel = item.text 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 sideInset: CGFloat = 16.0
let verticalInset: CGFloat = 11.0 let verticalInset: CGFloat = 11.0
let titleSubtitleSpacing: CGFloat = 1.0 let titleSubtitleSpacing: CGFloat = 1.0
@ -365,8 +365,8 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
component: AnyComponent(LottieComponent( component: AnyComponent(LottieComponent(
content: LottieComponent.AppBundleContent(name: iconAnimation.name), content: LottieComponent.AppBundleContent(name: iconAnimation.name),
color: titleColor, color: titleColor,
startingPosition: .end, startingPosition: iconAnimation.loop ? .begin : .end,
loop: false loop: iconAnimation.loop
)), )),
environment: {}, environment: {},
containerSize: animatedIconSize containerSize: animatedIconSize

View File

@ -95,8 +95,9 @@ public final class HashtagSearchController: TelegramBaseController {
}, openStorageManagement: { }, openStorageManagement: {
}, openPasswordSetup: { }, openPasswordSetup: {
}, openPremiumIntro: { }, openPremiumIntro: {
}, openPremiumGift: { }, openPremiumGift: { _ in
}, openActiveSessions: { }, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in }, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: { }, openChatFolderUpdates: {
}, hideChatFolderUpdates: { }, hideChatFolderUpdates: {

View File

@ -719,7 +719,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
@objc private func createActionButtonPressed() { @objc private func createActionButtonPressed() {
var proceedImpl: ((String, String?) -> Void)? 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 { if let strongSelf = self, let title = title {
strongSelf.shortNameSuggestionDisposable.set((strongSelf.context.engine.stickers.getStickerSetShortNameSuggestion(title: title) strongSelf.shortNameSuggestionDisposable.set((strongSelf.context.engine.stickers.getStickerSetShortNameSuggestion(title: title)
|> deliverOnMainQueue).start(next: { suggestedShortName in |> deliverOnMainQueue).start(next: { suggestedShortName in
@ -735,7 +735,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, UIScroll
guard let strongSelf = self else { guard let strongSelf = self else {
return 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 { if let shortName = shortName {
self?.createStickerSet(title: title, shortName: shortName) self?.createStickerSet(title: title, shortName: shortName)
} }

View File

@ -48,6 +48,14 @@ public func cachedPrivacyPage(context: AccountContext) -> Signal<ResolvedUrl, No
return cachedInternalInstantPage(context: context, url: privacyUrl) 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> { private func cachedInternalInstantPage(context: AccountContext, url: String) -> Signal<ResolvedUrl, NoError> {
let (cachedUrl, anchor) = extractAnchor(string: url) let (cachedUrl, anchor) = extractAnchor(string: url)
return cachedInstantPage(engine: context.engine, url: cachedUrl) return cachedInstantPage(engine: context.engine, url: cachedUrl)

View File

@ -488,8 +488,8 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
var thumbnailItem: StickerPackThumbnailItem? var thumbnailItem: StickerPackThumbnailItem?
var resourceReference: MediaResourceReference? var resourceReference: MediaResourceReference?
if let thumbnail = item.packInfo.thumbnail { if let thumbnail = item.packInfo.thumbnail {
if item.packInfo.flags.contains(.isAnimated) || item.packInfo.flags.contains(.isVideo) { if thumbnail.typeHint != .generic {
thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, item.packInfo.flags.contains(.isVideo), item.packInfo.flags.contains(.isCustomTemplateEmoji)) thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions, thumbnail.typeHint == .video, item.packInfo.flags.contains(.isCustomTemplateEmoji))
} else { } else {
thumbnailItem = .still(thumbnail) thumbnailItem = .still(thumbnail)
} }
@ -844,7 +844,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
var imageSize = PixelDimensions(width: 512, height: 512) var imageSize = PixelDimensions(width: 512, height: 512)
var immediateThumbnailData: Data? var immediateThumbnailData: Data?
if let data = item.packInfo.immediateThumbnailData { 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) imageSize = PixelDimensions(width: 100, height: 100)
} }
immediateThumbnailData = data immediateThumbnailData = data

View File

@ -21,23 +21,6 @@ public struct LocalAuth {
case error(Error) 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 { public final class PrivateKey {
private let privateKey: SecKey private let privateKey: SecKey
private let publicKey: SecKey private let publicKey: SecKey
@ -81,7 +64,6 @@ public struct LocalAuth {
return .result(result) return .result(result)
} }
} }
#endif
public static var biometricAuthentication: LocalAuthBiometricAuthentication? { public static var biometricAuthentication: LocalAuthBiometricAuthentication? {
let context = LAContext() let context = LAContext()
@ -175,18 +157,7 @@ public struct LocalAuth {
return seedId; return seedId;
} }
public static func getOrCreatePrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { public static func getPrivateKey(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
guard let bundleSeedId = self.bundleSeedId() else { guard let bundleSeedId = self.bundleSeedId() else {
return nil return nil
} }
@ -225,7 +196,6 @@ public struct LocalAuth {
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data) let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
return result return result
#endif
} }
public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool { public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool {
@ -251,10 +221,7 @@ public struct LocalAuth {
return true return true
} }
private static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { public static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
#if targetEnvironment(simulator)
return PrivateKey()
#else
guard let bundleSeedId = self.bundleSeedId() else { guard let bundleSeedId = self.bundleSeedId() else {
return nil return nil
} }
@ -295,6 +262,5 @@ public struct LocalAuth {
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data) let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
return result return result
#endif
} }
} }

View File

@ -2248,7 +2248,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
if !items.isEmpty { if !items.isEmpty {
items.append(.separator) 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) f(.default)
guard let strongSelf = self else { guard let strongSelf = self else {
return return

View File

@ -8,7 +8,6 @@
@property (nonatomic) NSUInteger internalServerErrorCount; @property (nonatomic) NSUInteger internalServerErrorCount;
@property (nonatomic) NSUInteger floodWaitSeconds; @property (nonatomic) NSUInteger floodWaitSeconds;
@property (nonatomic, strong) NSString *floodWaitErrorText;
@property (nonatomic) bool waitingForTokenExport; @property (nonatomic) bool waitingForTokenExport;
@property (nonatomic, strong) id waitingForRequestToComplete; @property (nonatomic, strong) id waitingForRequestToComplete;

View File

@ -808,7 +808,7 @@
} }
restartRequest = true; 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) if (request.errorContext == nil)
request.errorContext = [[MTRequestErrorContext alloc] init]; request.errorContext = [[MTRequestErrorContext alloc] init];
@ -821,32 +821,6 @@
if ([scanner scanInt:&errorWaitTime]) if ([scanner scanInt:&errorWaitTime])
{ {
request.errorContext.floodWaitSeconds = 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) if (request.shouldContinueExecutionWithErrorContext != nil)
{ {

View File

@ -2999,7 +2999,6 @@ final class PostboxImpl {
let startTime = CFAbsoluteTimeGetCurrent() let startTime = CFAbsoluteTimeGetCurrent()
self.valueBox.begin() self.valueBox.begin()
let transaction = Transaction(queue: self.queue, postbox: self) let transaction = Transaction(queue: self.queue, postbox: self)
self.afterBegin(transaction: transaction) self.afterBegin(transaction: transaction)
@ -3014,7 +3013,6 @@ final class PostboxImpl {
postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file), on:\(line)") postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file), on:\(line)")
} }
let _ = self.isInTransaction.swap(false) let _ = self.isInTransaction.swap(false)
if let currentUpdatedState = self.currentUpdatedState { if let currentUpdatedState = self.currentUpdatedState {

View File

@ -19,9 +19,8 @@ public func printOpenFiles() {
var flags: Int32 = 0 var flags: Int32 = 0
var fd: Int32 = 0 var fd: Int32 = 0
var buf = Data(count: Int(MAXPATHLEN) + 1) var buf = Data(count: Int(MAXPATHLEN) + 1)
let maxFd = min(1024, FD_SETSIZE)
while fd < maxFd { while fd < FD_SETSIZE {
errno = 0; errno = 0;
flags = fcntl(fd, F_GETFD, 0); flags = fcntl(fd, F_GETFD, 0);
if flags == -1 && errno != 0 { if flags == -1 && errno != 0 {

View File

@ -848,7 +848,6 @@ struct PremiumIntroConfiguration {
} }
#endif #endif
var businessPerks: [PremiumPerk] = [] var businessPerks: [PremiumPerk] = []
if let values = data["business_promo_order"] as? [String] { if let values = data["business_promo_order"] as? [String] {
for value in values { for value in values {

View File

@ -332,6 +332,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
public var displayTail: Bool = true public var displayTail: Bool = true
public var forceTailToRight: Bool = false public var forceTailToRight: Bool = false
public var forceDark: Bool = false public var forceDark: Bool = false
public var hideBackground: Bool = false
private var didAnimateIn: Bool = false private var didAnimateIn: Bool = false
public private(set) var isAnimatingOut: Bool = false public private(set) var isAnimatingOut: Bool = false
@ -1900,7 +1901,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
externalExpansionView: self.view, externalExpansionView: self.view,
customContentView: nil, customContentView: nil,
useOpaqueTheme: false, useOpaqueTheme: false,
hideBackground: false, hideBackground: self.hideBackground,
stateContext: nil, stateContext: nil,
addImage: nil addImage: nil
) )

View File

@ -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 }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: { }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in }, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in }, openStories: { _, _ in

View File

@ -371,7 +371,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, present: { _ in
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: { }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in }, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in }, openStories: { _, _ in

View File

@ -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) let accentColor = params.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0)
if self.arrowIcon == nil { 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 scaleFactor: CGFloat = 0.8
let imageSize = CGSize(width: floor(templateImage.size.width * scaleFactor), height: floor(templateImage.size.height * scaleFactor)) let imageSize = CGSize(width: floor(templateImage.size.width * scaleFactor), height: floor(templateImage.size.height * scaleFactor))
self.arrowIcon = generateImage(imageSize, contextGenerator: { size, context in self.arrowIcon = generateImage(imageSize, contextGenerator: { size, context in

View File

@ -297,8 +297,8 @@ public func preloadedStickerPackThumbnail(account: Account, info: StickerPackCol
let signal = Signal<Bool, NoError> { subscriber in 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 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 let dataDisposable: Disposable
if info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) { if thumbnail.typeHint != .generic {
dataDisposable = chatMessageAnimationData(mediaBox: account.postbox.mediaBox, resource: thumbnail.resource, isVideo: info.flags.contains(.isVideo), width: 80, height: 80, synchronousLoad: false).start(next: { data in 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 { if data.complete {
subscriber.putNext(true) subscriber.putNext(true)
subscriber.putCompletion() subscriber.putCompletion()

View File

@ -1140,6 +1140,7 @@ private final class StickerPackContainer: ASDisplayNode {
private let stickerPickerInputData = Promise<StickerPickerInput>() private let stickerPickerInputData = Promise<StickerPickerInput>()
private func presentAddStickerOptions() { private func presentAddStickerOptions() {
//TODO:localize
let actionSheet = ActionSheetController(presentationData: self.presentationData) let actionSheet = ActionSheetController(presentationData: self.presentationData)
var items: [ActionSheetItem] = [] var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: "Create a New Sticker", color: .accent, action: { [weak actionSheet, weak self] in 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) 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( let stickerItems = EmojiPagerContentComponent.stickerInputData(
context: self.context, context: self.context,
animationCache: self.context.animationCache, animationCache: self.context.animationCache,
@ -1191,16 +1176,14 @@ private final class StickerPackContainer: ASDisplayNode {
stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers], stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers],
chatPeerId: self.context.account.peerId, chatPeerId: self.context.account.peerId,
hasSearch: true, hasSearch: true,
hasTrending: true, hasTrending: false,
forceHasPremium: true forceHasPremium: true
) )
let signal = combineLatest( let signal = stickerItems
queue: .mainQueue(), |> deliverOnMainQueue
emojiItems, |> map { stickers -> StickerPickerInput in
stickerItems return StickerPickerInputData(emoji: nil, stickers: stickers, gifs: nil)
) |> map { emoji, stickers -> StickerPickerInput in
return StickerPickerInputData(emoji: emoji, stickers: stickers, gifs: nil)
} }
self.stickerPickerInputData.set(signal) self.stickerPickerInputData.set(signal)
@ -1224,7 +1207,7 @@ private final class StickerPackContainer: ASDisplayNode {
context: context, context: context,
source: result, source: result,
transitionArguments: (transitionView, transitionRect, transitionImage), transitionArguments: (transitionView, transitionRect, transitionImage),
completion: { file in completion: { file, commit in
dismissImpl?() dismissImpl?()
let sticker = ImportSticker( let sticker = ImportSticker(
resource: file.resource, resource: file.resource,
@ -1236,6 +1219,8 @@ private final class StickerPackContainer: ASDisplayNode {
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash) let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
let _ = (context.engine.stickers.addStickerToStickerSet(packReference: packReference, sticker: sticker) let _ = (context.engine.stickers.addStickerToStickerSet(packReference: packReference, sticker: sticker)
|> deliverOnMainQueue).start(completed: { |> 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) 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)) (navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
@ -1300,7 +1285,7 @@ private final class StickerPackContainer: ASDisplayNode {
context: context, context: context,
source: initialFile, source: initialFile,
transitionArguments: nil, transitionArguments: nil,
completion: { file in completion: { file, commit in
let sticker = ImportSticker( let sticker = ImportSticker(
resource: file.resource, resource: file.resource,
emojis: ["😀"], 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) let _ = (context.engine.stickers.replaceSticker(previousSticker: .stickerPack(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), media: initialFile), sticker: sticker)
|> deliverOnMainQueue).start(completed: { |> 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) 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)) (navigationController?.viewControllers.last as? ViewController)?.present(packController, in: .window(.root))
}) })
@ -1327,7 +1314,7 @@ private final class StickerPackContainer: ASDisplayNode {
let context = self.context let context = self.context
//TODO:localize //TODO:localize
var dismissImpl: (() -> Void)? 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 { guard let self, let title else {
return return
} }

View File

@ -392,7 +392,7 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode {
items.append(reaction) items.append(reaction)
} }
let selectedItems = ValuePromise<Set<MessageReaction.Reaction>>() let selectedItems = ValuePromise<Set<MessageReaction.Reaction>>(Set())
//TODO:localize //TODO:localize
let reactionContextNode = ReactionContextNode( let reactionContextNode = ReactionContextNode(
context: self.context, context: self.context,
@ -440,6 +440,7 @@ final class EmojiStickerAccessoryNode: SparseNode, PeekControllerAccessoryNode {
layoutImpl?(transition) layoutImpl?(transition)
} }
) )
reactionContextNode.hideBackground = true
reactionContextNode.displayTail = true reactionContextNode.displayTail = true
reactionContextNode.forceTailToRight = true reactionContextNode.forceTailToRight = true
reactionContextNode.forceDark = true reactionContextNode.forceDark = true

View File

@ -671,7 +671,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) } dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) }
dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) } dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) }
dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($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[-1707742823] = { return Api.PeerStories.parse_peerStories($0) }
dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) } dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) }
dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) } dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) }

View File

@ -898,28 +898,26 @@ public extension Api {
} }
public extension Api { public extension Api {
enum PeerSettings: TypeConstructorDescription { 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { 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 { if boxed {
buffer.appendInt32(-1395233698) buffer.appendInt32(-1525149427)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(geoDistance!, 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 {serializeString(requestChatTitle!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 9) != 0 {serializeInt32(requestChatDate!, 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 break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { 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):
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)]) 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) } if Int(_1!) & Int(1 << 9) != 0 {_3 = parseString(reader) }
var _4: Int32? var _4: Int32?
if Int(_1!) & Int(1 << 9) != 0 {_4 = reader.readInt32() } 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 _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil
let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil
let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil
let _c5 = (Int(_1!) & Int(1 << 13) == 0) || _5 != nil if _c1 && _c2 && _c3 && _c4 {
let _c6 = (Int(_1!) & Int(1 << 13) == 0) || _6 != nil return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4)
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6)
} }
else { else {
return nil return nil

View File

@ -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 { public extension Api.functions.account {
static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() 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 { public extension Api.functions.account {
static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()

View File

@ -144,13 +144,13 @@ public class UnauthorizedAccount {
return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in
return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self)) 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 |> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration) in return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in
return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self), transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue) return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self))
} }
} }
|> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<UnauthorizedAccount, NoError> in |> 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, appConfiguration: appConfiguration) 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 |> 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) 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()) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
@ -248,7 +248,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
if let accountState = accountState { if let accountState = accountState {
switch accountState { switch accountState {
case let unauthorizedState as UnauthorizedAccountState: 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 |> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) 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 return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
} }
|> mapToSignal { phoneNumber in |> 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 |> 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)) 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 |> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) 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 { public class Account {
static let sharedQueue = Queue(name: "Account-Shared") static let sharedQueue = Queue(name: "Account-Shared")
@ -1524,8 +1519,7 @@ public func standaloneStateManager(
proxySettings: proxySettings, proxySettings: proxySettings,
networkSettings: networkSettings, networkSettings: networkSettings,
phoneNumber: phoneNumber, phoneNumber: phoneNumber,
useRequestTimeoutTimers: false, useRequestTimeoutTimers: false
appConfiguration: .defaultValue
) )
|> map { network -> AccountStateManager? in |> map { network -> AccountStateManager? in
Logger.shared.log("StandaloneStateManager", "received network") Logger.shared.log("StandaloneStateManager", "received network")

View File

@ -103,7 +103,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId) 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>) let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
if asBigPart { if asBigPart {
let totalParts: Int32 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)) 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 |> mapError { error -> UploadPartError in
if error.errorCode == 400 { if error.errorCode == 400 {
return .invalidMedia 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 return Signal<Void, MTRpcError> { subscriber in
let request = MTRequest() let request = MTRequest()
@ -159,13 +159,6 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
request.dependsOnPasswordEntry = false request.dependsOnPasswordEntry = false
request.shouldContinueExecutionWithErrorContext = { errorContext in 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 return true
} }
@ -302,7 +295,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|> retryRequest |> 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 return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
request.expectedResponseSize = expectedResponseSize ?? 0 request.expectedResponseSize = expectedResponseSize ?? 0
@ -321,9 +314,6 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false 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 return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
request.expectedResponseSize = expectedResponseSize ?? 0 request.expectedResponseSize = expectedResponseSize ?? 0
@ -373,9 +363,6 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false 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 let requestService = self.requestService
return Signal { subscriber in return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
@ -429,9 +416,6 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false return false
} }

View File

@ -104,14 +104,14 @@ private struct DownloadWrapper {
self.useMainConnection = useMainConnection 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 let target: MultiplexedRequestTarget
if self.isCdn { if self.isCdn {
target = .cdn(Int(self.datacenterId)) target = .cdn(Int(self.datacenterId))
} else { } else {
target = .main(Int(self.datacenterId)) 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 |> mapError { error, _ -> MTRpcError in
return error return error
} }
@ -192,7 +192,7 @@ private final class MultipartCdnHashSource {
clusterContext = ClusterContext(disposable: disposable) clusterContext = ClusterContext(disposable: disposable)
self.clusterContexts[offset] = clusterContext 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 |> map { partHashes, _ -> [Int64: Data] in
var parsedPartHashes: [Int64: Data] = [:] var parsedPartHashes: [Int64: Data] = [:]
for part in partHashes { 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? var resourceReferenceValue: MediaResourceReference?
switch resourceReference { switch resourceReference {
case .forceRevalidate: case .forceRevalidate:
@ -348,9 +348,7 @@ private enum MultipartFetchSource {
case .revalidate: case .revalidate:
return .fail(.revalidateMediaReference) return .fail(.revalidateMediaReference)
case let .location(parsedLocation): 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 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)
})
|> mapError { error -> MultipartFetchDownloadError in |> mapError { error -> MultipartFetchDownloadError in
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
return .revalidateMediaReference return .revalidateMediaReference
@ -382,9 +380,7 @@ private enum MultipartFetchSource {
} }
} }
case let .web(_, location): 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 return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit))
onFloodWaitError(error)
})
|> mapError { error -> MultipartFetchDownloadError in |> mapError { error -> MultipartFetchDownloadError in
if error.errorDescription == "WEBFILE_NOT_AVAILABLE" { if error.errorDescription == "WEBFILE_NOT_AVAILABLE" {
return .webfileNotAvailable return .webfileNotAvailable
@ -408,9 +404,7 @@ private enum MultipartFetchSource {
updatedLength += 1 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 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)
})
|> mapError { _ -> MultipartFetchDownloadError in |> mapError { _ -> MultipartFetchDownloadError in
return .generic 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() { func checkState() {
guard let currentIntervals = self.currentIntervals else { guard let currentIntervals = self.currentIntervals else {
return return
@ -849,15 +836,7 @@ private final class MultipartFetchManager {
} }
let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound) 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)
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)
}
})
|> deliverOn(self.queue) |> deliverOn(self.queue)
let partDisposable = MetaDisposable() let partDisposable = MetaDisposable()
self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable) self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable)
@ -940,7 +919,7 @@ private final class MultipartFetchManager {
case let .cdn(_, _, fileToken, _, _, _, masterDownload, _): case let .cdn(_, _, fileToken, _, _, _, masterDownload, _):
if !strongSelf.reuploadingToCdn { if !strongSelf.reuploadingToCdn {
strongSelf.reuploadingToCdn = true 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 |> map { result, _ -> [Api.FileHash] in
return result return result
} }

View File

@ -470,21 +470,12 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload
fetchedResource = .complete() 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 let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in
switch uploadInterface { switch uploadInterface {
case let .download(download): 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): 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 }, progress: { progress in
subscriber.putNext(.progress(progress)) subscriber.putNext(.progress(progress))

View File

@ -33,13 +33,12 @@ private final class RequestData {
let tag: MediaResourceFetchTag? let tag: MediaResourceFetchTag?
let continueInBackground: Bool let continueInBackground: Bool
let automaticFloodWait: Bool let automaticFloodWait: Bool
let onFloodWaitError: ((String) -> Void)?
let expectedResponseSize: Int32? let expectedResponseSize: Int32?
let deserializeResponse: (Buffer) -> Any? let deserializeResponse: (Buffer) -> Any?
let completed: (Any, NetworkResponseInfo) -> Void let completed: (Any, NetworkResponseInfo) -> Void
let error: (MTRpcError, Double) -> 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.id = id
self.consumerId = consumerId self.consumerId = consumerId
self.resourceId = resourceId self.resourceId = resourceId
@ -48,7 +47,6 @@ private final class RequestData {
self.tag = tag self.tag = tag
self.continueInBackground = continueInBackground self.continueInBackground = continueInBackground
self.automaticFloodWait = automaticFloodWait self.automaticFloodWait = automaticFloodWait
self.onFloodWaitError = onFloodWaitError
self.expectedResponseSize = expectedResponseSize self.expectedResponseSize = expectedResponseSize
self.payload = payload self.payload = payload
self.deserializeResponse = deserializeResponse 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 targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground)
let requestId = self.nextId let requestId = self.nextId
self.nextId += 1 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) return data.2(buffer)
}, completed: { result, info in }, completed: { result, info in
completed(result, info) completed(result, info)
@ -256,7 +254,7 @@ private final class MultiplexedRequestManagerContext {
let requestId = request.id let requestId = request.id
selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable)) selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable))
let queue = self.queue 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 { queue.async {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -356,13 +354,13 @@ final class MultiplexedRequestManager {
return disposable 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 return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
self.context.with { context in self.context.with { context in
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
return data.2.parse(buffer) 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 { if let result = result as? T {
subscriber.putNext(result) subscriber.putNext(result)
subscriber.putCompletion() 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 return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
self.context.with { context in self.context.with { context in
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
return data.2.parse(buffer) 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 { if let result = result as? T {
subscriber.putNext((result, info)) subscriber.putNext((result, info))
subscriber.putCompletion() subscriber.putCompletion()

View File

@ -459,7 +459,7 @@ public struct NetworkInitializationArguments {
private let cloudDataContext = Atomic<CloudDataContext?>(value: nil) private let cloudDataContext = Atomic<CloudDataContext?>(value: nil)
#endif #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 return Signal { subscriber in
let queue = Queue() let queue = Queue()
queue.async { queue.async {
@ -612,11 +612,6 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false 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) 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 appDataUpdatedImpl = { [weak network] data in
guard let data = data else { guard let data = data else {
return return
@ -739,22 +734,6 @@ public enum NetworkRequestResult<T> {
case progress(Float, Int32) 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 final class Network: NSObject, MTRequestMessageServiceDelegate {
public let encryptionProvider: EncryptionProvider public let encryptionProvider: EncryptionProvider
@ -787,12 +766,6 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
return self._connectionStatus.get() |> distinctUntilChanged 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() { public func dropConnectionStatus() {
_connectionStatus.set(.single(.waitingForNetwork)) _connectionStatus.set(.single(.waitingForNetwork))
} }
@ -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 let requestService = self.requestService
return Signal { subscriber in return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
@ -1033,9 +1006,6 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false return false
} }
@ -1087,7 +1057,7 @@ 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 let requestService = self.requestService
return Signal { subscriber in return Signal { subscriber in
let request = MTRequest() let request = MTRequest()
@ -1105,9 +1075,6 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
guard let errorContext = errorContext else { guard let errorContext = errorContext else {
return true return true
} }
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
onFloodWaitError(errorText)
}
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
return false 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> { public func retryRequest<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError> {

View File

@ -6,7 +6,7 @@ import SwiftSignalKit
extension PeerStatusSettings { extension PeerStatusSettings {
init(apiSettings: Api.PeerSettings) { init(apiSettings: Api.PeerSettings) {
switch apiSettings { switch apiSettings {
case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl): case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate):
var result = PeerStatusSettings.Flags() var result = PeerStatusSettings.Flags()
if (flags & (1 << 1)) != 0 { if (flags & (1 << 1)) != 0 {
result.insert(.canAddContact) result.insert(.canAddContact)
@ -32,18 +32,7 @@ extension PeerStatusSettings {
if (flags & (1 << 8)) != 0 { if (flags & (1 << 8)) != 0 {
result.insert(.suggestAddMembers) result.insert(.suggestAddMembers)
} }
self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0)
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)
} }
} }
} }

View File

@ -360,11 +360,11 @@ final class ChatHistoryPreloadManager {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
/*#if DEBUG #if DEBUG
if "".isEmpty { if "".isEmpty {
return return
} }
#endif*/ #endif
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = [] var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
for item in loadItems { for item in loadItems {

View File

@ -13,6 +13,7 @@ public enum ServerProvidedSuggestion: String {
case annualPremium = "PREMIUM_ANNUAL" case annualPremium = "PREMIUM_ANNUAL"
case restorePremium = "PREMIUM_RESTORE" case restorePremium = "PREMIUM_RESTORE"
case xmasPremiumGift = "PREMIUM_CHRISTMAS" case xmasPremiumGift = "PREMIUM_CHRISTMAS"
case setupBirthday = "BIRTHDAY_SETUP"
} }
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:]) private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])

View File

@ -551,7 +551,7 @@ public final class CachedChannelData: CachedPeerData {
var peerIds = Set<PeerId>() var peerIds = Set<PeerId>()
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { 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 { } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
} else { } else {

View File

@ -203,7 +203,7 @@ public final class CachedGroupData: CachedPeerData {
self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i") self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i")
self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo] self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo]
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { 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 { } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
} else { } else {

View File

@ -558,7 +558,7 @@ public final class CachedUserData: CachedPeerData {
self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo
self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { 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 { } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
} else { } else {

View File

@ -16,20 +16,7 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
public static let canReportIrrelevantGeoLocation = Flags(rawValue: 1 << 6) public static let canReportIrrelevantGeoLocation = Flags(rawValue: 1 << 6)
public static let autoArchived = Flags(rawValue: 1 << 7) public static let autoArchived = Flags(rawValue: 1 << 7)
public static let suggestAddMembers = Flags(rawValue: 1 << 8) 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 public var flags: PeerStatusSettings.Flags
@ -37,23 +24,20 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
public var requestChatTitle: String? public var requestChatTitle: String?
public var requestChatDate: Int32? public var requestChatDate: Int32?
public var requestChatIsChannel: Bool? public var requestChatIsChannel: Bool?
public var managingBot: ManagingBot?
public init() { public init() {
self.flags = PeerStatusSettings.Flags() self.flags = PeerStatusSettings.Flags()
self.geoDistance = nil self.geoDistance = nil
self.requestChatTitle = nil self.requestChatTitle = nil
self.requestChatDate = 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.flags = flags
self.geoDistance = geoDistance self.geoDistance = geoDistance
self.requestChatTitle = requestChatTitle self.requestChatTitle = requestChatTitle
self.requestChatDate = requestChatDate self.requestChatDate = requestChatDate
self.requestChatIsChannel = requestChatIsChannel self.requestChatIsChannel = requestChatIsChannel
self.managingBot = managingBot
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -62,7 +46,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle") self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle")
self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate") self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate")
self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel") self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel")
self.managingBot = decoder.decodeCodable(ManagingBot.self, forKey: "managingBot")
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -87,11 +70,6 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "requestChatIsChannel") 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 { public func contains(_ member: PeerStatusSettings.Flags) -> Bool {

View File

@ -21,12 +21,6 @@ public struct StickerPackCollectionInfoFlags: OptionSet {
if flags.contains(StickerPackCollectionInfoFlags.isOfficial) { if flags.contains(StickerPackCollectionInfoFlags.isOfficial) {
rawValue |= StickerPackCollectionInfoFlags.isOfficial.rawValue 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) { if flags.contains(StickerPackCollectionInfoFlags.isEmoji) {
rawValue |= StickerPackCollectionInfoFlags.isEmoji.rawValue rawValue |= StickerPackCollectionInfoFlags.isEmoji.rawValue
} }
@ -39,8 +33,6 @@ public struct StickerPackCollectionInfoFlags: OptionSet {
public static let isMasks = StickerPackCollectionInfoFlags(rawValue: 1 << 0) public static let isMasks = StickerPackCollectionInfoFlags(rawValue: 1 << 0)
public static let isOfficial = StickerPackCollectionInfoFlags(rawValue: 1 << 1) 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 isEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 4)
public static let isAvailableAsChannelStatus = StickerPackCollectionInfoFlags(rawValue: 1 << 5) public static let isAvailableAsChannelStatus = StickerPackCollectionInfoFlags(rawValue: 1 << 5)
public static let isCustomTemplateEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 6) public static let isCustomTemplateEmoji = StickerPackCollectionInfoFlags(rawValue: 1 << 6)

View File

@ -360,20 +360,36 @@ public final class TelegramMediaImage: Media, Equatable, Codable {
} }
public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, CustomStringConvertible { public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, CustomStringConvertible {
public enum TypeHint: Int32 {
case generic
case animated
case video
}
public let dimensions: PixelDimensions public let dimensions: PixelDimensions
public let resource: TelegramMediaResource public let resource: TelegramMediaResource
public let progressiveSizes: [Int32] public let progressiveSizes: [Int32]
public let immediateThumbnailData: Data? public let immediateThumbnailData: Data?
public let hasVideo: Bool public let hasVideo: Bool
public let isPersonal: 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.dimensions = dimensions
self.resource = resource self.resource = resource
self.progressiveSizes = progressiveSizes self.progressiveSizes = progressiveSizes
self.immediateThumbnailData = immediateThumbnailData self.immediateThumbnailData = immediateThumbnailData
self.hasVideo = hasVideo self.hasVideo = hasVideo
self.isPersonal = isPersonal self.isPersonal = isPersonal
self.typeHint = typeHint
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -383,6 +399,7 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C
self.immediateThumbnailData = decoder.decodeDataForKey("th") self.immediateThumbnailData = decoder.decodeDataForKey("th")
self.hasVideo = decoder.decodeBoolForKey("hv", orElse: false) self.hasVideo = decoder.decodeBoolForKey("hv", orElse: false)
self.isPersonal = decoder.decodeBoolForKey("ip", orElse: false) self.isPersonal = decoder.decodeBoolForKey("ip", orElse: false)
self.typeHint = TypeHint(rawValue: decoder.decodeInt32ForKey("th", orElse: 0)) ?? .generic
} }
public func encode(_ encoder: PostboxEncoder) { 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.hasVideo, forKey: "hv")
encoder.encodeBool(self.isPersonal, forKey: "ip") encoder.encodeBool(self.isPersonal, forKey: "ip")
encoder.encodeInt32(self.typeHint.rawValue, forKey: "th")
} }
public var description: String { public var description: String {
@ -422,6 +440,9 @@ public final class TelegramMediaImageRepresentation: PostboxCoding, Equatable, C
if self.isPersonal != other.isPersonal { if self.isPersonal != other.isPersonal {
return false return false
} }
if self.typeHint != other.typeHint {
return false
}
return true return true
} }
} }

View File

@ -93,7 +93,7 @@ public final class CachedSecretChatData: CachedPeerData {
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { 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 { } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings {
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
} else { } else {

View File

@ -90,18 +90,14 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId:
var items: [RenderedChannelParticipant] = [] var items: [RenderedChannelParticipant] = []
switch result { switch result {
case let .channelParticipants(_, participants, chats, users): case let .channelParticipants(_, participants, chats, users):
postboxLog("channel users insertion started, count: \(participants.count)")
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
postboxLog("channel users parsed")
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
postboxLog("channel users postbox updated, started mapping ids")
var peers: [PeerId: Peer] = [:] var peers: [PeerId: Peer] = [:]
for id in parsedPeers.allIds { for id in parsedPeers.allIds {
if let peer = transaction.getPeer(id) { if let peer = transaction.getPeer(id) {
peers[peer.id] = peer peers[peer.id] = peer
} }
} }
postboxLog("channel users finish mapping, started updating participants")
for participant in CachedChannelParticipants(apiParticipants: participants).participants { for participant in CachedChannelParticipants(apiParticipants: participants).participants {
if let peer = parsedPeers.get(participant.peerId) { if let peer = parsedPeers.get(participant.peerId) {
@ -112,7 +108,6 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId:
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: renderedPresences)) items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: renderedPresences))
} }
} }
postboxLog("channel participants finish updating")
case .channelParticipantsNotModified: case .channelParticipantsNotModified:
return nil return nil
} }

View File

@ -214,7 +214,7 @@ func _internal_dismissPeerStatusOptions(account: Account, peerId: PeerId) -> Sig
if let current = current as? CachedUserData { if let current = current as? CachedUserData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings() var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = [] peerStatusSettings.flags = []
return current.withUpdatedPeerStatusSettings(peerStatusSettings) return current.withUpdatedPeerStatusSettings(PeerStatusSettings(flags: []))
} else if let current = current as? CachedGroupData { } else if let current = current as? CachedGroupData {
var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings() var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings()
peerStatusSettings.flags = [] peerStatusSettings.flags = []

View File

@ -56,12 +56,12 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, accountP
var peerStatusSettings: PeerStatusSettings var peerStatusSettings: PeerStatusSettings
if let peer = transaction.getPeer(peer.id), let associatedPeerId = peer.associatedPeerId, !transaction.isPeerContact(peerId: associatedPeerId) { 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 { if let peer = peer as? TelegramSecretChat, case .creator = peer.role {
peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil) peerStatusSettings = PeerStatusSettings(flags: [])
} else { } else {
peerStatusSettings = PeerStatusSettings(flags: [.canReport], managingBot: nil) peerStatusSettings = PeerStatusSettings(flags: [.canReport])
} }
} else { } else {
peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil) peerStatusSettings = PeerStatusSettings(flags: [])
} }
transaction.updatePeerCachedData(peerIds: [peer.id], update: { peerId, current in transaction.updatePeerCachedData(peerIds: [peer.id], update: { peerId, current in

View File

@ -194,7 +194,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
flags |= (1 << 1) 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? var thumbnailDocument: Api.InputDocument?
if thumbnail != nil, let resource = resources.last { if thumbnail != nil, let resource = resources.last {
@ -307,7 +307,7 @@ func _internal_addStickerToStickerSet(account: Account, packReference: StickerPa
if sticker.keywords.count > 0 { if sticker.keywords.count > 0 {
flags |= (1 << 1) 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)) return account.network.request(Api.functions.stickers.addStickerToSet(stickerset: packReference.apiInputStickerSet, sticker: inputSticker))
|> mapError { error -> AddStickerToSetError in |> mapError { error -> AddStickerToSetError in
@ -416,7 +416,7 @@ func _internal_replaceSticker(account: Account, previousSticker: FileMediaRefere
if sticker.keywords.count > 0 { if sticker.keywords.count > 0 {
flags |= (1 << 1) 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)) 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 |> mapError { error -> ReplaceStickerError in

View File

@ -5,19 +5,31 @@ import SwiftSignalKit
import MtProtoKit import MtProtoKit
func telegramStickerPackThumbnailRepresentationFromApiSizes(datacenterId: Int32, thumbVersion: Int32?, sizes: [Api.PhotoSize]) -> (immediateThumbnail: Data?, representations: [TelegramMediaImageRepresentation]) { 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 immediateThumbnailData: Data?
var representations: [TelegramMediaImageRepresentation] = [] var representations: [TelegramMediaImageRepresentation] = []
for size in sizes { for size in sizes {
switch size { switch size {
case let .photoCachedSize(_, w, h, _): case let .photoCachedSize(type, w, h, _):
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil) 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)) representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, typeHint: stickerTypeHint(for: type)))
case let .photoSize(_, w, h, _): case let .photoSize(type, w, h, _):
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil) 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)) representations.append(TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: w, height: h), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, typeHint: stickerTypeHint(for: type)))
case let .photoSizeProgressive(_, w, h, sizes): case let .photoSizeProgressive(type, w, h, sizes):
let resource = CloudStickerPackThumbnailMediaResource(datacenterId: datacenterId, thumbVersion: thumbVersion, volumeId: nil, localId: nil) 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): case let .photoPathSize(_, data):
immediateThumbnailData = data.makeData() immediateThumbnailData = data.makeData()
case .photoStrippedSize: case .photoStrippedSize:
@ -40,12 +52,6 @@ extension StickerPackCollectionInfo {
if (flags & (1 << 3)) != 0 { if (flags & (1 << 3)) != 0 {
setFlags.insert(.isMasks) setFlags.insert(.isMasks)
} }
if (flags & (1 << 5)) != 0 {
setFlags.insert(.isAnimated)
}
if (flags & (1 << 6)) != 0 {
setFlags.insert(.isVideo)
}
if (flags & (1 << 7)) != 0 { if (flags & (1 << 7)) != 0 {
setFlags.insert(.isEmoji) setFlags.insert(.isEmoji)
} }

View File

@ -620,10 +620,12 @@ public final class ChatInlineSearchResultsListComponent: Component {
}, },
openPremiumIntro: { openPremiumIntro: {
}, },
openPremiumGift: { openPremiumGift: { _ in
}, },
openActiveSessions: { openActiveSessions: {
}, },
openBirthdaySetup: {
},
performActiveSessionAction: { _, _ in performActiveSessionAction: { _, _ in
}, },
openChatFolderUpdates: { openChatFolderUpdates: {

View File

@ -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 { if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
authorTitle = nil authorTitle = nil
} }

View File

@ -184,7 +184,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings 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()) let reactions: Signal<[String], NoError> = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|> map { appConfiguration -> [String] in |> map { appConfiguration -> [String] in

View File

@ -7540,12 +7540,12 @@ private final class FadingMaskLayer: SimpleLayer {
} }
public struct StickerPickerInputData: StickerPickerInput, Equatable { public struct StickerPickerInputData: StickerPickerInput, Equatable {
public var emoji: EmojiPagerContentComponent public var emoji: EmojiPagerContentComponent?
public var stickers: EmojiPagerContentComponent? public var stickers: EmojiPagerContentComponent?
public var gifs: GifPagerContentComponent? public var gifs: GifPagerContentComponent?
public init( public init(
emoji: EmojiPagerContentComponent, emoji: EmojiPagerContentComponent?,
stickers: EmojiPagerContentComponent?, stickers: EmojiPagerContentComponent?,
gifs: GifPagerContentComponent? gifs: GifPagerContentComponent?
) { ) {

View File

@ -1162,11 +1162,6 @@ public extension EmojiPagerContentComponent {
} }
} else if case .stickerAlt = subject { } else if case .stickerAlt = subject {
for reactionItem in topReactionItems { for reactionItem in topReactionItems {
// if existingIds.contains(reactionItem.reaction) {
// continue
// }
// existingIds.insert(reactionItem.reaction)
let icon: EmojiPagerContentComponent.Item.Icon let icon: EmojiPagerContentComponent.Item.Icon
if case .reaction(onlyTop: true) = subject { if case .reaction(onlyTop: true) = subject {
icon = .none icon = .none
@ -1612,6 +1607,7 @@ public extension EmojiPagerContentComponent {
hasSearch: Bool, hasSearch: Bool,
hasTrending: Bool, hasTrending: Bool,
forceHasPremium: Bool, forceHasPremium: Bool,
hasEdit: Bool = false,
searchIsPlaceholderOnly: Bool = true, searchIsPlaceholderOnly: Bool = true,
isProfilePhotoEmojiSelection: Bool = false, isProfilePhotoEmojiSelection: Bool = false,
isGroupPhotoEmojiSelection: Bool = false, isGroupPhotoEmojiSelection: Bool = false,
@ -1944,11 +1940,11 @@ public extension EmojiPagerContentComponent {
var title = "" var title = ""
var headerItem: EntityKeyboardAnimationData? var headerItem: EntityKeyboardAnimationData?
var hasEdit = false var groupHasEdit = false
inner: for (id, info, _) in view.collectionInfos { inner: for (id, info, _) in view.collectionInfos {
if id == groupId, let info = info as? StickerPackCollectionInfo { if id == groupId, let info = info as? StickerPackCollectionInfo {
title = info.title title = info.title
hasEdit = info.flags.contains(.isCreator) groupHasEdit = info.flags.contains(.isCreator)
if let thumbnail = info.thumbnail { if let thumbnail = info.thumbnail {
let type: EntityKeyboardAnimationData.ItemType let type: EntityKeyboardAnimationData.ItemType
@ -1974,7 +1970,7 @@ public extension EmojiPagerContentComponent {
break inner 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]))
} }
} }

View File

@ -5677,10 +5677,6 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate) 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 entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView) let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities) 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) contextItems.append(.custom(StickerPackListContextItem(context: self.context, packs: self.myStickerPacks, packSelected: { [weak self] pack in
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(.action(ContextMenuActionItem(text: pack.title, icon: { _ in return nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { [weak self] _, f in
guard let self else { guard let self else {
return 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( let items = ContextController.Items(
id: 1, id: 1,
@ -5877,7 +5849,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme)
var dismissImpl: (() -> Void)? 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 { guard let self else {
return return
} }
@ -5972,7 +5944,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case let .createStickerPack(title): case let .createStickerPack(title):
let sticker = ImportSticker( let sticker = ImportSticker(
resource: resource, resource: resource,
emojis: ["😀"], emojis: ["😀😂"],
dimensions: dimensions, dimensions: dimensions,
mimeType: "image/webp", mimeType: "image/webp",
keywords: "" keywords: ""
@ -5991,7 +5963,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
case let .addToStickerPack(pack, _): case let .addToStickerPack(pack, _):
let sticker = ImportSticker( let sticker = ImportSticker(
resource: resource, resource: resource,
emojis: ["😀"], emojis: ["😀😂"],
dimensions: dimensions, dimensions: dimensions,
mimeType: "image/webp", mimeType: "image/webp",
keywords: "" keywords: ""

View File

@ -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)
}
}
}

View File

@ -0,0 +1,8 @@
//
// PeerInfoScreenBirthdatePickerItem.swift
// MediaEditorScreen
//
// Created by Ilya Laktyushin on 15.03.2024.
//
import Foundation

View File

@ -38,9 +38,10 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
let text: String let text: String
let icon: UIImage? let icon: UIImage?
let iconSignal: Signal<UIImage?, NoError>? let iconSignal: Signal<UIImage?, NoError>?
let hasArrow: Bool
let action: (() -> Void)? 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.id = id
self.label = label self.label = label
self.additionalBadgeLabel = additionalBadgeLabel self.additionalBadgeLabel = additionalBadgeLabel
@ -48,6 +49,7 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
self.text = text self.text = text
self.icon = icon self.icon = icon
self.iconSignal = iconSignal self.iconSignal = iconSignal
self.hasArrow = hasArrow
self.action = action self.action = action
} }
@ -139,7 +141,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
let sideInset: CGFloat = 16.0 + safeInsets.left let sideInset: CGFloat = 16.0 + safeInsets.left
let leftInset = (item.icon == nil && item.iconSignal == nil ? sideInset : sideInset + 29.0 + 16.0) 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 separatorInset = item.icon == nil && item.iconSignal == nil ? sideInset : leftInset - 1.0
let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize) let titleFont = Font.regular(presentationData.listsFontSize.itemListBaseFontSize)
@ -206,7 +208,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
self.iconNode.removeFromSupernode() self.iconNode.removeFromSupernode()
} }
if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) { if item.hasArrow, let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) {
self.arrowNode.image = arrowImage 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) 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) transition.updateFrame(node: self.arrowNode, frame: arrowFrame)

View File

@ -1354,7 +1354,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.controller?.containerLayoutUpdated(layout, transition: .immediate) self.controller?.containerLayoutUpdated(layout, transition: .immediate)
} }
} else { } 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 self.contactListNode = contactListNode
contactListNode.enableUpdates = true contactListNode.enableUpdates = true
contactListNode.selectionStateUpdated = { [weak self] selectionState in contactListNode.selectionStateUpdated = { [weak self] selectionState in

View File

@ -186,10 +186,12 @@ final class GreetingMessageListItemComponent: Component {
}, },
openPremiumIntro: { openPremiumIntro: {
}, },
openPremiumGift: { openPremiumGift: { _ in
}, },
openActiveSessions: { openActiveSessions: {
}, },
openBirthdaySetup: {
},
performActiveSessionAction: { _, _ in performActiveSessionAction: { _, _ in
}, },
openChatFolderUpdates: { openChatFolderUpdates: {

View File

@ -201,10 +201,12 @@ final class QuickReplySetupScreenComponent: Component {
}, },
openPremiumIntro: { openPremiumIntro: {
}, },
openPremiumGift: { openPremiumGift: { _ in
}, },
openActiveSessions: { openActiveSessions: {
}, },
openBirthdaySetup: {
},
performActiveSessionAction: { _, _ in performActiveSessionAction: { _, _ in
}, },
openChatFolderUpdates: { openChatFolderUpdates: {

View File

@ -865,7 +865,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}, activateChatPreview: { _, _, _, gesture, _ in }, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel() gesture?.cancel()
}, present: { _ in }, present: { _ in
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: { }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: { _ in }, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in }, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openChatFolderUpdates: {}, hideChatFolderUpdates: {
}, openStories: { _, _ in }, openStories: { _, _ in

View File

@ -30,6 +30,7 @@ private final class StickerSelectionComponent: Component {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings let strings: PresentationStrings
let deviceMetrics: DeviceMetrics let deviceMetrics: DeviceMetrics
let topInset: CGFloat
let bottomInset: CGFloat let bottomInset: CGFloat
let content: StickerPickerInputData let content: StickerPickerInputData
let backgroundColor: UIColor let backgroundColor: UIColor
@ -41,6 +42,7 @@ private final class StickerSelectionComponent: Component {
theme: PresentationTheme, theme: PresentationTheme,
strings: PresentationStrings, strings: PresentationStrings,
deviceMetrics: DeviceMetrics, deviceMetrics: DeviceMetrics,
topInset: CGFloat,
bottomInset: CGFloat, bottomInset: CGFloat,
content: StickerPickerInputData, content: StickerPickerInputData,
backgroundColor: UIColor, backgroundColor: UIColor,
@ -51,6 +53,7 @@ private final class StickerSelectionComponent: Component {
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.deviceMetrics = deviceMetrics self.deviceMetrics = deviceMetrics
self.topInset = topInset
self.bottomInset = bottomInset self.bottomInset = bottomInset
self.content = content self.content = content
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
@ -68,6 +71,9 @@ private final class StickerSelectionComponent: Component {
if lhs.deviceMetrics != rhs.deviceMetrics { if lhs.deviceMetrics != rhs.deviceMetrics {
return false return false
} }
if lhs.topInset != rhs.topInset {
return false
}
if lhs.bottomInset != rhs.bottomInset { if lhs.bottomInset != rhs.bottomInset {
return false return false
} }
@ -235,12 +241,13 @@ private final class StickerSelectionComponent: Component {
self.backgroundColor = component.backgroundColor self.backgroundColor = component.backgroundColor
let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85) let panelBackgroundColor = component.backgroundColor.withMultipliedAlpha(0.85)
self.panelBackgroundView.updateColor(color: panelBackgroundColor, transition: .immediate) 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.component = component
self.state = state self.state = state
let topPanelHeight: CGFloat = 42.0 let topPanelHeight: CGFloat = 42.0
let topInset = component.topInset
let controller = component.getController() let controller = component.getController()
let defaultToEmoji = controller?.defaultToEmoji ?? false let defaultToEmoji = controller?.defaultToEmoji ?? false
@ -248,7 +255,7 @@ private final class StickerSelectionComponent: Component {
let context = component.context let context = component.context
let stickerPeekBehavior = EmojiContentPeekBehaviorImpl( let stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
context: context, context: context,
forceTheme: defaultDarkColorPresentationTheme, forceTheme: controller?.forceDark == true ? defaultDarkColorPresentationTheme : nil,
interaction: nil, interaction: nil,
chatPeerId: nil, chatPeerId: nil,
present: { c, a in present: { c, a in
@ -258,19 +265,20 @@ private final class StickerSelectionComponent: Component {
} }
) )
let isFullscreen = controller?.isFullscreen == true
let keyboardSize = self.keyboardView.update( let keyboardSize = self.keyboardView.update(
transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)), transition: transition.withUserData(EmojiPagerContentComponent.SynchronousLoadBehavior(isDisabled: true)),
component: AnyComponent(EntityKeyboardComponent( component: AnyComponent(EntityKeyboardComponent(
theme: component.theme, theme: component.theme,
strings: component.strings, strings: component.strings,
isContentInFocus: true, 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), topPanelInsets: UIEdgeInsets(top: 0.0, left: 4.0, bottom: 0.0, right: 4.0),
emojiContent: component.content.emoji, emojiContent: component.content.emoji,
stickerContent: component.content.stickers, stickerContent: component.content.stickers,
maskContent: nil, maskContent: nil,
gifContent: component.content.gifs, gifContent: component.content.gifs,
hasRecentGifs: true, hasRecentGifs: !isFullscreen,
availableGifSearchEmojies: [], availableGifSearchEmojies: [],
defaultToEmojiTab: defaultToEmoji, defaultToEmojiTab: defaultToEmoji,
externalTopPanelContainer: self.panelHostView, externalTopPanelContainer: self.panelHostView,
@ -313,7 +321,10 @@ private final class StickerSelectionComponent: Component {
mappedMode = .gif 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( let searchContainerNode = PaneSearchContainerNode(
context: context, context: context,
theme: presentationData.theme, theme: presentationData.theme,
@ -344,7 +355,7 @@ private final class StickerSelectionComponent: Component {
deviceMetrics: component.deviceMetrics, deviceMetrics: component.deviceMetrics,
hiddenInputHeight: 0.0, hiddenInputHeight: 0.0,
inputHeight: 0.0, inputHeight: 0.0,
displayBottomPanel: controller?.isFullscreen == false, displayBottomPanel: !isFullscreen,
isExpanded: true, isExpanded: true,
clipContentToTopPanel: false, clipContentToTopPanel: false,
useExternalSearchContainer: false useExternalSearchContainer: false
@ -365,18 +376,26 @@ private final class StickerSelectionComponent: Component {
self.keyboardClippingView.clipsToBounds = false 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))) 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, left: 0.0, bottom: 0.0, right: 0.0) 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: 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 - 34.0), size: CGSize(width: keyboardSize.width, height: 0.0))) 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) self.panelBackgroundView.update(size: self.panelBackgroundView.bounds.size, transition: transition.containedViewLayoutTransition)
let topPanelAlpha: CGFloat let topPanelAlpha: CGFloat
if self.searchVisible || self.keyboardContentId == AnyHashable("gifs") { if self.searchVisible || self.keyboardContentId == AnyHashable("gifs") {
topPanelAlpha = 0.0 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 { } else {
topPanelAlpha = max(0.0, min(1.0, (self.topPanelScrollingOffset / 20.0))) topPanelAlpha = max(0.0, min(1.0, (self.topPanelScrollingOffset / 20.0)))
} }
@ -384,7 +403,7 @@ private final class StickerSelectionComponent: Component {
transition.setAlpha(view: self.panelBackgroundView, alpha: topPanelAlpha) transition.setAlpha(view: self.panelBackgroundView, alpha: topPanelAlpha)
transition.setAlpha(view: self.panelSeparatorView, 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 return availableSize
@ -642,7 +661,7 @@ public class StickerPickerScreen: ViewController {
inputData.gifs = gifData?.component inputData.gifs = gifData?.component
let emoji = inputData.emoji if let emoji = inputData.emoji {
if let emojiSearchResult = emojiSearchState.result { if let emojiSearchResult = emojiSearchState.result {
var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults? var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults?
if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
@ -651,12 +670,15 @@ public class StickerPickerScreen: ViewController {
iconFile: nil iconFile: nil
) )
} }
let defaultSearchState: EmojiPagerContentComponent.SearchState = emojiSearchResult.isPreset ? .active : .empty(hasResults: true) 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) 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 { } else if emojiSearchState.isSearching {
inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emoji.contentItemGroups, itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: .searching) inputData.emoji = emoji.withUpdatedItemGroups(panelItemGroups: emoji.panelItemGroups, contentItemGroups: emoji.contentItemGroups, itemContentUniqueId: emoji.itemContentUniqueId, emptySearchResults: emoji.emptySearchResults, searchState: .searching)
} }
}
if let stickers = inputData.stickers {
if let stickerSearchResult = stickerSearchState.result { if let stickerSearchResult = stickerSearchState.result {
var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults? var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults?
if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) {
@ -665,12 +687,10 @@ public class StickerPickerScreen: ViewController {
iconFile: nil iconFile: nil
) )
} }
if let stickers = inputData.stickers {
let defaultSearchState: EmojiPagerContentComponent.SearchState = stickerSearchResult.isPreset ? .active : .empty(hasResults: true) 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) 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 { } else if stickerSearchState.isSearching {
if let stickers = inputData.stickers {
inputData.stickers = stickers.withUpdatedItemGroups(panelItemGroups: stickers.panelItemGroups, contentItemGroups: stickers.contentItemGroups, itemContentUniqueId: stickers.itemContentUniqueId, emptySearchResults: stickers.emptySearchResults, searchState: .searching) 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 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 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 { if case .info = action, let controller {
let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs, forceDark: true, dismissed: nil) let premiumController = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs, forceDark: controller.forceDark, dismissed: nil)
controller?.push(premiumController) controller.push(premiumController)
return true return true
} }
return false return false
@ -802,7 +822,7 @@ public class StickerPickerScreen: ViewController {
return return
} }
content.emoji.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( content.emoji?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction(
performItemAction: { [weak self] groupId, item, _, _, _, _ in performItemAction: { [weak self] groupId, item, _, _, _, _ in
guard let strongSelf = self, let controller = strongSelf.controller else { guard let strongSelf = self, let controller = strongSelf.controller else {
return return
@ -846,10 +866,10 @@ public class StickerPickerScreen: ViewController {
for featuredStickerPack in stickerPacks { for featuredStickerPack in stickerPacks {
if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) { if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) {
if let componentView = self.hostView.componentView as? StickerSelectionComponent.View { 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( pagerView.openCustomSearch(content: EmojiSearchContent(
context: context, context: context,
forceTheme: defaultDarkPresentationTheme, forceTheme: controller.forceDark ? defaultDarkPresentationTheme : nil,
items: stickerPacks, items: stickerPacks,
initialFocusId: featuredStickerPack.info.id, initialFocusId: featuredStickerPack.info.id,
hasPremiumForUse: hasPremium, hasPremiumForUse: hasPremium,
@ -858,7 +878,6 @@ public class StickerPickerScreen: ViewController {
)) ))
} }
} }
break break
} }
} }
@ -943,7 +962,10 @@ public class StickerPickerScreen: ViewController {
guard let strongSelf = self, let controller = strongSelf.controller else { guard let strongSelf = self, let controller = strongSelf.controller else {
return 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 let context = controller.context
if groupId == AnyHashable("recent") { if groupId == AnyHashable("recent") {
let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize)) let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
@ -1243,7 +1265,7 @@ public class StickerPickerScreen: ViewController {
if let controller = self.controller { if let controller = self.controller {
stickerPeekBehavior = EmojiContentPeekBehaviorImpl( stickerPeekBehavior = EmojiContentPeekBehaviorImpl(
context: controller.context, context: controller.context,
forceTheme: defaultDarkColorPresentationTheme, forceTheme: controller.forceDark ? defaultDarkColorPresentationTheme : nil,
interaction: nil, interaction: nil,
chatPeerId: nil, chatPeerId: nil,
present: { [weak controller] c, a in present: { [weak controller] c, a in
@ -1348,7 +1370,10 @@ public class StickerPickerScreen: ViewController {
} }
let context = controller.context let context = controller.context
if groupId == AnyHashable("recent") { 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)) let actionSheet = ActionSheetController(theme: ActionSheetControllerTheme(presentationTheme: presentationData.theme, fontSize: presentationData.listsFontSize))
var items: [ActionSheetItem] = [] var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: presentationData.strings.Stickers_ClearRecent, color: .destructive, action: { [weak actionSheet] in 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(_:)))) 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) controller.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
} }
} }
@ -1711,10 +1736,11 @@ public class StickerPickerScreen: ViewController {
theme: self.theme, theme: self.theme,
strings: self.presentationData.strings, strings: self.presentationData.strings,
deviceMetrics: layout.deviceMetrics, deviceMetrics: layout.deviceMetrics,
topInset: controller.isFullscreen ? navigationHeight : 0.0,
bottomInset: bottomInset, bottomInset: bottomInset,
content: content, content: content,
backgroundColor: self.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.85), backgroundColor: self.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.85),
separatorColor: self.theme.list.blocksBackgroundColor, separatorColor: self.theme.rootController.navigationBar.separatorColor,
getController: { [weak self] in getController: { [weak self] in
if let self { if let self {
return self.controller return self.controller
@ -1752,12 +1778,12 @@ public class StickerPickerScreen: ViewController {
} }
private var defaultTopInset: CGFloat { private var defaultTopInset: CGFloat {
guard let (layout, navigationBarHeight) = self.currentLayout else { guard let (layout, _) = self.currentLayout else {
return 210.0 return 210.0
} }
if let controller = self.controller, controller.isFullscreen { if let controller = self.controller, controller.isFullscreen {
return navigationBarHeight return 0.0
} }
if case .compact = layout.metrics.widthClass { if case .compact = layout.metrics.widthClass {
@ -1978,6 +2004,7 @@ public class StickerPickerScreen: ViewController {
private let context: AccountContext private let context: AccountContext
private let theme: PresentationTheme private let theme: PresentationTheme
fileprivate let forceDark: Bool
private let inputData: Signal<StickerPickerInput, NoError> private let inputData: Signal<StickerPickerInput, NoError>
fileprivate let defaultToEmoji: Bool fileprivate let defaultToEmoji: Bool
let isFullscreen: Bool let isFullscreen: Bool
@ -2002,6 +2029,7 @@ public class StickerPickerScreen: ViewController {
self.context = context self.context = context
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.theme = forceDark ? defaultDarkColorPresentationTheme : presentationData.theme self.theme = forceDark ? defaultDarkColorPresentationTheme : presentationData.theme
self.forceDark = forceDark
self.inputData = inputData self.inputData = inputData
self.isFullscreen = expanded self.isFullscreen = expanded
self.defaultToEmoji = defaultToEmoji self.defaultToEmoji = defaultToEmoji

View File

@ -255,7 +255,7 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF
private let maxLength: Int 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.theme = theme
self.maxLength = maxLength self.maxLength = maxLength
@ -370,6 +370,10 @@ private final class ImportStickerPackTitleInputFieldNode: ASDisplayNode, UITextF
return false return false
} }
if string == " " && updatedText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return false
}
if self.textInputNode.keyboardType == .asciiCapable { if self.textInputNode.keyboardType == .asciiCapable {
var cleanString = string.folding(options: .diacriticInsensitive, locale: .current).replacingOccurrences(of: " ", with: "_") var cleanString = string.folding(options: .diacriticInsensitive, locale: .current).replacingOccurrences(of: " ", with: "_")
@ -506,7 +510,7 @@ private final class ImportStickerPackTitleAlertContentNode: AlertContentNode {
return self.isUserInteractionEnabled 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.strings = strings
self.alertTheme = theme self.alertTheme = theme
self.theme = ptheme 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 = ActivityIndicator(type: .custom(ptheme.rootController.navigationBar.secondaryTextColor, 20.0, 1.5, false), speed: .slow)
self.activityIndicator.isHidden = true 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 { if asciiOnly {
self.inputFieldNode.prefix = "t.me/addstickers/" self.inputFieldNode.prefix = "t.me/addstickers/"
} }
@ -743,7 +747,7 @@ public func stickerPackEditTitleController(context: AccountContext, forceDark: B
applyImpl?() 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 = { contentNode.complete = {
applyImpl?() applyImpl?()
} }
@ -805,7 +809,7 @@ public func importStickerPackShortNameController(context: AccountContext, title:
applyImpl?() 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 = { contentNode.complete = {
applyImpl?() applyImpl?()
} }

View File

@ -209,31 +209,8 @@ final class StoryContentCaptionComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
self.shadowGradientView = UIImageView() self.shadowGradientView = UIImageView()
if let _ = StoryContentCaptionComponent.View.shadowImage { if let image = StoryContentCaptionComponent.View.shadowImage {
let height: CGFloat = 128.0 self.shadowGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.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))
} }
self.scrollViewContainer = UIView() 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())) 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 = 58.0
let shadowOverflow: CGFloat = floor(shadowHeight * 0.6)
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 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)) let shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0))

View File

@ -2690,7 +2690,7 @@ public final class StoryItemSetContainerComponent: Component {
self.bottomContentGradientLayer.colors = colors self.bottomContentGradientLayer.colors = colors
self.bottomContentGradientLayer.type = .axial 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 let wasPanning = self.component?.isPanning ?? false

View File

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "more.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -453,6 +453,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
weak var checksTooltipController: TooltipController? weak var checksTooltipController: TooltipController?
weak var copyProtectionTooltipController: TooltipController? weak var copyProtectionTooltipController: TooltipController?
weak var emojiPackTooltipController: TooltipScreen? weak var emojiPackTooltipController: TooltipScreen?
weak var birthdayTooltipController: TooltipScreen?
var currentMessageTooltipScreens: [(TooltipScreen, ListViewItemNode)] = [] var currentMessageTooltipScreens: [(TooltipScreen, ListViewItemNode)] = []
@ -587,8 +588,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)? var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)? var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
var networkSpeedEventsDisposable: Disposable?
public var alwaysShowSearchResultsAsList: Bool = false { public var alwaysShowSearchResultsAsList: Bool = false {
didSet { didSet {
self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) 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 { do {
let peerId = chatLocationPeerId let peerId = chatLocationPeerId
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId { if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
@ -5335,11 +5297,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
threadInfo, threadInfo,
hasSearchTags, hasSearchTags,
hasSavedChats, hasSavedChats,
isPremiumRequiredForMessaging, isPremiumRequiredForMessaging
managingBot ).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
if let strongSelf = self { 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 return
} }
@ -5434,7 +5395,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var contactStatus: ChatContactStatus? var contactStatus: ChatContactStatus?
if let peer = peerView.peers[peerView.peerId] { if let peer = peerView.peers[peerView.peerId] {
if let cachedData = peerView.cachedData as? CachedUserData { 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 { } else if let cachedData = peerView.cachedData as? CachedGroupData {
var invitedBy: Peer? var invitedBy: Peer?
if let invitedByPeerId = cachedData.invitedBy { if let invitedByPeerId = cachedData.invitedBy {
@ -5442,7 +5403,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
invitedBy = peer 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 { } else if let cachedData = peerView.cachedData as? CachedChannelData {
var canReportIrrelevantLocation = true var canReportIrrelevantLocation = true
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { 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 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>() 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 { if strongSelf.presentationInterfaceState.search != nil && strongSelf.presentationInterfaceState.hasSearchTags {
didDisplayActionsPanel = true 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 { if strongSelf.presentationInterfaceState.search != nil && hasSearchTags {
displayActionsPanel = true displayActionsPanel = true
} }
@ -5919,10 +5874,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
hasScheduledMessages, hasScheduledMessages,
hasSearchTags, hasSearchTags,
hasSavedChats, hasSavedChats,
isPremiumRequiredForMessaging, isPremiumRequiredForMessaging
managingBot
) )
|> 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 { if let strongSelf = self {
strongSelf.hasScheduledMessages = hasScheduledMessages strongSelf.hasScheduledMessages = hasScheduledMessages
@ -5932,7 +5886,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let peer = peerView.peers[peerView.peerId] { if let peer = peerView.peers[peerView.peerId] {
copyProtectionEnabled = peer.isCopyProtectionEnabled copyProtectionEnabled = peer.isCopyProtectionEnabled
if let cachedData = peerView.cachedData as? CachedUserData { 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 { } else if let cachedData = peerView.cachedData as? CachedGroupData {
var invitedBy: Peer? var invitedBy: Peer?
if let invitedByPeerId = cachedData.invitedBy { if let invitedByPeerId = cachedData.invitedBy {
@ -5940,7 +5894,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
invitedBy = peer 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 { } else if let cachedData = peerView.cachedData as? CachedChannelData {
var canReportIrrelevantLocation = true var canReportIrrelevantLocation = true
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { 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 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>() 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 var displayActionsPanel = false
if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { 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 { if displayActionsPanel != didDisplayActionsPanel {
animated = true animated = true
@ -6851,7 +6799,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.preloadSavedMessagesChatsDisposable?.dispose() self.preloadSavedMessagesChatsDisposable?.dispose()
self.recorderDataDisposable.dispose() self.recorderDataDisposable.dispose()
self.displaySendWhenOnlineTipDisposable.dispose() self.displaySendWhenOnlineTipDisposable.dispose()
self.networkSpeedEventsDisposable?.dispose()
} }
deallocate() 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() 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) { 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() { func displayChecksTooltip() {
self.checksTooltipController?.dismiss() self.checksTooltipController?.dismiss()

View File

@ -3412,6 +3412,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
return nil 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 { var isTextInputPanelActive: Bool {
return self.inputPanelNode is ChatTextInputPanelNode return self.inputPanelNode is ChatTextInputPanelNode
} }

View File

@ -167,7 +167,11 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
if case .scheduledMessages = chatPresentationInterfaceState.subject { if case .scheduledMessages = chatPresentationInterfaceState.subject {
} else { } else {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) 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 { 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) accessoryItems.append(.gift)
} }

View File

@ -117,8 +117,7 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
} }
var displayActionsPanel = false var displayActionsPanel = false
if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus { if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings {
if let peerStatusSettings = contactStatus.peerStatusSettings {
if !peerStatusSettings.flags.isEmpty { if !peerStatusSettings.flags.isEmpty {
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
displayActionsPanel = true displayActionsPanel = true
@ -136,10 +135,8 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
displayActionsPanel = true displayActionsPanel = true
} }
} }
}
if (selectedContext == nil || selectedContext! <= .pinnedMessage) { if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) {
if displayActionsPanel {
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode { if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
return currentPanel return currentPanel
} else if let controllerInteraction = controllerInteraction { } else if let controllerInteraction = controllerInteraction {
@ -147,15 +144,6 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
panel.interfaceInteraction = interfaceInteraction panel.interfaceInteraction = interfaceInteraction
return panel 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 let selectedContext = selectedContext { if let selectedContext = selectedContext {

View File

@ -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)
}
}

View File

@ -282,8 +282,9 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
}, openStorageManagement: { }, openStorageManagement: {
}, openPasswordSetup: { }, openPasswordSetup: {
}, openPremiumIntro: { }, openPremiumIntro: {
}, openPremiumGift: { }, openPremiumGift: { _ in
}, openActiveSessions: { }, openActiveSessions: {
}, openBirthdaySetup: {
}, performActiveSessionAction: { _, _ in }, performActiveSessionAction: { _, _ in
}, openChatFolderUpdates: { }, openChatFolderUpdates: {
}, hideChatFolderUpdates: { }, hideChatFolderUpdates: {

View File

@ -4595,6 +4595,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
return nil 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? { func makeSnapshotForTransition() -> ChatMessageTransitionNodeImpl.Source.TextInput? {
guard let backgroundImage = self.transparentTextInputBackgroundImage else { guard let backgroundImage = self.transparentTextInputBackgroundImage else {
return nil return nil

View File

@ -156,10 +156,12 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
}, },
openPremiumIntro: { openPremiumIntro: {
}, },
openPremiumGift: { openPremiumGift: { _ in
}, },
openActiveSessions: { openActiveSessions: {
}, },
openBirthdaySetup: {
},
performActiveSessionAction: { _, _ in performActiveSessionAction: { _, _ in
}, },
openChatFolderUpdates: { openChatFolderUpdates: {

View File

@ -52,7 +52,7 @@ final class ComposeControllerNode: ASDisplayNode {
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: { ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: .generic(UIImage(bundleImageName: "Contact List/CreateChannelActionIcon")!), action: {
openCreateNewChannelImpl?() openCreateNewChannelImpl?()
}) })
], includeChatList: false, topPeers: false)), onlyWriteable: false, displayPermissionPlaceholder: false) ], includeChatList: false, topPeers: .none)), onlyWriteable: false, displayPermissionPlaceholder: false)
super.init() super.init()

View File

@ -169,11 +169,17 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
} }
self.contentNode = .chats(chatListNode) self.contentNode = .chats(chatListNode)
} else { } else {
var displayTopPeers = false let displayTopPeers: ContactListPresentation.TopPeers
if case .premiumGifting = mode { if case let .premiumGifting(topSectionTitle, topSectionPeers) = mode {
displayTopPeers = true if let topSectionTitle {
displayTopPeers = .custom(title: topSectionTitle, peerIds: topSectionPeers)
} else {
displayTopPeers = .recent
}
} else if case .requestedUsersSelection = mode { } 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()) let contactListNode = ContactListNode(context: context, presentation: .single(.natural(options: options, includeChatList: includeChatList, topPeers: displayTopPeers)), filters: filters, onlyWriteable: onlyWriteable, selectionState: ContactListNodeGroupSelectionState())
self.contentNode = .contacts(contactListNode) self.contentNode = .contacts(contactListNode)

View File

@ -68,7 +68,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
self.filters = filters self.filters = filters
var contextActionImpl: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? 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) contextActionImpl?(peer, node, gesture, nil)
} : nil, multipleSelection: multipleSelection) } : nil, multipleSelection: multipleSelection)

View File

@ -2115,7 +2115,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
let limit: Int32 = 10 let limit: Int32 = 10
var reachedLimitImpl: ((Int32) -> Void)? 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) { if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) {
return true return true
} else { } 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) 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 subject: MediaEditorScreen.Subject
let mode: MediaEditorScreen.Mode.StickerEditorMode let mode: MediaEditorScreen.Mode.StickerEditorMode
if let file = source as? TelegramMediaFile { if let file = source as? TelegramMediaFile {
@ -2347,9 +2355,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
} }
return nil return nil
}, completion: { result, commit in }, completion: { result, commit in
commit({})
if case let .sticker(file) = result.media { if case let .sticker(file) = result.media {
completion(file) completion(file, {
commit({})
})
} }
} as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void } as (MediaEditorScreen.Result, @escaping (@escaping () -> Void) -> Void) -> Void
) )

View File

@ -441,8 +441,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
var resourceReference: MediaResourceReference? var resourceReference: MediaResourceReference?
if let thumbnail = info.thumbnail { if let thumbnail = info.thumbnail {
if info.flags.contains(.isAnimated) || info.flags.contains(.isVideo) { if thumbnail.typeHint != .generic {
thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, info.flags.contains(.isVideo)) thumbnailItem = .animated(EngineMediaResource(thumbnail.resource), thumbnail.dimensions, thumbnail.typeHint == .video)
} else { } else {
thumbnailItem = .still(thumbnail) thumbnailItem = .still(thumbnail)
} }

View File

@ -34,6 +34,7 @@ swift_library(
"//submodules/Markdown:Markdown", "//submodules/Markdown:Markdown",
"//submodules/TextFormat:TextFormat", "//submodules/TextFormat:TextFormat",
"//submodules/LocalAuth", "//submodules/LocalAuth",
"//submodules/InstantPageCache"
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -23,6 +23,7 @@ import PromptUI
import PhoneNumberFormat import PhoneNumberFormat
import QrCodeUI import QrCodeUI
import InstantPageUI import InstantPageUI
import InstantPageCache
import LocalAuth import LocalAuth
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
@ -1073,8 +1074,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.requestBiometryAuth() self.requestBiometryAuth()
case "web_app_biometry_update_token": case "web_app_biometry_update_token":
var tokenData: Data? var tokenData: Data?
if let json, let tokenDataValue = json["token"] as? String, !tokenDataValue.isEmpty { if let json, let tokenDataValue = json["token"] as? Data {
tokenData = tokenDataValue.data(using: .utf8) tokenData = tokenDataValue
} }
self.requestBiometryUpdateToken(tokenData: tokenData) self.requestBiometryUpdateToken(tokenData: tokenData)
default: default:
@ -1513,7 +1514,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
Thread { [weak self] in 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 let decryptedData: LocalAuth.DecryptionResult
if let key { if let key {
@ -1563,9 +1567,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
data["status"] = isAuthorized ? "authorized" : "failed" data["status"] = isAuthorized ? "authorized" : "failed"
if isAuthorized { if isAuthorized {
if let tokenData { if let tokenData {
data["token"] = String(data: tokenData, encoding: .utf8) ?? "" data["token"] = tokenData
} else { } else {
data["token"] = "" data["token"] = Data()
} }
} }
@ -1589,7 +1593,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let tokenData { if let tokenData {
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
Thread { [weak self] in 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? var encryptedData: TelegramBotBiometricsState.OpaqueToken?
if let key { if let key {
@ -1612,28 +1619,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
state.opaqueToken = encryptedData state.opaqueToken = encryptedData
return state 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() }.start()
@ -1643,17 +1628,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
state.opaqueToken = nil state.opaqueToken = nil
return state 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() 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) { if let _ = attachMenuBot, [.attachMenu, .settings, .generic].contains(source) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in 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) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)

View File

@ -159,7 +159,7 @@ final class WebAppWebView: WKWebView {
self.interactiveTransitionGestureRecognizerTest = { point -> Bool in self.interactiveTransitionGestureRecognizerTest = { point -> Bool in
return point.x > 30.0 return point.x > 30.0
} }
self.allowsBackForwardNavigationGestures = true self.allowsBackForwardNavigationGestures = false
if #available(iOS 16.4, *) { if #available(iOS 16.4, *) {
self.isInspectable = true self.isInspectable = true
} }