mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'db1d4422cb97018bbc7d0d554a63b70361471d4f'
This commit is contained in:
commit
c05c6b8e23
@ -11616,3 +11616,6 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"ChannelBoost.NoAds" = "Switch Off Ads";
|
"ChannelBoost.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";
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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?)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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: {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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) }
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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()
|
||||||
|
@ -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> {
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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>]>([:])
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 = []
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -620,10 +620,12 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
},
|
},
|
||||||
openPremiumIntro: {
|
openPremiumIntro: {
|
||||||
},
|
},
|
||||||
openPremiumGift: {
|
openPremiumGift: { _ in
|
||||||
},
|
},
|
||||||
openActiveSessions: {
|
openActiveSessions: {
|
||||||
},
|
},
|
||||||
|
openBirthdaySetup: {
|
||||||
|
},
|
||||||
performActiveSessionAction: { _, _ in
|
performActiveSessionAction: { _, _ in
|
||||||
},
|
},
|
||||||
openChatFolderUpdates: {
|
openChatFolderUpdates: {
|
||||||
|
@ -156,18 +156,6 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if authorTitle == nil {
|
|
||||||
for attribute in message.attributes {
|
|
||||||
if let attribute = attribute as? InlineBusinessBotMessageAttribute {
|
|
||||||
if let title = attribute.title {
|
|
||||||
authorTitle = title
|
|
||||||
} else if let peerId = attribute.peerId, let peer = message.peers[peerId] {
|
|
||||||
authorTitle = peer.debugDisplayTitle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
|
if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
|
||||||
authorTitle = nil
|
authorTitle = nil
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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?
|
||||||
) {
|
) {
|
||||||
|
@ -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]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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: ""
|
||||||
|
@ -0,0 +1,191 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import AccountContext
|
||||||
|
import TelegramPresentationData
|
||||||
|
import StickerResources
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
|
final class StickerPackListContextItem: ContextMenuCustomItem {
|
||||||
|
let context: AccountContext
|
||||||
|
let packs: [(StickerPackCollectionInfo, StickerPackItem?)]
|
||||||
|
let packSelected: (StickerPackCollectionInfo) -> Void
|
||||||
|
|
||||||
|
init(context: AccountContext, packs: [(StickerPackCollectionInfo, StickerPackItem?)], packSelected: @escaping (StickerPackCollectionInfo) -> Void) {
|
||||||
|
self.context = context
|
||||||
|
self.packs = packs
|
||||||
|
self.packSelected = packSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||||
|
return StickerPackListContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class StickerPackListContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol, UIScrollViewDelegate {
|
||||||
|
private let item: StickerPackListContextItem
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
private let getController: () -> ContextControllerProtocol?
|
||||||
|
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||||
|
|
||||||
|
private let scrollNode: ASScrollNode
|
||||||
|
private let actionNodes: [ContextControllerActionsListActionItemNode]
|
||||||
|
private let separatorNodes: [ASDisplayNode]
|
||||||
|
|
||||||
|
init(presentationData: PresentationData, item: StickerPackListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||||
|
self.item = item
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.getController = getController
|
||||||
|
self.actionSelected = actionSelected
|
||||||
|
|
||||||
|
self.scrollNode = ASScrollNode()
|
||||||
|
|
||||||
|
var actionNodes: [ContextControllerActionsListActionItemNode] = []
|
||||||
|
var separatorNodes: [ASDisplayNode] = []
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
for (pack, topItem) in item.packs {
|
||||||
|
let thumbSize = CGSize(width: 24.0, height: 24.0)
|
||||||
|
let thumbnailResource = pack.thumbnail?.resource ?? topItem?.file.resource
|
||||||
|
let thumbnailIconSource: ContextMenuActionItemIconSource?
|
||||||
|
if let thumbnailResource {
|
||||||
|
var resourceId: Int64 = 0
|
||||||
|
if let resource = thumbnailResource as? CloudDocumentMediaResource {
|
||||||
|
resourceId = resource.fileId
|
||||||
|
}
|
||||||
|
let thumbnailFile = topItem?.file ?? TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: resourceId), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: thumbnailResource.size ?? 0, attributes: [])
|
||||||
|
|
||||||
|
let _ = freeMediaFileInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .stickerPack(stickerPack: .id(id: pack.id.id, accessHash: pack.accessHash), media: thumbnailFile)).start()
|
||||||
|
thumbnailIconSource = ContextMenuActionItemIconSource(
|
||||||
|
size: thumbSize,
|
||||||
|
signal: chatMessageStickerPackThumbnail(postbox: item.context.account.postbox, resource: thumbnailResource)
|
||||||
|
|> map { generator -> UIImage? in
|
||||||
|
return generator(TransformImageArguments(corners: ImageCorners(), imageSize: thumbSize, boundingSize: thumbSize, intrinsicInsets: .zero))?.generateImage()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
thumbnailIconSource = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let action = ContextMenuActionItem(text: pack.title, textLayout: .singleLine, icon: { _ in nil }, iconSource: thumbnailIconSource, iconPosition: .left, action: { _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
item.packSelected(pack)
|
||||||
|
})
|
||||||
|
let actionNode = ContextControllerActionsListActionItemNode(getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: action)
|
||||||
|
actionNodes.append(actionNode)
|
||||||
|
if actionNodes.count != item.packs.count {
|
||||||
|
let separatorNode = ASDisplayNode()
|
||||||
|
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||||
|
separatorNodes.append(separatorNode)
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
self.actionNodes = actionNodes
|
||||||
|
self.separatorNodes = separatorNodes
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.scrollNode)
|
||||||
|
for separatorNode in self.separatorNodes {
|
||||||
|
self.scrollNode.addSubnode(separatorNode)
|
||||||
|
}
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
self.scrollNode.addSubnode(actionNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func didLoad() {
|
||||||
|
super.didLoad()
|
||||||
|
|
||||||
|
self.scrollNode.view.delegate = self
|
||||||
|
self.scrollNode.view.alwaysBounceVertical = false
|
||||||
|
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||||
|
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||||
|
let minActionsWidth: CGFloat = 250.0
|
||||||
|
let maxActionsWidth: CGFloat = 300.0
|
||||||
|
let constrainedWidth = min(constrainedWidth, maxActionsWidth)
|
||||||
|
var maxWidth: CGFloat = 0.0
|
||||||
|
var contentHeight: CGFloat = 0.0
|
||||||
|
var heightsAndCompletions: [(CGFloat, (CGSize, ContainedViewLayoutTransition) -> Void)?] = []
|
||||||
|
for i in 0 ..< self.actionNodes.count {
|
||||||
|
let itemNode = self.actionNodes[i]
|
||||||
|
let (minSize, complete) = itemNode.update(presentationData: self.presentationData, constrainedSize: CGSize(width: constrainedWidth, height: constrainedHeight))
|
||||||
|
maxWidth = max(maxWidth, minSize.width)
|
||||||
|
heightsAndCompletions.append((minSize.height, complete))
|
||||||
|
contentHeight += minSize.height
|
||||||
|
}
|
||||||
|
|
||||||
|
maxWidth = max(maxWidth, minActionsWidth)
|
||||||
|
|
||||||
|
let maxHeight: CGFloat = min(155.0, constrainedHeight - 108.0)
|
||||||
|
|
||||||
|
return (CGSize(width: maxWidth, height: min(maxHeight, contentHeight)), { size, transition in
|
||||||
|
var verticalOffset: CGFloat = 0.0
|
||||||
|
for i in 0 ..< heightsAndCompletions.count {
|
||||||
|
let itemNode = self.actionNodes[i]
|
||||||
|
if let (itemHeight, itemCompletion) = heightsAndCompletions[i] {
|
||||||
|
let itemSize = CGSize(width: maxWidth, height: itemHeight)
|
||||||
|
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: itemSize))
|
||||||
|
itemCompletion(itemSize, transition)
|
||||||
|
verticalOffset += itemHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < self.actionNodes.count - 1 {
|
||||||
|
let separatorNode = self.separatorNodes[i]
|
||||||
|
separatorNode.frame = CGRect(x: 0, y: verticalOffset, width: size.width, height: UIScreenPixel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTheme(presentationData: PresentationData) {
|
||||||
|
// for actionNode in self.actionNodes {
|
||||||
|
// actionNode.updateTheme(presentationData: presentationData)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActionEnabled: Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func performAction() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIsHighlighted(_ value: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func canBeHighlighted() -> Bool {
|
||||||
|
return self.isActionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIsHighlighted(isHighlighted: Bool) {
|
||||||
|
self.setIsHighlighted(isHighlighted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||||
|
// for actionNode in self.actionNodes {
|
||||||
|
// let frame = actionNode.convert(actionNode.bounds, to: self)
|
||||||
|
// if frame.contains(point) {
|
||||||
|
// return actionNode
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
actionNode.updateIsHighlighted(isHighlighted: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
//
|
||||||
|
// PeerInfoScreenBirthdatePickerItem.swift
|
||||||
|
// MediaEditorScreen
|
||||||
|
//
|
||||||
|
// Created by Ilya Laktyushin on 15.03.2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
@ -38,9 +38,10 @@ final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
|
|||||||
let text: String
|
let 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)
|
||||||
|
@ -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
|
||||||
|
@ -186,10 +186,12 @@ final class GreetingMessageListItemComponent: Component {
|
|||||||
},
|
},
|
||||||
openPremiumIntro: {
|
openPremiumIntro: {
|
||||||
},
|
},
|
||||||
openPremiumGift: {
|
openPremiumGift: { _ in
|
||||||
},
|
},
|
||||||
openActiveSessions: {
|
openActiveSessions: {
|
||||||
},
|
},
|
||||||
|
openBirthdaySetup: {
|
||||||
|
},
|
||||||
performActiveSessionAction: { _, _ in
|
performActiveSessionAction: { _, _ in
|
||||||
},
|
},
|
||||||
openChatFolderUpdates: {
|
openChatFolderUpdates: {
|
||||||
|
@ -201,10 +201,12 @@ final class QuickReplySetupScreenComponent: Component {
|
|||||||
},
|
},
|
||||||
openPremiumIntro: {
|
openPremiumIntro: {
|
||||||
},
|
},
|
||||||
openPremiumGift: {
|
openPremiumGift: { _ in
|
||||||
},
|
},
|
||||||
openActiveSessions: {
|
openActiveSessions: {
|
||||||
},
|
},
|
||||||
|
openBirthdaySetup: {
|
||||||
|
},
|
||||||
performActiveSessionAction: { _, _ in
|
performActiveSessionAction: { _, _ in
|
||||||
},
|
},
|
||||||
openChatFolderUpdates: {
|
openChatFolderUpdates: {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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?()
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "more.pdf",
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
%PDF-1.7
|
|
||||||
|
|
||||||
1 0 obj
|
|
||||||
<< >>
|
|
||||||
endobj
|
|
||||||
|
|
||||||
2 0 obj
|
|
||||||
<< /Length 3 0 R >>
|
|
||||||
stream
|
|
||||||
/DeviceRGB CS
|
|
||||||
/DeviceRGB cs
|
|
||||||
q
|
|
||||||
1.000000 0.000000 -0.000000 1.000000 1.500000 1.335754 cm
|
|
||||||
0.000000 0.000000 0.000000 scn
|
|
||||||
5.252930 4.662109 m
|
|
||||||
5.252930 4.527832 5.199219 4.409668 5.097168 4.307617 c
|
|
||||||
0.843262 0.145020 l
|
|
||||||
0.746582 0.048340 0.628418 0.000000 0.488770 0.000000 c
|
|
||||||
0.214844 0.000000 0.000000 0.209473 0.000000 0.488770 c
|
|
||||||
0.000000 0.628418 0.053711 0.746582 0.139648 0.837891 c
|
|
||||||
4.049805 4.662109 l
|
|
||||||
0.139648 8.486328 l
|
|
||||||
0.053711 8.577637 0.000000 8.701172 0.000000 8.835449 c
|
|
||||||
0.000000 9.114746 0.214844 9.324219 0.488770 9.324219 c
|
|
||||||
0.628418 9.324219 0.746582 9.275879 0.843262 9.184570 c
|
|
||||||
5.097168 5.016602 l
|
|
||||||
5.199219 4.919922 5.252930 4.796387 5.252930 4.662109 c
|
|
||||||
h
|
|
||||||
f
|
|
||||||
n
|
|
||||||
Q
|
|
||||||
|
|
||||||
endstream
|
|
||||||
endobj
|
|
||||||
|
|
||||||
3 0 obj
|
|
||||||
675
|
|
||||||
endobj
|
|
||||||
|
|
||||||
4 0 obj
|
|
||||||
<< /Annots []
|
|
||||||
/Type /Page
|
|
||||||
/MediaBox [ 0.000000 0.000000 8.000000 12.000000 ]
|
|
||||||
/Resources 1 0 R
|
|
||||||
/Contents 2 0 R
|
|
||||||
/Parent 5 0 R
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
|
|
||||||
5 0 obj
|
|
||||||
<< /Kids [ 4 0 R ]
|
|
||||||
/Count 1
|
|
||||||
/Type /Pages
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
|
|
||||||
6 0 obj
|
|
||||||
<< /Pages 5 0 R
|
|
||||||
/Type /Catalog
|
|
||||||
>>
|
|
||||||
endobj
|
|
||||||
|
|
||||||
xref
|
|
||||||
0 7
|
|
||||||
0000000000 65535 f
|
|
||||||
0000000010 00000 n
|
|
||||||
0000000034 00000 n
|
|
||||||
0000000765 00000 n
|
|
||||||
0000000787 00000 n
|
|
||||||
0000000959 00000 n
|
|
||||||
0000001033 00000 n
|
|
||||||
trailer
|
|
||||||
<< /ID [ (some) (id) ]
|
|
||||||
/Root 6 0 R
|
|
||||||
/Size 7
|
|
||||||
>>
|
|
||||||
startxref
|
|
||||||
1092
|
|
||||||
%%EOF
|
|
Binary file not shown.
@ -453,6 +453,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
weak var checksTooltipController: TooltipController?
|
weak var 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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -1,472 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import UIKit
|
|
||||||
import Display
|
|
||||||
import AsyncDisplayKit
|
|
||||||
import TelegramPresentationData
|
|
||||||
import ChatPresentationInterfaceState
|
|
||||||
import ComponentFlow
|
|
||||||
import AvatarNode
|
|
||||||
import MultilineTextComponent
|
|
||||||
import PlainButtonComponent
|
|
||||||
import ComponentDisplayAdapters
|
|
||||||
import AccountContext
|
|
||||||
import TelegramCore
|
|
||||||
import BundleIconComponent
|
|
||||||
import ContextUI
|
|
||||||
import SwiftSignalKit
|
|
||||||
|
|
||||||
private final class ChatManagingBotTitlePanelComponent: Component {
|
|
||||||
let context: AccountContext
|
|
||||||
let theme: PresentationTheme
|
|
||||||
let strings: PresentationStrings
|
|
||||||
let insets: UIEdgeInsets
|
|
||||||
let peer: EnginePeer
|
|
||||||
let managesChat: Bool
|
|
||||||
let isPaused: Bool
|
|
||||||
let toggleIsPaused: () -> Void
|
|
||||||
let openSettings: (UIView) -> Void
|
|
||||||
|
|
||||||
init(
|
|
||||||
context: AccountContext,
|
|
||||||
theme: PresentationTheme,
|
|
||||||
strings: PresentationStrings,
|
|
||||||
insets: UIEdgeInsets,
|
|
||||||
peer: EnginePeer,
|
|
||||||
managesChat: Bool,
|
|
||||||
isPaused: Bool,
|
|
||||||
toggleIsPaused: @escaping () -> Void,
|
|
||||||
openSettings: @escaping (UIView) -> Void
|
|
||||||
) {
|
|
||||||
self.context = context
|
|
||||||
self.theme = theme
|
|
||||||
self.strings = strings
|
|
||||||
self.insets = insets
|
|
||||||
self.peer = peer
|
|
||||||
self.managesChat = managesChat
|
|
||||||
self.isPaused = isPaused
|
|
||||||
self.toggleIsPaused = toggleIsPaused
|
|
||||||
self.openSettings = openSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
static func ==(lhs: ChatManagingBotTitlePanelComponent, rhs: ChatManagingBotTitlePanelComponent) -> Bool {
|
|
||||||
if lhs.context !== rhs.context {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.theme !== rhs.theme {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.strings != rhs.strings {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.insets != rhs.insets {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.peer != rhs.peer {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.managesChat != rhs.managesChat {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.isPaused != rhs.isPaused {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
final class View: UIView {
|
|
||||||
private let title = ComponentView<Empty>()
|
|
||||||
private let text = ComponentView<Empty>()
|
|
||||||
private var avatarNode: AvatarNode?
|
|
||||||
private let actionButton = ComponentView<Empty>()
|
|
||||||
private let settingsButton = ComponentView<Empty>()
|
|
||||||
|
|
||||||
private var component: ChatManagingBotTitlePanelComponent?
|
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
|
||||||
super.init(frame: frame)
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(component: ChatManagingBotTitlePanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
|
||||||
self.component = component
|
|
||||||
|
|
||||||
let topInset: CGFloat = 6.0
|
|
||||||
let bottomInset: CGFloat = 6.0
|
|
||||||
let avatarDiameter: CGFloat = 36.0
|
|
||||||
let avatarTextSpacing: CGFloat = 10.0
|
|
||||||
let titleTextSpacing: CGFloat = 1.0
|
|
||||||
let leftInset: CGFloat = component.insets.left + 12.0
|
|
||||||
let rightInset: CGFloat = component.insets.right + 10.0
|
|
||||||
let actionAndSettingsButtonsSpacing: CGFloat = 8.0
|
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let actionButtonSize = self.actionButton.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(PlainButtonComponent(
|
|
||||||
content: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(string: component.isPaused ? "START" : "STOP", font: Font.semibold(15.0), textColor: component.theme.list.itemCheckColors.foregroundColor))
|
|
||||||
)),
|
|
||||||
background: AnyComponent(RoundedRectangle(
|
|
||||||
color: component.theme.list.itemCheckColors.fillColor,
|
|
||||||
cornerRadius: nil
|
|
||||||
)),
|
|
||||||
effectAlignment: .center,
|
|
||||||
contentInsets: UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0),
|
|
||||||
action: { [weak self] in
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
component.toggleIsPaused()
|
|
||||||
},
|
|
||||||
animateAlpha: true,
|
|
||||||
animateScale: false,
|
|
||||||
animateContents: false
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 150.0, height: 100.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
let settingsButtonSize = self.settingsButton.update(
|
|
||||||
transition: transition,
|
|
||||||
component: AnyComponent(PlainButtonComponent(
|
|
||||||
content: AnyComponent(BundleIconComponent(
|
|
||||||
name: "Chat/Context Menu/Customize",
|
|
||||||
tintColor: component.theme.rootController.navigationBar.controlColor
|
|
||||||
)),
|
|
||||||
effectAlignment: .center,
|
|
||||||
minSize: CGSize(width: 1.0, height: 40.0),
|
|
||||||
contentInsets: UIEdgeInsets(top: 0.0, left: 2.0, bottom: 0.0, right: 2.0),
|
|
||||||
action: { [weak self] in
|
|
||||||
guard let self, let component = self.component else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let settingsButtonView = self.settingsButton.view else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
component.openSettings(settingsButtonView)
|
|
||||||
},
|
|
||||||
animateAlpha: true,
|
|
||||||
animateScale: false,
|
|
||||||
animateContents: false
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: 150.0, height: 100.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
let maxTextWidth: CGFloat = availableSize.width - leftInset - avatarDiameter - avatarTextSpacing - rightInset - actionButtonSize.width - actionAndSettingsButtonsSpacing - settingsButtonSize.width - 8.0
|
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.semibold(16.0), textColor: component.theme.rootController.navigationBar.primaryTextColor))
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
|
||||||
)
|
|
||||||
//TODO:localize
|
|
||||||
let textSize = self.text.update(
|
|
||||||
transition: .immediate,
|
|
||||||
component: AnyComponent(MultilineTextComponent(
|
|
||||||
text: .plain(NSAttributedString(string: "bot has access to this chat", font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor))
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
let size = CGSize(width: availableSize.width, height: topInset + titleSize.height + titleTextSpacing + textSize.height + bottomInset)
|
|
||||||
|
|
||||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset + avatarDiameter + avatarTextSpacing, y: topInset), size: titleSize)
|
|
||||||
if let titleView = self.title.view {
|
|
||||||
if titleView.superview == nil {
|
|
||||||
titleView.layer.anchorPoint = CGPoint()
|
|
||||||
self.addSubview(titleView)
|
|
||||||
}
|
|
||||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
|
||||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
|
||||||
}
|
|
||||||
|
|
||||||
let textFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleTextSpacing), size: textSize)
|
|
||||||
if let textView = self.text.view {
|
|
||||||
if textView.superview == nil {
|
|
||||||
textView.layer.anchorPoint = CGPoint()
|
|
||||||
self.addSubview(textView)
|
|
||||||
}
|
|
||||||
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
|
||||||
transition.setPosition(view: textView, position: textFrame.origin)
|
|
||||||
}
|
|
||||||
|
|
||||||
let avatarFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - avatarDiameter) * 0.5)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
|
|
||||||
let avatarNode: AvatarNode
|
|
||||||
if let current = self.avatarNode {
|
|
||||||
avatarNode = current
|
|
||||||
} else {
|
|
||||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
|
|
||||||
self.avatarNode = avatarNode
|
|
||||||
self.addSubview(avatarNode.view)
|
|
||||||
}
|
|
||||||
avatarNode.frame = avatarFrame
|
|
||||||
avatarNode.updateSize(size: avatarFrame.size)
|
|
||||||
avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer)
|
|
||||||
|
|
||||||
let settingsButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - settingsButtonSize.width, y: floor((size.height - settingsButtonSize.height) * 0.5)), size: settingsButtonSize)
|
|
||||||
if let settingsButtonView = self.settingsButton.view {
|
|
||||||
if settingsButtonView.superview == nil {
|
|
||||||
self.addSubview(settingsButtonView)
|
|
||||||
}
|
|
||||||
transition.setFrame(view: settingsButtonView, frame: settingsButtonFrame)
|
|
||||||
}
|
|
||||||
|
|
||||||
let actionButtonFrame = CGRect(origin: CGPoint(x: settingsButtonFrame.minX - actionAndSettingsButtonsSpacing - actionButtonSize.width, y: floor((size.height - actionButtonSize.height) * 0.5)), size: actionButtonSize)
|
|
||||||
if let actionButtonView = self.actionButton.view {
|
|
||||||
if actionButtonView.superview == nil {
|
|
||||||
self.addSubview(actionButtonView)
|
|
||||||
}
|
|
||||||
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
|
||||||
}
|
|
||||||
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeView() -> View {
|
|
||||||
return View(frame: CGRect())
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
|
||||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode {
|
|
||||||
private let context: AccountContext
|
|
||||||
private let separatorNode: ASDisplayNode
|
|
||||||
private let content = ComponentView<Empty>()
|
|
||||||
|
|
||||||
private var chatLocation: ChatLocation?
|
|
||||||
private var theme: PresentationTheme?
|
|
||||||
private var managingBot: ChatManagingBot?
|
|
||||||
|
|
||||||
init(context: AccountContext) {
|
|
||||||
self.context = context
|
|
||||||
self.separatorNode = ASDisplayNode()
|
|
||||||
self.separatorNode.isLayerBacked = true
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
self.addSubnode(self.separatorNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func toggleIsPaused() {
|
|
||||||
guard let chatPeerId = self.chatLocation?.peerId else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = (self.context.engine.data.get(
|
|
||||||
TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: self.context.account.peerId)
|
|
||||||
)
|
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] bot in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let bot else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var recipients = bot.recipients
|
|
||||||
var additionalPeers = recipients.additionalPeers
|
|
||||||
if additionalPeers.contains(chatPeerId) {
|
|
||||||
additionalPeers.remove(chatPeerId)
|
|
||||||
} else {
|
|
||||||
additionalPeers.insert(chatPeerId)
|
|
||||||
}
|
|
||||||
recipients = TelegramBusinessRecipients(
|
|
||||||
categories: recipients.categories,
|
|
||||||
additionalPeers: additionalPeers,
|
|
||||||
exclude: recipients.exclude
|
|
||||||
)
|
|
||||||
|
|
||||||
let _ = self.context.engine.accountData.setAccountConnectedBot(bot: TelegramAccountConnectedBot(
|
|
||||||
id: bot.id,
|
|
||||||
recipients: recipients,
|
|
||||||
canReply: bot.canReply
|
|
||||||
)).startStandalone()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private func openSettingsMenu(sourceView: UIView) {
|
|
||||||
guard let interfaceInteraction = self.interfaceInteraction else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let chatController = interfaceInteraction.chatController() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let managingBot = self.managingBot else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = managingBot
|
|
||||||
|
|
||||||
let strings = self.context.sharedContext.currentPresentationData.with { $0 }.strings
|
|
||||||
let _ = strings
|
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Remove bot from this chat", textColor: .destructive, icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
|
|
||||||
}, action: { [weak self] _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = self
|
|
||||||
})))
|
|
||||||
if let url = managingBot.settingsUrl {
|
|
||||||
items.append(.action(ContextMenuActionItem(text: "Manage Bot", icon: { theme in
|
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
|
|
||||||
}, action: { [weak self] _, a in
|
|
||||||
a(.default)
|
|
||||||
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let _ = (self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url, skipUrlAuth: false)
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let chatController = interfaceInteraction.chatController() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.context.sharedContext.openResolvedUrl(
|
|
||||||
result,
|
|
||||||
context: self.context,
|
|
||||||
urlContext: .generic,
|
|
||||||
navigationController: chatController.navigationController as? NavigationController,
|
|
||||||
forceExternal: false,
|
|
||||||
openPeer: { [weak self] peer, navigation in
|
|
||||||
guard let self, let chatController = interfaceInteraction.chatController() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let navigationController = chatController.navigationController as? NavigationController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch navigation {
|
|
||||||
case let .chat(_, subject, peekData):
|
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: subject, peekData: peekData))
|
|
||||||
case let .withBotStartPayload(botStart):
|
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botStart: botStart, keepStack: .always))
|
|
||||||
case let .withAttachBot(attachBotStart):
|
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
|
|
||||||
case let .withBotApp(botAppStart):
|
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botAppStart: botAppStart))
|
|
||||||
case .info:
|
|
||||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
|
||||||
guard let self, let peer, let chatController = interfaceInteraction.chatController() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let navigationController = chatController.navigationController as? NavigationController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
|
||||||
navigationController.pushViewController(controller)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sendFile: nil,
|
|
||||||
sendSticker: nil,
|
|
||||||
requestMessageActionUrlAuth: nil,
|
|
||||||
joinVoiceChat: nil,
|
|
||||||
present: { [weak chatController] c, a in
|
|
||||||
chatController?.present(c, in: .window(.root), with: a)
|
|
||||||
},
|
|
||||||
dismissInput: {
|
|
||||||
},
|
|
||||||
contentContext: nil,
|
|
||||||
progress: nil,
|
|
||||||
completion: nil
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: chatController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
|
||||||
interfaceInteraction.presentController(contextController, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
|
|
||||||
self.chatLocation = interfaceState.chatLocation
|
|
||||||
self.managingBot = interfaceState.contactStatus?.managingBot
|
|
||||||
|
|
||||||
if interfaceState.theme !== self.theme {
|
|
||||||
self.theme = interfaceState.theme
|
|
||||||
|
|
||||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
|
||||||
|
|
||||||
if let managingBot = interfaceState.contactStatus?.managingBot {
|
|
||||||
let contentSize = self.content.update(
|
|
||||||
transition: Transition(transition),
|
|
||||||
component: AnyComponent(ChatManagingBotTitlePanelComponent(
|
|
||||||
context: self.context,
|
|
||||||
theme: interfaceState.theme,
|
|
||||||
strings: interfaceState.strings,
|
|
||||||
insets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: 0.0, right: rightInset),
|
|
||||||
peer: managingBot.bot,
|
|
||||||
managesChat: managingBot.canReply,
|
|
||||||
isPaused: managingBot.isPaused,
|
|
||||||
toggleIsPaused: { [weak self] in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.toggleIsPaused()
|
|
||||||
},
|
|
||||||
openSettings: { [weak self] sourceView in
|
|
||||||
guard let self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.openSettingsMenu(sourceView: sourceView)
|
|
||||||
}
|
|
||||||
)),
|
|
||||||
environment: {},
|
|
||||||
containerSize: CGSize(width: width, height: 1000.0)
|
|
||||||
)
|
|
||||||
if let contentView = self.content.view {
|
|
||||||
if contentView.superview == nil {
|
|
||||||
self.view.addSubview(contentView)
|
|
||||||
}
|
|
||||||
transition.updateFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: contentSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
return LayoutResult(backgroundHeight: contentSize.height, insetHeight: contentSize.height, hitTestSlop: 0.0)
|
|
||||||
} else {
|
|
||||||
return LayoutResult(backgroundHeight: 0.0, insetHeight: 0.0, hitTestSlop: 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
|
||||||
private let controller: ViewController
|
|
||||||
private let sourceView: UIView
|
|
||||||
|
|
||||||
init(controller: ViewController, sourceView: UIView) {
|
|
||||||
self.controller = controller
|
|
||||||
self.sourceView = sourceView
|
|
||||||
}
|
|
||||||
|
|
||||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
|
||||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -282,8 +282,9 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
}, openStorageManagement: {
|
}, openStorageManagement: {
|
||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
}, openPremiumIntro: {
|
}, openPremiumIntro: {
|
||||||
}, openPremiumGift: {
|
}, openPremiumGift: { _ in
|
||||||
}, openActiveSessions: {
|
}, openActiveSessions: {
|
||||||
|
}, openBirthdaySetup: {
|
||||||
}, performActiveSessionAction: { _, _ in
|
}, performActiveSessionAction: { _, _ in
|
||||||
}, openChatFolderUpdates: {
|
}, openChatFolderUpdates: {
|
||||||
}, hideChatFolderUpdates: {
|
}, hideChatFolderUpdates: {
|
||||||
|
@ -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
|
||||||
|
@ -156,10 +156,12 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
|
|||||||
},
|
},
|
||||||
openPremiumIntro: {
|
openPremiumIntro: {
|
||||||
},
|
},
|
||||||
openPremiumGift: {
|
openPremiumGift: { _ in
|
||||||
},
|
},
|
||||||
openActiveSessions: {
|
openActiveSessions: {
|
||||||
},
|
},
|
||||||
|
openBirthdaySetup: {
|
||||||
|
},
|
||||||
performActiveSessionAction: { _, _ in
|
performActiveSessionAction: { _, _ in
|
||||||
},
|
},
|
||||||
openChatFolderUpdates: {
|
openChatFolderUpdates: {
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user