diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 64cfd03dc6..585db597dc 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -856,6 +856,9 @@ public protocol AutomaticBusinessMessageSetupScreenInitialData: AnyObject { public protocol ChatbotSetupScreenInitialData: AnyObject { } +public protocol BusinessIntroSetupScreenInitialData: AnyObject { +} + public protocol CollectibleItemInfoScreenInitialData: AnyObject { var collectibleItemInfo: TelegramCollectibleItemInfo { get } } @@ -960,6 +963,7 @@ public protocol SharedAccountContext: AnyObject { func makeAutomaticBusinessMessageSetupScreenInitialData(context: AccountContext) -> Signal func makeQuickReplySetupScreen(context: AccountContext, initialData: QuickReplySetupScreenInitialData) -> ViewController func makeQuickReplySetupScreenInitialData(context: AccountContext) -> Signal + func makeBusinessIntroSetupScreen(context: AccountContext) -> ViewController func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController func makeCollectibleItemInfoScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, subject: CollectibleItemInfoScreenSubject) -> Signal func navigateToChatController(_ params: NavigateToChatControllerParams) diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index 0d9f444f23..880014fbf6 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -46,6 +46,7 @@ public enum ContactMultiselectionControllerMode { public var displayAutoremoveTimeout: Bool public var displayPresence: Bool public var onlyUsers: Bool + public var disableChannels: Bool public init( title: String, @@ -55,7 +56,8 @@ public enum ContactMultiselectionControllerMode { chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool = false, displayPresence: Bool = false, - onlyUsers: Bool = false + onlyUsers: Bool = false, + disableChannels: Bool = false ) { self.title = title self.searchPlaceholder = searchPlaceholder @@ -65,6 +67,7 @@ public enum ContactMultiselectionControllerMode { self.displayAutoremoveTimeout = displayAutoremoveTimeout self.displayPresence = displayPresence self.onlyUsers = onlyUsers + self.disableChannels = disableChannels } } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index 75406cdcf6..3cc929b696 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -231,17 +231,52 @@ public enum ChatRecordedMediaPreview: Equatable { case video(Video) } +public final class ChatManagingBot: Equatable { + public let bot: EnginePeer + public let isPaused: Bool + public let canReply: Bool + public let settingsUrl: String? + + public init(bot: EnginePeer, isPaused: Bool, canReply: Bool, settingsUrl: String?) { + self.bot = bot + self.isPaused = isPaused + self.canReply = canReply + self.settingsUrl = settingsUrl + } + + public static func ==(lhs: ChatManagingBot, rhs: ChatManagingBot) -> Bool { + if lhs === rhs { + return true + } + if lhs.bot != rhs.bot { + return false + } + if lhs.isPaused != rhs.isPaused { + return false + } + if lhs.canReply != rhs.canReply { + return false + } + if lhs.settingsUrl != rhs.settingsUrl { + return false + } + return true + } +} + public struct ChatContactStatus: Equatable { public var canAddContact: Bool public var canReportIrrelevantLocation: Bool public var peerStatusSettings: PeerStatusSettings? public var invitedBy: Peer? + public var managingBot: ChatManagingBot? - public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?) { + public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?, managingBot: ChatManagingBot?) { self.canAddContact = canAddContact self.canReportIrrelevantLocation = canReportIrrelevantLocation self.peerStatusSettings = peerStatusSettings self.invitedBy = invitedBy + self.managingBot = managingBot } public var isEmpty: Bool { @@ -270,6 +305,9 @@ public struct ChatContactStatus: Equatable { if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) { return false } + if lhs.managingBot != rhs.managingBot { + return false + } return true } } diff --git a/submodules/ComponentFlow/Source/Components/Rectangle.swift b/submodules/ComponentFlow/Source/Components/Rectangle.swift index e1254ec1de..8eaf0971e4 100644 --- a/submodules/ComponentFlow/Source/Components/Rectangle.swift +++ b/submodules/ComponentFlow/Source/Components/Rectangle.swift @@ -5,11 +5,13 @@ public final class Rectangle: Component { private let color: UIColor private let width: CGFloat? private let height: CGFloat? + private let tag: NSObject? - public init(color: UIColor, width: CGFloat? = nil, height: CGFloat? = nil) { + public init(color: UIColor, width: CGFloat? = nil, height: CGFloat? = nil, tag: NSObject? = nil) { self.color = color self.width = width self.height = height + self.tag = tag } public static func ==(lhs: Rectangle, rhs: Rectangle) -> Bool { @@ -25,7 +27,33 @@ public final class Rectangle: Component { return true } - public func update(view: UIView, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + public final class View: UIView, ComponentTaggedView { + fileprivate var componentTag: NSObject? + + override public init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func matches(tag: Any) -> Bool { + if let componentTag = self.componentTag { + let tag = tag as AnyObject + if componentTag === tag { + return true + } + } + return false + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { var size = availableSize if let width = self.width { size.width = min(size.width, width) @@ -35,6 +63,7 @@ public final class Rectangle: Component { } view.backgroundColor = self.color + view.componentTag = self.tag return size } diff --git a/submodules/ComponentFlow/Source/Components/VStack.swift b/submodules/ComponentFlow/Source/Components/VStack.swift index 1dd0e3d31d..b114765b0a 100644 --- a/submodules/ComponentFlow/Source/Components/VStack.swift +++ b/submodules/ComponentFlow/Source/Components/VStack.swift @@ -13,11 +13,13 @@ public final class VStack: CombinedComponent { private let items: [AnyComponentWithIdentity] private let alignment: VStackAlignment private let spacing: CGFloat + private let fillWidth: Bool - public init(_ items: [AnyComponentWithIdentity], alignment: VStackAlignment = .center, spacing: CGFloat) { + public init(_ items: [AnyComponentWithIdentity], alignment: VStackAlignment = .center, spacing: CGFloat, fillWidth: Bool = false) { self.items = items self.alignment = alignment self.spacing = spacing + self.fillWidth = fillWidth } public static func ==(lhs: VStack, rhs: VStack) -> Bool { @@ -30,6 +32,9 @@ public final class VStack: CombinedComponent { if lhs.spacing != rhs.spacing { return false } + if lhs.fillWidth != rhs.fillWidth { + return false + } return true } @@ -48,6 +53,9 @@ public final class VStack: CombinedComponent { } var size = CGSize(width: 0.0, height: 0.0) + if context.component.fillWidth { + size.width = context.availableSize.width + } for child in updatedChildren { size.height += child.size.height size.width = max(size.width, child.size.width) diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index 739937f0f7..bc606e7a54 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -445,6 +445,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { let threadInfo: EngineMessageHistoryThread.Info? let height: ItemListPeerItemHeight let aliasHandling: ItemListPeerItemAliasHandling + let customAvatarIcon: UIImage? let nameColor: ItemListPeerItemNameColor let nameStyle: ItemListPeerItemNameStyle let presence: EnginePeer.Presence? @@ -486,6 +487,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { threadInfo: EngineMessageHistoryThread.Info? = nil, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, + customAvatarIcon: UIImage? = nil, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, @@ -526,6 +528,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { self.threadInfo = threadInfo self.height = height self.aliasHandling = aliasHandling + self.customAvatarIcon = customAvatarIcon self.nameColor = nameColor self.nameStyle = nameStyle self.presence = presence @@ -568,6 +571,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { threadInfo: EngineMessageHistoryThread.Info? = nil, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, + customAvatarIcon: UIImage? = nil, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, @@ -608,6 +612,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { self.threadInfo = threadInfo self.height = height self.aliasHandling = aliasHandling + self.customAvatarIcon = customAvatarIcon self.nameColor = nameColor self.nameStyle = nameStyle self.presence = presence @@ -717,6 +722,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo private var avatarIconComponent: EmojiStatusComponent? private var avatarIconView: ComponentView? + private var customAvatarIconView: UIImageView? + private var avatarButton: HighlightTrackingButton? private let titleNode: TextNode @@ -1701,6 +1708,24 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo shimmerNode.removeFromSupernode() } + if let customAvatarIcon = item.customAvatarIcon { + strongSelf.avatarNode.isHidden = true + + let customAvatarIconView: UIImageView + if let current = strongSelf.customAvatarIconView { + customAvatarIconView = current + } else { + customAvatarIconView = UIImageView() + strongSelf.customAvatarIconView = customAvatarIconView + strongSelf.containerNode.view.addSubview(customAvatarIconView) + } + customAvatarIconView.image = customAvatarIcon + customAvatarIconView.frame = strongSelf.avatarNode.frame + } else if let customAvatarIconView = strongSelf.customAvatarIconView { + strongSelf.customAvatarIconView = nil + customAvatarIconView.removeFromSuperview() + } + strongSelf.backgroundNode.isHidden = !item.displayDecorations strongSelf.highlightedBackgroundNode.isHidden = !item.displayDecorations || !item.highlightable diff --git a/submodules/LocalAuth/Sources/LocalAuth.swift b/submodules/LocalAuth/Sources/LocalAuth.swift index 7796fc355e..ad67c038e6 100644 --- a/submodules/LocalAuth/Sources/LocalAuth.swift +++ b/submodules/LocalAuth/Sources/LocalAuth.swift @@ -21,6 +21,23 @@ public struct LocalAuth { case error(Error) } + #if targetEnvironment(simulator) + public final class PrivateKey { + public let publicKeyRepresentation: Data + + fileprivate init() { + self.publicKeyRepresentation = Data(count: 32) + } + + public func encrypt(data: Data) -> Data? { + return data + } + + public func decrypt(data: Data) -> DecryptionResult { + return .result(data) + } + } + #else public final class PrivateKey { private let privateKey: SecKey private let publicKey: SecKey @@ -64,6 +81,7 @@ public struct LocalAuth { return .result(result) } } + #endif public static var biometricAuthentication: LocalAuthBiometricAuthentication? { let context = LAContext() @@ -157,7 +175,18 @@ public struct LocalAuth { return seedId; } - public static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + public static func getOrCreatePrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + if let key = self.getPrivateKey(baseAppBundleId: baseAppBundleId, keyId: keyId) { + return key + } else { + return self.addPrivateKey(baseAppBundleId: baseAppBundleId, keyId: keyId) + } + } + + private static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + #if targetEnvironment(simulator) + return PrivateKey() + #else guard let bundleSeedId = self.bundleSeedId() else { return nil } @@ -196,6 +225,7 @@ public struct LocalAuth { let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data) return result + #endif } public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool { @@ -221,7 +251,10 @@ public struct LocalAuth { return true } - public static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + private static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + #if targetEnvironment(simulator) + return PrivateKey() + #else guard let bundleSeedId = self.bundleSeedId() else { return nil } @@ -262,5 +295,6 @@ public struct LocalAuth { let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data) return result + #endif } } diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h index b1e9361e08..c7105cb69c 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h @@ -8,6 +8,7 @@ @property (nonatomic) NSUInteger internalServerErrorCount; @property (nonatomic) NSUInteger floodWaitSeconds; +@property (nonatomic, strong) NSString *floodWaitErrorText; @property (nonatomic) bool waitingForTokenExport; @property (nonatomic, strong) id waitingForRequestToComplete; diff --git a/submodules/MtProtoKit/Sources/MTRequestMessageService.m b/submodules/MtProtoKit/Sources/MTRequestMessageService.m index 83457c3c61..a81adedb34 100644 --- a/submodules/MtProtoKit/Sources/MTRequestMessageService.m +++ b/submodules/MtProtoKit/Sources/MTRequestMessageService.m @@ -808,7 +808,7 @@ } restartRequest = true; } - else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound) { + else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) { if (request.errorContext == nil) request.errorContext = [[MTRequestErrorContext alloc] init]; @@ -821,6 +821,32 @@ if ([scanner scanInt:&errorWaitTime]) { request.errorContext.floodWaitSeconds = errorWaitTime; + request.errorContext.floodWaitErrorText = rpcError.errorDescription; + + if (request.shouldContinueExecutionWithErrorContext != nil) + { + if (request.shouldContinueExecutionWithErrorContext(request.errorContext)) + { + restartRequest = true; + request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime); + } + } + else + { + restartRequest = true; + request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime); + } + } + } else if ([rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) { + int errorWaitTime = 0; + + NSScanner *scanner = [[NSScanner alloc] initWithString:rpcError.errorDescription]; + [scanner scanUpToString:@"FLOOD_PREMIUM_WAIT_" intoString:nil]; + [scanner scanString:@"FLOOD_PREMIUM_WAIT_" intoString:nil]; + if ([scanner scanInt:&errorWaitTime]) + { + request.errorContext.floodWaitSeconds = errorWaitTime; + request.errorContext.floodWaitErrorText = rpcError.errorDescription; if (request.shouldContinueExecutionWithErrorContext != nil) { diff --git a/submodules/Postbox/Sources/TimeBasedCleanup.swift b/submodules/Postbox/Sources/TimeBasedCleanup.swift index 1c1a0be7eb..d0b9fddc61 100644 --- a/submodules/Postbox/Sources/TimeBasedCleanup.swift +++ b/submodules/Postbox/Sources/TimeBasedCleanup.swift @@ -19,8 +19,9 @@ public func printOpenFiles() { var flags: Int32 = 0 var fd: Int32 = 0 var buf = Data(count: Int(MAXPATHLEN) + 1) + let maxFd = min(1024, FD_SETSIZE) - while fd < FD_SETSIZE { + while fd < maxFd { errno = 0; flags = fcntl(fd, F_GETFD, 0); if flags == -1 && errno != 0 { diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 5872e7f481..87252b60f1 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -117,6 +117,7 @@ swift_library( "//submodules/TelegramUI/Components/EmojiStatusSelectionComponent", "//submodules/TelegramUI/Components/EntityKeyboard", "//submodules/TelegramUI/Components/PremiumPeerShortcutComponent", + "//submodules/TelegramUI/Components/EmojiActionIconComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index df434153cc..7aff6f6db1 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -32,6 +32,7 @@ import ListActionItemComponent import EmojiStatusSelectionComponent import EmojiStatusComponent import EntityKeyboard +import EmojiActionIconComponent public enum PremiumSource: Equatable { public static func == (lhs: PremiumSource, rhs: PremiumSource) -> Bool { @@ -2240,6 +2241,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData)) }) + case .businessIntro: + push(accountContext.sharedContext.makeBusinessIntroSetupScreen(context: accountContext)) default: fatalError() } @@ -3714,89 +3717,6 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { } } - - - -private final class EmojiActionIconComponent: Component { - let context: AccountContext - let color: UIColor - let fileId: Int64? - let file: TelegramMediaFile? - - init( - context: AccountContext, - color: UIColor, - fileId: Int64?, - file: TelegramMediaFile? - ) { - self.context = context - self.color = color - self.fileId = fileId - self.file = file - } - - static func ==(lhs: EmojiActionIconComponent, rhs: EmojiActionIconComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.color != rhs.color { - return false - } - if lhs.fileId != rhs.fileId { - return false - } - if lhs.file != rhs.file { - return false - } - return true - } - - final class View: UIView { - private let icon = ComponentView() - - func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - let size = CGSize(width: 24.0, height: 24.0) - - let _ = self.icon.update( - transition: .immediate, - component: AnyComponent(EmojiStatusComponent( - context: component.context, - animationCache: component.context.animationCache, - animationRenderer: component.context.animationRenderer, - content: component.fileId.flatMap { .animation( - content: .customEmoji(fileId: $0), - size: CGSize(width: size.width * 2.0, height: size.height * 2.0), - placeholderColor: .lightGray, - themeColor: component.color, - loopMode: .forever - ) } ?? .premium(color: component.color), - isVisibleForAnimations: false, - action: nil - )), - environment: {}, - containerSize: size - ) - let iconFrame = CGRect(origin: CGPoint(), size: size) - if let iconView = self.icon.view { - if iconView.superview == nil { - self.addSubview(iconView) - } - iconView.frame = iconFrame - } - - return size - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - private final class BadgeComponent: CombinedComponent { let color: UIColor let text: String diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index d144c3129f..e6efe3d882 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -475,11 +475,24 @@ private func countForSelectivePeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> private func stringForSelectiveSettings(strings: PresentationStrings, settings: SelectivePrivacySettings) -> String { switch settings { - case let .disableEveryone(enableFor, enableForCloseFriends): - if enableFor.isEmpty && !enableForCloseFriends { + case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremium): + if enableFor.isEmpty && !enableForCloseFriends && !enableForPremium { return strings.PrivacySettings_LastSeenNobody } else { - if enableForCloseFriends { + if enableForCloseFriends && enableForPremium { + //TODO:localize + if enableFor.isEmpty { + return "Close Friends, Premium" + } else { + return "Close Friends, Premium +\(countForSelectivePeers(enableFor))" + } + } else if enableForPremium { + if enableFor.isEmpty { + return "Premium Users" + } else { + return "Premium Users +\(countForSelectivePeers(enableFor))" + } + } else if enableForCloseFriends { if enableFor.isEmpty { return strings.PrivacySettings_LastSeenCloseFriendsPlus("\(countForSelectivePeers(enableFor))").string } else { @@ -499,15 +512,27 @@ private func stringForSelectiveSettings(strings: PresentationStrings, settings: } else { return strings.PrivacySettings_LastSeenEverybodyMinus("\(countForSelectivePeers(disableFor))").string } - case let .enableContacts(enableFor, disableFor): - if !enableFor.isEmpty && !disableFor.isEmpty { - return strings.PrivacySettings_LastSeenContactsMinusPlus("\(countForSelectivePeers(disableFor))", "\(countForSelectivePeers(enableFor))").string - } else if !enableFor.isEmpty { - return strings.PrivacySettings_LastSeenContactsPlus("\(countForSelectivePeers(enableFor))").string - } else if !disableFor.isEmpty { - return strings.PrivacySettings_LastSeenContactsMinus("\(countForSelectivePeers(disableFor))").string + case let .enableContacts(enableFor, disableFor, enableForPremium): + if enableForPremium { + if !enableFor.isEmpty && !disableFor.isEmpty { + return "Premium, " + strings.PrivacySettings_LastSeenContactsMinusPlus("\(countForSelectivePeers(disableFor))", "\(countForSelectivePeers(enableFor))").string + } else if !enableFor.isEmpty { + return "Premium, " + strings.PrivacySettings_LastSeenContactsPlus("\(countForSelectivePeers(enableFor))").string + } else if !disableFor.isEmpty { + return "Premium, " + strings.PrivacySettings_LastSeenContactsMinus("\(countForSelectivePeers(disableFor))").string + } else { + return "Premium, " + strings.PrivacySettings_LastSeenContacts + } } else { - return strings.PrivacySettings_LastSeenContacts + if !enableFor.isEmpty && !disableFor.isEmpty { + return strings.PrivacySettings_LastSeenContactsMinusPlus("\(countForSelectivePeers(disableFor))", "\(countForSelectivePeers(enableFor))").string + } else if !enableFor.isEmpty { + return strings.PrivacySettings_LastSeenContactsPlus("\(countForSelectivePeers(enableFor))").string + } else if !disableFor.isEmpty { + return strings.PrivacySettings_LastSeenContactsMinus("\(countForSelectivePeers(disableFor))").string + } else { + return strings.PrivacySettings_LastSeenContacts + } } } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index f0dba91eb5..a0ea26298c 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -103,15 +103,25 @@ private enum SelectivePrivacySettingsSection: Int32 { case premium } -private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], strings: PresentationStrings) -> String { - if peers.isEmpty { +private func stringForUserCount(_ peers: [EnginePeer.Id: SelectivePrivacyPeer], enableForPremium: Bool, strings: PresentationStrings) -> String { + if peers.isEmpty && !enableForPremium { return strings.PrivacyLastSeenSettings_EmpryUsersPlaceholder } else { var result = 0 for (_, peer) in peers { result += peer.userCount } - return strings.UserCount(Int32(result)) + + if enableForPremium { + //TODO:localize + if result == 0 { + return "Premium Users" + } else { + return "Premium Users +\(result)" + } + } else { + return strings.UserCount(Int32(result)) + } } } @@ -568,6 +578,7 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { private struct SelectivePrivacySettingsControllerState: Equatable { let setting: SelectivePrivacySettingType let enableFor: [EnginePeer.Id: SelectivePrivacyPeer] + let enableForPremium: Bool let disableFor: [EnginePeer.Id: SelectivePrivacyPeer] let enableForCloseFriends: Bool @@ -585,9 +596,10 @@ private struct SelectivePrivacySettingsControllerState: Equatable { let uploadedPhoto: UIImage? - init(setting: SelectivePrivacySettingType, enableFor: [EnginePeer.Id: SelectivePrivacyPeer], disableFor: [EnginePeer.Id: SelectivePrivacyPeer], enableForCloseFriends: Bool, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PDisableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PEnableForCloseFriends: Bool?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?, phoneDiscoveryEnabled: Bool?, hideReadTimeEnabled: Bool?, uploadedPhoto: UIImage?) { + init(setting: SelectivePrivacySettingType, enableFor: [EnginePeer.Id: SelectivePrivacyPeer], enableForPremium: Bool, disableFor: [EnginePeer.Id: SelectivePrivacyPeer], enableForCloseFriends: Bool, saving: Bool, callDataSaving: VoiceCallDataSaving?, callP2PMode: SelectivePrivacySettingType?, callP2PEnableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PDisableFor: [EnginePeer.Id: SelectivePrivacyPeer]?, callP2PEnableForCloseFriends: Bool?, callIntegrationAvailable: Bool?, callIntegrationEnabled: Bool?, phoneDiscoveryEnabled: Bool?, hideReadTimeEnabled: Bool?, uploadedPhoto: UIImage?) { self.setting = setting self.enableFor = enableFor + self.enableForPremium = enableForPremium self.disableFor = disableFor self.enableForCloseFriends = enableForCloseFriends self.saving = saving @@ -610,6 +622,9 @@ private struct SelectivePrivacySettingsControllerState: Equatable { if lhs.enableFor != rhs.enableFor { return false } + if lhs.enableForPremium != rhs.enableForPremium { + return false + } if lhs.disableFor != rhs.disableFor { return false } @@ -654,55 +669,59 @@ private struct SelectivePrivacySettingsControllerState: Equatable { } func withUpdatedSetting(_ setting: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedEnableFor(_ enableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + } + + func withUpdatedEnableForPremium(_ value: Bool) -> SelectivePrivacySettingsControllerState { + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: value, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedDisableFor(_ disableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedEnableForCloseFriends(_ enableForCloseFriends: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedSaving(_ saving: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallP2PMode(_ mode: SelectivePrivacySettingType) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: mode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallP2PEnableFor(_ enableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: enableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallP2PDisableFor(_ disableFor: [EnginePeer.Id: SelectivePrivacyPeer]) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: disableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallP2PEnableForCloseFriends(_ callP2PEnableForCloseFriends: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedCallsIntegrationEnabled(_ enabled: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: enabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: enabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedPhoneDiscoveryEnabled(_ phoneDiscoveryEnabled: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } func withUpdatedUploadedPhoto(_ uploadedPhoto: UIImage?) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: self.hideReadTimeEnabled, uploadedPhoto: uploadedPhoto) } func withUpdatedHideReadTimeEnabled(_ hideReadTimeEnabled: Bool) -> SelectivePrivacySettingsControllerState { - return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) + return SelectivePrivacySettingsControllerState(setting: self.setting, enableFor: self.enableFor, enableForPremium: self.enableForPremium, disableFor: self.disableFor, enableForCloseFriends: self.enableForCloseFriends, saving: self.saving, callDataSaving: self.callDataSaving, callP2PMode: self.callP2PMode, callP2PEnableFor: self.callP2PEnableFor, callP2PDisableFor: self.callP2PDisableFor, callP2PEnableForCloseFriends: self.callP2PEnableForCloseFriends, callIntegrationAvailable: self.callIntegrationAvailable, callIntegrationEnabled: self.callIntegrationEnabled, phoneDiscoveryEnabled: self.phoneDiscoveryEnabled, hideReadTimeEnabled: hideReadTimeEnabled, uploadedPhoto: self.uploadedPhoto) } } @@ -820,12 +839,12 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present switch state.setting { case .everybody: - entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings))) + entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, enableForPremium: false, strings: presentationData.strings))) case .contacts: - entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings))) - entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings))) + entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, enableForPremium: false, strings: presentationData.strings))) + entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, enableForPremium: state.enableForPremium, strings: presentationData.strings))) case .nobody: - entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings))) + entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, enableForPremium: state.enableForPremium, strings: presentationData.strings))) } let exceptionsInfo: String if case .profilePhoto = kind { @@ -854,12 +873,12 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present if let callP2PMode = state.callP2PMode, let disableFor = state.callP2PDisableFor, let enableFor = state.callP2PEnableFor { switch callP2PMode { case .everybody: - entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor, strings: presentationData.strings))) + entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor, enableForPremium: false, strings: presentationData.strings))) case .contacts: - entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor, strings: presentationData.strings))) - entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor, strings: presentationData.strings))) + entries.append(.callsP2PDisableFor(presentationData.theme, disableForText, stringForUserCount(disableFor, enableForPremium: false, strings: presentationData.strings))) + entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor, enableForPremium: false, strings: presentationData.strings))) case .nobody: - entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor, strings: presentationData.strings))) + entries.append(.callsP2PEnableFor(presentationData.theme, enableForText, stringForUserCount(enableFor, enableForPremium: false, strings: presentationData.strings))) } } entries.append(.callsP2PPeersInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp)) @@ -909,6 +928,26 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present return entries } +func generatePremiumCategoryIcon(size: CGSize, cornerRadius: CGFloat) -> UIImage { + return generateImage(size, contextGenerator: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius) + context.addPath(path.cgPath) + context.clip() + + if let gradientImage = UIImage(bundleImageName: "Premium/PremiumIconBackground"), let cgImage = gradientImage.cgImage { + context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) + } + + if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/ButtonIcon"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage { + let imageSize = image.size.aspectFitted(CGSize(width: floor(size.width * 0.6), height: floor(size.height * 0.6))) + context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - imageSize.width) / 2.0), y: floorToScreenPixels((bounds.height - imageSize.height) / 2.0)), size: imageSize)) + } + })! +} + func selectivePrivacySettingsController( context: AccountContext, kind: SelectivePrivacySettingsKind, @@ -925,15 +964,18 @@ func selectivePrivacySettingsController( let strings = context.sharedContext.currentPresentationData.with { $0 }.strings var initialEnableFor: [EnginePeer.Id: SelectivePrivacyPeer] = [:] + var initialEnableForPremium = false var initialDisableFor: [EnginePeer.Id: SelectivePrivacyPeer] = [:] var initialEnableForCloseFriends = false switch current { - case let .disableEveryone(enableFor, enableForCloseFriends): + case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremiumValue): initialEnableFor = enableFor initialEnableForCloseFriends = enableForCloseFriends - case let .enableContacts(enableFor, disableFor): + initialEnableForPremium = enableForPremiumValue + case let .enableContacts(enableFor, disableFor, enableForPremiumValue): initialEnableFor = enableFor initialDisableFor = disableFor + initialEnableForPremium = enableForPremiumValue case let .enableEveryone(disableFor): initialDisableFor = disableFor } @@ -942,11 +984,11 @@ func selectivePrivacySettingsController( var initialCallEnableForCloseFriends = false if let callCurrent = callSettings?.0 { switch callCurrent { - case let .disableEveryone(enableFor, enableForCloseFriends): + case let .disableEveryone(enableFor, enableForCloseFriends, _): initialCallP2PEnableFor = enableFor initialCallP2PDisableFor = [:] initialCallEnableForCloseFriends = enableForCloseFriends - case let .enableContacts(enableFor, disableFor): + case let .enableContacts(enableFor, disableFor, _): initialCallP2PEnableFor = enableFor initialCallP2PDisableFor = disableFor case let .enableEveryone(disableFor): @@ -956,7 +998,7 @@ func selectivePrivacySettingsController( } //TODO:replace hideReadTimeEnabled with actual value - let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, disableFor: initialDisableFor, enableForCloseFriends: initialEnableForCloseFriends, saving: false, callDataSaving: callSettings?.1.dataSaving, callP2PMode: callSettings != nil ? SelectivePrivacySettingType(callSettings!.0) : nil, callP2PEnableFor: initialCallP2PEnableFor, callP2PDisableFor: initialCallP2PDisableFor, callP2PEnableForCloseFriends: initialCallEnableForCloseFriends, callIntegrationAvailable: callIntegrationAvailable, callIntegrationEnabled: callSettings?.1.enableSystemIntegration, phoneDiscoveryEnabled: phoneDiscoveryEnabled, hideReadTimeEnabled: globalSettings?.hideReadTime, uploadedPhoto: nil) + let initialState = SelectivePrivacySettingsControllerState(setting: SelectivePrivacySettingType(current), enableFor: initialEnableFor, enableForPremium: initialEnableForPremium, disableFor: initialDisableFor, enableForCloseFriends: initialEnableForCloseFriends, saving: false, callDataSaving: callSettings?.1.dataSaving, callP2PMode: callSettings != nil ? SelectivePrivacySettingType(callSettings!.0) : nil, callP2PEnableFor: initialCallP2PEnableFor, callP2PDisableFor: initialCallP2PDisableFor, callP2PEnableForCloseFriends: initialCallEnableForCloseFriends, callIntegrationAvailable: callIntegrationAvailable, callIntegrationEnabled: callSettings?.1.enableSystemIntegration, phoneDiscoveryEnabled: phoneDiscoveryEnabled, hideReadTimeEnabled: globalSettings?.hideReadTime, uploadedPhoto: nil) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -1040,17 +1082,55 @@ func selectivePrivacySettingsController( } return state } - if peerIds.isEmpty { - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: false), options: [])) + if peerIds.isEmpty && !stateValue.with({ $0 }).enableForPremium { + enum AdditionalCategoryId: Int { + case premiumUsers + } + + var displayPremiumCategory = false + switch kind { + case .groupInvitations: + displayPremiumCategory = true + default: + break + } + + //TODO:localize + var additionalCategories: [ChatListNodeAdditionalCategory] = [] + + if displayPremiumCategory && enable { + additionalCategories = [ + ChatListNodeAdditionalCategory( + id: AdditionalCategoryId.premiumUsers.rawValue, + icon: generatePremiumCategoryIcon(size: CGSize(width: 40.0, height: 40.0), cornerRadius: 12.0), + smallIcon: generatePremiumCategoryIcon(size: CGSize(width: 22.0, height: 22.0), cornerRadius: 6.0), + title: "Premium Users", + appearance: .option(sectionTitle: "USER TYPES") + ) + ] + } + let selectedCategories = Set() + + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection( + title: "Add Users", + searchPlaceholder: "Search users and groups", + selectedChats: Set(), + additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), + chatListFilters: nil, + onlyUsers: false, + disableChannels: true + )), options: [], filters: [.excludeSelf])) addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] result in var peerIds: [ContactListPeerId] = [] - if case let .result(peerIdsValue, _) = result { + var premiumSelected = false + if case let .result(peerIdsValue, additionalOptionIds) = result { peerIds = peerIdsValue + premiumSelected = additionalOptionIds.contains(AdditionalCategoryId.premiumUsers.rawValue) } - if peerIds.isEmpty { + if peerIds.isEmpty && !premiumSelected { controller?.dismiss() return } @@ -1100,6 +1180,7 @@ func selectivePrivacySettingsController( disableFor.removeValue(forKey: key) } return state.withUpdatedEnableFor(updatedPeerIds).withUpdatedDisableFor(disableFor) + .withUpdatedEnableForPremium(premiumSelected ? true : state.enableForPremium) case .callP2P: var callP2PDisableFor = state.callP2PDisableFor ?? [:] for (key, _) in updatedPeerIds { @@ -1128,7 +1209,17 @@ func selectivePrivacySettingsController( })) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) } else { - let controller = selectivePrivacyPeersController(context: context, title: title, initialPeers: peerIds, updated: { updatedPeerIds in + var displayPremiumCategory = false + switch kind { + case .groupInvitations: + if enable { + displayPremiumCategory = true + } + default: + break + } + + let controller = selectivePrivacyPeersController(context: context, title: title, initialPeers: peerIds, initialEnableForPremium: stateValue.with({ $0 }).enableForPremium, displayPremiumCategory: displayPremiumCategory, updated: { updatedPeerIds, enableForPremium in updateState { state in if enable { switch target { @@ -1137,7 +1228,7 @@ func selectivePrivacySettingsController( for (key, _) in updatedPeerIds { disableFor.removeValue(forKey: key) } - return state.withUpdatedEnableFor(updatedPeerIds).withUpdatedDisableFor(disableFor) + return state.withUpdatedEnableFor(updatedPeerIds).withUpdatedDisableFor(disableFor).withUpdatedEnableForPremium(enableForPremium) case .callP2P: var callP2PDisableFor = state.callP2PDisableFor ?? [:] for (key, _) in updatedPeerIds { @@ -1312,9 +1403,9 @@ func selectivePrivacySettingsController( case .everybody: settings = SelectivePrivacySettings.enableEveryone(disableFor: state.disableFor) case .contacts: - settings = SelectivePrivacySettings.enableContacts(enableFor: state.enableFor, disableFor: state.disableFor) + settings = SelectivePrivacySettings.enableContacts(enableFor: state.enableFor, disableFor: state.disableFor, enableForPremium: state.enableForPremium) case .nobody: - settings = SelectivePrivacySettings.disableEveryone(enableFor: state.enableFor, enableForCloseFriends: state.enableForCloseFriends) + settings = SelectivePrivacySettings.disableEveryone(enableFor: state.enableFor, enableForCloseFriends: state.enableForCloseFriends, enableForPremium: state.enableForPremium) } if case .phoneNumber = kind, let value = state.phoneDiscoveryEnabled { @@ -1330,9 +1421,9 @@ func selectivePrivacySettingsController( case .everybody: callP2PSettings = SelectivePrivacySettings.enableEveryone(disableFor: disableFor) case .contacts: - callP2PSettings = SelectivePrivacySettings.enableContacts(enableFor: enableFor, disableFor: disableFor) + callP2PSettings = SelectivePrivacySettings.enableContacts(enableFor: enableFor, disableFor: disableFor, enableForPremium: false) case .nobody: - callP2PSettings = SelectivePrivacySettings.disableEveryone(enableFor: enableFor, enableForCloseFriends: enableForCloseFriends) + callP2PSettings = SelectivePrivacySettings.disableEveryone(enableFor: enableFor, enableForCloseFriends: enableForCloseFriends, enableForPremium: false) } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index 4d1d743535..e67e6300de 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -19,14 +19,16 @@ private final class SelectivePrivacyPeersControllerArguments { let addPeer: () -> Void let openPeer: (EnginePeer) -> Void let deleteAll: () -> Void + let removePremiumUsers: () -> Void - init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, deleteAll: @escaping () -> Void) { + init(context: AccountContext, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, addPeer: @escaping () -> Void, openPeer: @escaping (EnginePeer) -> Void, deleteAll: @escaping () -> Void, removePremiumUsers: @escaping () -> Void) { self.context = context self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.removePeer = removePeer self.addPeer = addPeer self.openPeer = openPeer self.deleteAll = deleteAll + self.removePremiumUsers = removePremiumUsers } } @@ -38,19 +40,25 @@ private enum SelectivePrivacyPeersSection: Int32 { private enum SelectivePrivacyPeersEntryStableId: Hashable { case header case add + case premiumUsers case peer(EnginePeer.Id) case delete } +private let premiumAvatarIcon: UIImage? = { + return generatePremiumCategoryIcon(size: CGSize(width: 31.0, height: 31.0), cornerRadius: 8.0) +}() + private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { - case peerItem(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, SelectivePrivacyPeer, ItemListPeerItemEditing, Bool) - case addItem(PresentationTheme, String, Bool) - case headerItem(PresentationTheme, String) - case deleteItem(PresentationTheme, String) + case premiumUsersItem(ItemListPeerItemEditing, Bool) + case peerItem(Int32, PresentationDateTimeFormat, PresentationPersonNameOrder, SelectivePrivacyPeer, ItemListPeerItemEditing, Bool) + case addItem(String, Bool) + case headerItem(String) + case deleteItem(String) var section: ItemListSectionId { switch self { - case .addItem, .peerItem, .headerItem: + case .addItem, .premiumUsersItem, .peerItem, .headerItem: return SelectivePrivacyPeersSection.peers.rawValue case .deleteItem: return SelectivePrivacyPeersSection.delete.rawValue @@ -59,7 +67,9 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { var stableId: SelectivePrivacyPeersEntryStableId { switch self { - case let .peerItem(_, _, _, _, _, peer, _, _): + case .premiumUsersItem: + return .premiumUsers + case let .peerItem(_, _, _, peer, _, _): return .peer(peer.peer.id) case .addItem: return .add @@ -72,20 +82,20 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { static func ==(lhs: SelectivePrivacyPeersEntry, rhs: SelectivePrivacyPeersEntry) -> Bool { switch lhs { - case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsEditing, lhsEnabled): - if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsEditing, rhsEnabled) = rhs { + case let .premiumUsersItem(editing, isEnabled): + if case .premiumUsersItem(editing, isEnabled) = rhs { + return true + } else { + return false + } + case let .peerItem(lhsIndex, lhsDateTimeFormat, lhsNameOrder, lhsPeer, lhsEditing, lhsEnabled): + if case let .peerItem(rhsIndex, rhsDateTimeFormat, rhsNameOrder, rhsPeer, rhsEditing, rhsEnabled) = rhs { if lhsIndex != rhsIndex { return false } if lhsPeer != rhsPeer { return false } - if lhsTheme !== rhsTheme { - return false - } - if lhsStrings !== rhsStrings { - return false - } if lhsDateTimeFormat != rhsDateTimeFormat { return false } @@ -102,20 +112,20 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { } else { return false } - case let .addItem(lhsTheme, lhsText, lhsEditing): - if case let .addItem(rhsTheme, rhsText, rhsEditing) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEditing == rhsEditing { + case let .addItem(lhsText, lhsEditing): + if case let .addItem(rhsText, rhsEditing) = rhs, lhsText == rhsText, lhsEditing == rhsEditing { return true } else { return false } - case let .headerItem(lhsTheme, lhsText): - if case let .headerItem(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .headerItem(lhsText): + if case let .headerItem(rhsText) = rhs, lhsText == rhsText { return true } else { return false } - case let .deleteItem(lhsTheme, lhsText): - if case let .deleteItem(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + case let .deleteItem(lhsText): + if case let .deleteItem(rhsText) = rhs, lhsText == rhsText { return true } else { return false @@ -125,47 +135,67 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { static func <(lhs: SelectivePrivacyPeersEntry, rhs: SelectivePrivacyPeersEntry) -> Bool { switch lhs { + case .deleteItem: + return false + case let .peerItem(index, _, _, _, _, _): + switch rhs { case .deleteItem: - return false - case let .peerItem(index, _, _, _, _, _, _, _): - switch rhs { - case .deleteItem: - return true - case let .peerItem(rhsIndex, _, _, _, _, _, _, _): - return index < rhsIndex - case .addItem, .headerItem: - return false - } - case .addItem: - switch rhs { - case .peerItem, .deleteItem: - return true - case .headerItem: - return false - default: - return false - } - case .headerItem: return true + case let .peerItem(rhsIndex, _, _, _, _, _): + return index < rhsIndex + case .addItem, .headerItem, .premiumUsersItem: + return false + } + case .premiumUsersItem: + switch rhs { + case .peerItem, .deleteItem: + return true + case .premiumUsersItem, .addItem, .headerItem: + return false + } + case .addItem: + switch rhs { + case .peerItem, .deleteItem, .premiumUsersItem: + return true + case .addItem, .headerItem: + return false + } + case .headerItem: + switch rhs { + case .peerItem, .deleteItem, .premiumUsersItem, .addItem: + return true + case .headerItem: + return false + } } } func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! SelectivePrivacyPeersControllerArguments switch self { - case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): + case let .premiumUsersItem(editing, enabled): + //TODO:localize + let peer: EnginePeer = .user(TelegramUser( + id: EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value(1)), accessHash: nil, firstName: "Premium Users", lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)) + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, customAvatarIcon: premiumAvatarIcon, presence: nil, text: .none, label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: { + }, setPeerIdWithRevealedOptions: { previousId, id in + arguments.setPeerIdWithRevealedOptions(previousId, id) + }, removePeer: { peerId in + arguments.removePremiumUsers() + }) + case let .peerItem(_, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): var text: ItemListPeerItemText = .none if let group = peer.peer as? TelegramGroup { - text = .text(strings.Conversation_StatusMembers(Int32(group.participantCount)), .secondary) + text = .text(presentationData.strings.Conversation_StatusMembers(Int32(group.participantCount)), .secondary) } else if let channel = peer.peer as? TelegramChannel { if let participantCount = peer.participantCount { - text = .text(strings.Conversation_StatusMembers(Int32(participantCount)), .secondary) + text = .text(presentationData.strings.Conversation_StatusMembers(Int32(participantCount)), .secondary) } else { switch channel.info { case .group: - text = .text(strings.Group_Status, .secondary) + text = .text(presentationData.strings.Group_Status, .secondary) case .broadcast: - text = .text(strings.Channel_Status, .secondary) + text = .text(presentationData.strings.Channel_Status, .secondary) } } } @@ -176,13 +206,13 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { }, removePeer: { peerId in arguments.removePeer(peerId) }) - case let .addItem(theme, text, editing): - return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(theme), title: text, sectionId: self.section, height: .compactPeerList, editing: editing, action: { + case let .addItem(text, editing): + return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.plusIconImage(presentationData.theme), title: text, sectionId: self.section, height: .compactPeerList, editing: editing, action: { arguments.addPeer() }) - case let .headerItem(_, text): + case let .headerItem(text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) - case let .deleteItem(_, text): + case let .deleteItem(text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .center, sectionId: self.section, style: .blocks, action: { arguments.deleteAll() }) @@ -191,36 +221,15 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { } private struct SelectivePrivacyPeersControllerState: Equatable { - let editing: Bool - let peerIdWithRevealedOptions: EnginePeer.Id? + var enableForPremium: Bool + var editing: Bool + var peerIdWithRevealedOptions: EnginePeer.Id? - init() { - self.editing = false - self.peerIdWithRevealedOptions = nil - } - - init(editing: Bool, peerIdWithRevealedOptions: EnginePeer.Id?) { + init(enableForPremium: Bool, editing: Bool, peerIdWithRevealedOptions: EnginePeer.Id?) { + self.enableForPremium = enableForPremium self.editing = editing self.peerIdWithRevealedOptions = peerIdWithRevealedOptions } - - static func ==(lhs: SelectivePrivacyPeersControllerState, rhs: SelectivePrivacyPeersControllerState) -> Bool { - if lhs.editing != rhs.editing { - return false - } - if lhs.peerIdWithRevealedOptions != rhs.peerIdWithRevealedOptions { - return false - } - return true - } - - func withUpdatedEditing(_ editing: Bool) -> SelectivePrivacyPeersControllerState { - return SelectivePrivacyPeersControllerState(editing: editing, peerIdWithRevealedOptions: self.peerIdWithRevealedOptions) - } - - func withUpdatedPeerIdWithRevealedOptions(_ peerIdWithRevealedOptions: EnginePeer.Id?) -> SelectivePrivacyPeersControllerState { - return SelectivePrivacyPeersControllerState(editing: self.editing, peerIdWithRevealedOptions: peerIdWithRevealedOptions) - } } private func selectivePrivacyPeersControllerEntries(presentationData: PresentationData, state: SelectivePrivacyPeersControllerState, peers: [SelectivePrivacyPeer]) -> [SelectivePrivacyPeersEntry] { @@ -232,25 +241,30 @@ private func selectivePrivacyPeersControllerEntries(presentationData: Presentati } else { title = presentationData.strings.Privacy_ExceptionsCount(Int32(peers.count)) } - entries.append(.headerItem(presentationData.theme, title)) - entries.append(.addItem(presentationData.theme, presentationData.strings.Privacy_AddNewPeer, state.editing)) + entries.append(.headerItem(title)) + entries.append(.addItem(presentationData.strings.Privacy_AddNewPeer, state.editing)) + + if state.enableForPremium { + entries.append(.premiumUsersItem(ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: state.peerIdWithRevealedOptions?.id._internalGetInt64Value() == 1), true)) + } var index: Int32 = 0 for peer in peers { - entries.append(.peerItem(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.peer.id == state.peerIdWithRevealedOptions), true)) + entries.append(.peerItem(index, presentationData.dateTimeFormat, presentationData.nameDisplayOrder, peer, ItemListPeerItemEditing(editable: true, editing: state.editing, revealed: peer.peer.id == state.peerIdWithRevealedOptions), true)) index += 1 } if !peers.isEmpty { - entries.append(.deleteItem(presentationData.theme, presentationData.strings.Privacy_Exceptions_DeleteAllExceptions)) + entries.append(.deleteItem(presentationData.strings.Privacy_Exceptions_DeleteAllExceptions)) } return entries } -public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer]) -> Void) -> ViewController { - let statePromise = ValuePromise(SelectivePrivacyPeersControllerState(), ignoreRepeated: true) - let stateValue = Atomic(value: SelectivePrivacyPeersControllerState()) +public func selectivePrivacyPeersController(context: AccountContext, title: String, initialPeers: [EnginePeer.Id: SelectivePrivacyPeer], initialEnableForPremium: Bool, displayPremiumCategory: Bool, updated: @escaping ([EnginePeer.Id: SelectivePrivacyPeer], Bool) -> Void) -> ViewController { + let initialState = SelectivePrivacyPeersControllerState(enableForPremium: initialEnableForPremium, editing: false, peerIdWithRevealedOptions: nil) + let statePromise = ValuePromise(initialState, ignoreRepeated: true) + let stateValue = Atomic(value: initialState) let updateState: ((SelectivePrivacyPeersControllerState) -> SelectivePrivacyPeersControllerState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } @@ -273,7 +287,9 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri let arguments = SelectivePrivacyPeersControllerArguments(context: context, setPeerIdWithRevealedOptions: { peerId, fromPeerId in updateState { state in if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) { - return state.withUpdatedPeerIdWithRevealedOptions(peerId) + var state = state + state.peerIdWithRevealedOptions = peerId + return state } else { return state } @@ -296,7 +312,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri for peer in updatedPeers { updatedPeerDict[peer.peer.id] = peer } - updated(updatedPeerDict) + updated(updatedPeerDict, stateValue.with({ $0 }).enableForPremium) if updatedPeerDict.isEmpty { dismissImpl?() @@ -307,13 +323,48 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri removePeerDisposable.set(applyPeers.start()) }, addPeer: { - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .peerSelection(searchChatList: true, searchGroups: true, searchChannels: false), options: [])) + enum AdditionalCategoryId: Int { + case premiumUsers + } + + //TODO:localize + var additionalCategories: [ChatListNodeAdditionalCategory] = [] + + if displayPremiumCategory { + additionalCategories = [ + ChatListNodeAdditionalCategory( + id: AdditionalCategoryId.premiumUsers.rawValue, + icon: generatePremiumCategoryIcon(size: CGSize(width: 40.0, height: 40.0), cornerRadius: 12.0), + smallIcon: generatePremiumCategoryIcon(size: CGSize(width: 22.0, height: 22.0), cornerRadius: 6.0), + title: "Premium Users", + appearance: .option(sectionTitle: "USER TYPES") + ) + ] + } + var selectedCategories = Set() + if stateValue.with({ $0 }).enableForPremium { + selectedCategories.insert(AdditionalCategoryId.premiumUsers.rawValue) + } + + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection( + title: "Add Users", + searchPlaceholder: "Search users and groups", + selectedChats: Set(), + additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), + chatListFilters: nil, + onlyUsers: false, + disableChannels: true + )), options: [])) addPeerDisposable.set((controller.result |> take(1) |> deliverOnMainQueue).start(next: { [weak controller] result in var peerIds: [ContactListPeerId] = [] - if case let .result(peerIdsValue, _) = result { + var premiumSelected = false + if case let .result(peerIdsValue, additionalOptionIds) = result { peerIds = peerIdsValue + premiumSelected = additionalOptionIds.contains(AdditionalCategoryId.premiumUsers.rawValue) + } else { + return } let applyPeers: Signal = peersPromise.get() @@ -360,7 +411,13 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri for peer in updatedPeers { updatedPeerDict[peer.peer.id] = peer } - updated(updatedPeerDict) + updated(updatedPeerDict, premiumSelected) + + updateState { state in + var state = state + state.enableForPremium = premiumSelected + return state + } return .complete() } @@ -386,8 +443,14 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri |> take(1) |> deliverOnMainQueue |> mapToSignal { _ -> Signal in + updateState { state in + var state = state + state.enableForPremium = false + return state + } + peersPromise.set(.single([])) - updated([:]) + updated([:], false) dismissImpl?() @@ -402,6 +465,33 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri }) ])]) presentControllerImpl?(actionSheet, nil) + }, removePremiumUsers: { + updateState { state in + var state = state + state.enableForPremium = false + return state + } + let applyPeers: Signal = peersPromise.get() + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { peers -> Signal in + let updatedPeers = peers + peersPromise.set(.single(updatedPeers)) + + var updatedPeerDict: [EnginePeer.Id: SelectivePrivacyPeer] = [:] + for peer in updatedPeers { + updatedPeerDict[peer.peer.id] = peer + } + updated(updatedPeerDict, false) + + if updatedPeerDict.isEmpty && !stateValue.with({ $0 }).enableForPremium { + dismissImpl?() + } + + return .complete() + } + + removePeerDisposable.set(applyPeers.start()) }) var previousPeers: [SelectivePrivacyPeer]? @@ -414,13 +504,17 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri if state.editing { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: { updateState { state in - return state.withUpdatedEditing(false) + var state = state + state.editing = false + return state } }) } else { rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Edit), style: .regular, enabled: true, action: { updateState { state in - return state.withUpdatedEditing(true) + var state = state + state.editing = true + return state } }) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index d56f330c35..155a951c3a 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -109,6 +109,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-867328308] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleCustom($0) } dict[-1007487743] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleOutsideWorkHours($0) } dict[-451302485] = { return Api.BusinessGreetingMessage.parse_businessGreetingMessage($0) } + dict[1510606445] = { return Api.BusinessIntro.parse_businessIntro($0) } dict[-1403249929] = { return Api.BusinessLocation.parse_businessLocation($0) } dict[554733559] = { return Api.BusinessRecipients.parse_businessRecipients($0) } dict[302717625] = { return Api.BusinessWeeklyOpen.parse_businessWeeklyOpen($0) } @@ -312,6 +313,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1462213465] = { return Api.InputBotInlineResult.parse_inputBotInlineResultPhoto($0) } dict[-2094959136] = { return Api.InputBusinessAwayMessage.parse_inputBusinessAwayMessage($0) } dict[26528571] = { return Api.InputBusinessGreetingMessage.parse_inputBusinessGreetingMessage($0) } + dict[163867085] = { return Api.InputBusinessIntro.parse_inputBusinessIntro($0) } dict[1871393450] = { return Api.InputBusinessRecipients.parse_inputBusinessRecipients($0) } dict[-212145112] = { return Api.InputChannel.parse_inputChannel($0) } dict[-292807034] = { return Api.InputChannel.parse_inputChannelEmpty($0) } @@ -889,13 +891,13 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[299870598] = { return Api.Update.parse_updateBotChatInviteRequester($0) } dict[1299263278] = { return Api.Update.parse_updateBotCommands($0) } dict[-1607821266] = { return Api.Update.parse_updateBotDeleteBusinessMessage($0) } - dict[1420915171] = { return Api.Update.parse_updateBotEditBusinessMessage($0) } + dict[132077692] = { return Api.Update.parse_updateBotEditBusinessMessage($0) } dict[1232025500] = { return Api.Update.parse_updateBotInlineQuery($0) } dict[317794823] = { return Api.Update.parse_updateBotInlineSend($0) } dict[347625491] = { return Api.Update.parse_updateBotMenuButton($0) } dict[-1407069234] = { return Api.Update.parse_updateBotMessageReaction($0) } dict[164329305] = { return Api.Update.parse_updateBotMessageReactions($0) } - dict[-2142069794] = { return Api.Update.parse_updateBotNewBusinessMessage($0) } + dict[-1646578564] = { return Api.Update.parse_updateBotNewBusinessMessage($0) } dict[-1934976362] = { return Api.Update.parse_updateBotPrecheckoutQuery($0) } dict[-1246823043] = { return Api.Update.parse_updateBotShippingQuery($0) } dict[-997782967] = { return Api.Update.parse_updateBotStopped($0) } @@ -1028,7 +1030,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[559694904] = { return Api.User.parse_user($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) } - dict[587153029] = { return Api.UserFull.parse_userFull($0) } + dict[1728822428] = { return Api.UserFull.parse_userFull($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } @@ -1419,6 +1421,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.BusinessGreetingMessage: _1.serialize(buffer, boxed) + case let _1 as Api.BusinessIntro: + _1.serialize(buffer, boxed) case let _1 as Api.BusinessLocation: _1.serialize(buffer, boxed) case let _1 as Api.BusinessRecipients: @@ -1581,6 +1585,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.InputBusinessGreetingMessage: _1.serialize(buffer, boxed) + case let _1 as Api.InputBusinessIntro: + _1.serialize(buffer, boxed) case let _1 as Api.InputBusinessRecipients: _1.serialize(buffer, boxed) case let _1 as Api.InputChannel: diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index 9436ea5239..1ba8861e64 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -750,6 +750,56 @@ public extension Api { } } +public extension Api { + enum BusinessIntro: TypeConstructorDescription { + case businessIntro(flags: Int32, title: String, description: String, sticker: Api.Document?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .businessIntro(let flags, let title, let description, let sticker): + if boxed { + buffer.appendInt32(1510606445) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {sticker!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .businessIntro(let flags, let title, let description, let sticker): + return ("businessIntro", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("sticker", sticker as Any)]) + } + } + + public static func parse_businessIntro(_ reader: BufferReader) -> BusinessIntro? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Api.Document? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Document + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.BusinessIntro.businessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4) + } + else { + return nil + } + } + + } +} public extension Api { enum BusinessLocation: TypeConstructorDescription { case businessLocation(flags: Int32, geoPoint: Api.GeoPoint?, address: String) diff --git a/submodules/TelegramApi/Sources/Api22.swift b/submodules/TelegramApi/Sources/Api22.swift index 7400b61fc3..56c8232eeb 100644 --- a/submodules/TelegramApi/Sources/Api22.swift +++ b/submodules/TelegramApi/Sources/Api22.swift @@ -514,13 +514,13 @@ public extension Api { case updateBotChatInviteRequester(peer: Api.Peer, date: Int32, userId: Int64, about: String, invite: Api.ExportedChatInvite, qts: Int32) case updateBotCommands(peer: Api.Peer, botId: Int64, commands: [Api.BotCommand]) case updateBotDeleteBusinessMessage(connectionId: String, peer: Api.Peer, messages: [Int32], qts: Int32) - case updateBotEditBusinessMessage(connectionId: String, message: Api.Message, qts: Int32) + case updateBotEditBusinessMessage(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) case updateBotInlineQuery(flags: Int32, queryId: Int64, userId: Int64, query: String, geo: Api.GeoPoint?, peerType: Api.InlineQueryPeerType?, offset: String) case updateBotInlineSend(flags: Int32, userId: Int64, query: String, geo: Api.GeoPoint?, id: String, msgId: Api.InputBotInlineMessageID?) case updateBotMenuButton(botId: Int64, button: Api.BotMenuButton) case updateBotMessageReaction(peer: Api.Peer, msgId: Int32, date: Int32, actor: Api.Peer, oldReactions: [Api.Reaction], newReactions: [Api.Reaction], qts: Int32) case updateBotMessageReactions(peer: Api.Peer, msgId: Int32, date: Int32, reactions: [Api.ReactionCount], qts: Int32) - case updateBotNewBusinessMessage(connectionId: String, message: Api.Message, qts: Int32) + case updateBotNewBusinessMessage(flags: Int32, connectionId: String, message: Api.Message, replyToMessage: Api.Message?, qts: Int32) case updateBotPrecheckoutQuery(flags: Int32, queryId: Int64, userId: Int64, payload: Buffer, info: Api.PaymentRequestedInfo?, shippingOptionId: String?, currency: String, totalAmount: Int64) case updateBotShippingQuery(queryId: Int64, userId: Int64, payload: Buffer, shippingAddress: Api.PostAddress) case updateBotStopped(userId: Int64, date: Int32, stopped: Api.Bool, qts: Int32) @@ -720,12 +720,14 @@ public extension Api { } serializeInt32(qts, buffer: buffer, boxed: false) break - case .updateBotEditBusinessMessage(let connectionId, let message, let qts): + case .updateBotEditBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): if boxed { - buffer.appendInt32(1420915171) + buffer.appendInt32(132077692) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeString(connectionId, buffer: buffer, boxed: false) message.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {replyToMessage!.serialize(buffer, true)} serializeInt32(qts, buffer: buffer, boxed: false) break case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset): @@ -792,12 +794,14 @@ public extension Api { } serializeInt32(qts, buffer: buffer, boxed: false) break - case .updateBotNewBusinessMessage(let connectionId, let message, let qts): + case .updateBotNewBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): if boxed { - buffer.appendInt32(-2142069794) + buffer.appendInt32(-1646578564) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeString(connectionId, buffer: buffer, boxed: false) message.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {replyToMessage!.serialize(buffer, true)} serializeInt32(qts, buffer: buffer, boxed: false) break case .updateBotPrecheckoutQuery(let flags, let queryId, let userId, let payload, let info, let shippingOptionId, let currency, let totalAmount): @@ -1830,8 +1834,8 @@ public extension Api { return ("updateBotCommands", [("peer", peer as Any), ("botId", botId as Any), ("commands", commands as Any)]) case .updateBotDeleteBusinessMessage(let connectionId, let peer, let messages, let qts): return ("updateBotDeleteBusinessMessage", [("connectionId", connectionId as Any), ("peer", peer as Any), ("messages", messages as Any), ("qts", qts as Any)]) - case .updateBotEditBusinessMessage(let connectionId, let message, let qts): - return ("updateBotEditBusinessMessage", [("connectionId", connectionId as Any), ("message", message as Any), ("qts", qts as Any)]) + case .updateBotEditBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): + return ("updateBotEditBusinessMessage", [("flags", flags as Any), ("connectionId", connectionId as Any), ("message", message as Any), ("replyToMessage", replyToMessage as Any), ("qts", qts as Any)]) case .updateBotInlineQuery(let flags, let queryId, let userId, let query, let geo, let peerType, let offset): return ("updateBotInlineQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("query", query as Any), ("geo", geo as Any), ("peerType", peerType as Any), ("offset", offset as Any)]) case .updateBotInlineSend(let flags, let userId, let query, let geo, let id, let msgId): @@ -1842,8 +1846,8 @@ public extension Api { return ("updateBotMessageReaction", [("peer", peer as Any), ("msgId", msgId as Any), ("date", date as Any), ("actor", actor as Any), ("oldReactions", oldReactions as Any), ("newReactions", newReactions as Any), ("qts", qts as Any)]) case .updateBotMessageReactions(let peer, let msgId, let date, let reactions, let qts): return ("updateBotMessageReactions", [("peer", peer as Any), ("msgId", msgId as Any), ("date", date as Any), ("reactions", reactions as Any), ("qts", qts as Any)]) - case .updateBotNewBusinessMessage(let connectionId, let message, let qts): - return ("updateBotNewBusinessMessage", [("connectionId", connectionId as Any), ("message", message as Any), ("qts", qts as Any)]) + case .updateBotNewBusinessMessage(let flags, let connectionId, let message, let replyToMessage, let qts): + return ("updateBotNewBusinessMessage", [("flags", flags as Any), ("connectionId", connectionId as Any), ("message", message as Any), ("replyToMessage", replyToMessage as Any), ("qts", qts as Any)]) case .updateBotPrecheckoutQuery(let flags, let queryId, let userId, let payload, let info, let shippingOptionId, let currency, let totalAmount): return ("updateBotPrecheckoutQuery", [("flags", flags as Any), ("queryId", queryId as Any), ("userId", userId as Any), ("payload", payload as Any), ("info", info as Any), ("shippingOptionId", shippingOptionId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any)]) case .updateBotShippingQuery(let queryId, let userId, let payload, let shippingAddress): @@ -2240,19 +2244,27 @@ public extension Api { } } public static func parse_updateBotEditBusinessMessage(_ reader: BufferReader) -> Update? { - var _1: String? - _1 = parseString(reader) - var _2: Api.Message? + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.Message? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Message + _3 = Api.parse(reader, signature: signature) as? Api.Message } - var _3: Int32? - _3 = reader.readInt32() + var _4: Api.Message? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Message + } } + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotEditBusinessMessage(connectionId: _1!, message: _2!, qts: _3!) + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotEditBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) } else { return nil @@ -2402,19 +2414,27 @@ public extension Api { } } public static func parse_updateBotNewBusinessMessage(_ reader: BufferReader) -> Update? { - var _1: String? - _1 = parseString(reader) - var _2: Api.Message? + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: Api.Message? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.Message + _3 = Api.parse(reader, signature: signature) as? Api.Message } - var _3: Int32? - _3 = reader.readInt32() + var _4: Api.Message? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.Message + } } + var _5: Int32? + _5 = reader.readInt32() let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.Update.updateBotNewBusinessMessage(connectionId: _1!, message: _2!, qts: _3!) + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.Update.updateBotNewBusinessMessage(flags: _1!, connectionId: _2!, message: _3!, replyToMessage: _4, qts: _5!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 12f4890d7c..44916f8cdd 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -602,13 +602,13 @@ public extension Api { } public extension Api { enum UserFull: TypeConstructorDescription { - case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?) + case userFull(flags: Int32, flags2: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?, stories: Api.PeerStories?, businessWorkHours: Api.BusinessWorkHours?, businessLocation: Api.BusinessLocation?, businessGreetingMessage: Api.BusinessGreetingMessage?, businessAwayMessage: Api.BusinessAwayMessage?, businessIntro: Api.BusinessIntro?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage): + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro): if boxed { - buffer.appendInt32(587153029) + buffer.appendInt32(1728822428) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -639,14 +639,15 @@ public extension Api { if Int(flags2) & Int(1 << 1) != 0 {businessLocation!.serialize(buffer, true)} if Int(flags2) & Int(1 << 2) != 0 {businessGreetingMessage!.serialize(buffer, true)} if Int(flags2) & Int(1 << 3) != 0 {businessAwayMessage!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 4) != 0 {businessIntro!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage): - return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any)]) + case .userFull(let flags, let flags2, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper, let stories, let businessWorkHours, let businessLocation, let businessGreetingMessage, let businessAwayMessage, let businessIntro): + return ("userFull", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any), ("stories", stories as Any), ("businessWorkHours", businessWorkHours as Any), ("businessLocation", businessLocation as Any), ("businessGreetingMessage", businessGreetingMessage as Any), ("businessAwayMessage", businessAwayMessage as Any), ("businessIntro", businessIntro as Any)]) } } @@ -731,6 +732,10 @@ public extension Api { if Int(_2!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { _25 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessage } } + var _26: Api.BusinessIntro? + if Int(_2!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() { + _26 = Api.parse(reader, signature: signature) as? Api.BusinessIntro + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -756,8 +761,9 @@ public extension Api { let _c23 = (Int(_2!) & Int(1 << 1) == 0) || _23 != nil let _c24 = (Int(_2!) & Int(1 << 2) == 0) || _24 != nil let _c25 = (Int(_2!) & Int(1 << 3) == 0) || _25 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 { - return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, premiumGifts: _19, wallpaper: _20, stories: _21, businessWorkHours: _22, businessLocation: _23, businessGreetingMessage: _24, businessAwayMessage: _25) + let _c26 = (Int(_2!) & Int(1 << 4) == 0) || _26 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 { + return Api.UserFull.userFull(flags: _1!, flags2: _2!, id: _3!, about: _4, settings: _5!, personalPhoto: _6, profilePhoto: _7, fallbackPhoto: _8, notifySettings: _9!, botInfo: _10, pinnedMsgId: _11, commonChatsCount: _12!, folderId: _13, ttlPeriod: _14, themeEmoticon: _15, privateForwardName: _16, botGroupAdminRights: _17, botBroadcastAdminRights: _18, premiumGifts: _19, wallpaper: _20, stories: _21, businessWorkHours: _22, businessLocation: _23, businessGreetingMessage: _24, businessAwayMessage: _25, businessIntro: _26) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index 9338176e30..166574fc36 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -1354,6 +1354,22 @@ public extension Api.functions.account { }) } } +public extension Api.functions.account { + static func updateBusinessIntro(flags: Int32, intro: Api.InputBusinessIntro?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1508585420) + serializeInt32(flags, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {intro!.serialize(buffer, true)} + return (FunctionDescription(name: "account.updateBusinessIntro", parameters: [("flags", String(describing: flags)), ("intro", String(describing: intro))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.account { static func updateBusinessLocation(flags: Int32, geoPoint: Api.InputGeoPoint?, address: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 7ad68b056b..69840c12e6 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -360,6 +360,56 @@ public extension Api { } } +public extension Api { + enum InputBusinessIntro: TypeConstructorDescription { + case inputBusinessIntro(flags: Int32, title: String, description: String, sticker: Api.InputDocument?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputBusinessIntro(let flags, let title, let description, let sticker): + if boxed { + buffer.appendInt32(163867085) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {sticker!.serialize(buffer, true)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputBusinessIntro(let flags, let title, let description, let sticker): + return ("inputBusinessIntro", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("sticker", sticker as Any)]) + } + } + + public static func parse_inputBusinessIntro(_ reader: BufferReader) -> InputBusinessIntro? { + var _1: Int32? + _1 = reader.readInt32() + var _2: String? + _2 = parseString(reader) + var _3: String? + _3 = parseString(reader) + var _4: Api.InputDocument? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.InputDocument + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputBusinessIntro.inputBusinessIntro(flags: _1!, title: _2!, description: _3!, sticker: _4) + } + else { + return nil + } + } + + } +} public extension Api { enum InputBusinessRecipients: TypeConstructorDescription { case inputBusinessRecipients(flags: Int32, users: [Api.InputUser]?) @@ -1082,79 +1132,3 @@ public extension Api { } } -public extension Api { - enum InputFile: TypeConstructorDescription { - case inputFile(id: Int64, parts: Int32, name: String, md5Checksum: String) - case inputFileBig(id: Int64, parts: Int32, name: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .inputFile(let id, let parts, let name, let md5Checksum): - if boxed { - buffer.appendInt32(-181407105) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt32(parts, buffer: buffer, boxed: false) - serializeString(name, buffer: buffer, boxed: false) - serializeString(md5Checksum, buffer: buffer, boxed: false) - break - case .inputFileBig(let id, let parts, let name): - if boxed { - buffer.appendInt32(-95482955) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt32(parts, buffer: buffer, boxed: false) - serializeString(name, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .inputFile(let id, let parts, let name, let md5Checksum): - return ("inputFile", [("id", id as Any), ("parts", parts as Any), ("name", name as Any), ("md5Checksum", md5Checksum as Any)]) - case .inputFileBig(let id, let parts, let name): - return ("inputFileBig", [("id", id as Any), ("parts", parts as Any), ("name", name as Any)]) - } - } - - public static func parse_inputFile(_ reader: BufferReader) -> InputFile? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - var _4: String? - _4 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.InputFile.inputFile(id: _1!, parts: _2!, name: _3!, md5Checksum: _4!) - } - else { - return nil - } - } - public static func parse_inputFileBig(_ reader: BufferReader) -> InputFile? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: String? - _3 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputFile.inputFileBig(id: _1!, parts: _2!, name: _3!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api8.swift b/submodules/TelegramApi/Sources/Api8.swift index 3276fce952..7ae35b59dd 100644 --- a/submodules/TelegramApi/Sources/Api8.swift +++ b/submodules/TelegramApi/Sources/Api8.swift @@ -1,3 +1,79 @@ +public extension Api { + enum InputFile: TypeConstructorDescription { + case inputFile(id: Int64, parts: Int32, name: String, md5Checksum: String) + case inputFileBig(id: Int64, parts: Int32, name: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputFile(let id, let parts, let name, let md5Checksum): + if boxed { + buffer.appendInt32(-181407105) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(parts, buffer: buffer, boxed: false) + serializeString(name, buffer: buffer, boxed: false) + serializeString(md5Checksum, buffer: buffer, boxed: false) + break + case .inputFileBig(let id, let parts, let name): + if boxed { + buffer.appendInt32(-95482955) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt32(parts, buffer: buffer, boxed: false) + serializeString(name, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputFile(let id, let parts, let name, let md5Checksum): + return ("inputFile", [("id", id as Any), ("parts", parts as Any), ("name", name as Any), ("md5Checksum", md5Checksum as Any)]) + case .inputFileBig(let id, let parts, let name): + return ("inputFileBig", [("id", id as Any), ("parts", parts as Any), ("name", name as Any)]) + } + } + + public static func parse_inputFile(_ reader: BufferReader) -> InputFile? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: String? + _4 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputFile.inputFile(id: _1!, parts: _2!, name: _3!, md5Checksum: _4!) + } + else { + return nil + } + } + public static func parse_inputFileBig(_ reader: BufferReader) -> InputFile? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputFile.inputFileBig(id: _1!, parts: _2!, name: _3!) + } + else { + return nil + } + } + + } +} public extension Api { indirect enum InputFileLocation: TypeConstructorDescription { case inputDocumentFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer, thumbSize: String) diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 6ed005ceef..1114aaa8bc 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -144,13 +144,13 @@ public class UnauthorizedAccount { return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self)) } - |> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in - return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in - return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self)) + |> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration), NoError> in + return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration) in + return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self), transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue) } } - |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal 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) + |> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal 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) |> map { network in let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) @@ -248,7 +248,7 @@ public func accountWithId(accountManager: AccountManager map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } @@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager 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) + 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) |> map { network -> AccountResult in return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary)) } @@ -267,7 +267,7 @@ public func accountWithId(accountManager: AccountManager map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } @@ -889,6 +889,11 @@ public func accountBackupData(postbox: Postbox) -> Signal map { network -> AccountStateManager? in Logger.shared.log("StandaloneStateManager", "received network") diff --git a/submodules/TelegramCore/Sources/Network/Download.swift b/submodules/TelegramCore/Sources/Network/Download.swift index 2ebe6cff22..7966a99445 100644 --- a/submodules/TelegramCore/Sources/Network/Download.swift +++ b/submodules/TelegramCore/Sources/Network/Download.swift @@ -103,7 +103,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId) } - static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal { + 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 { let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse) if asBigPart { let totalParts: Int32 @@ -117,7 +117,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data)) } - return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, expectedResponseSize: nil) + return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, onFloodWaitError: onFloodWaitError, expectedResponseSize: nil) |> mapError { error -> UploadPartError in if error.errorCode == 400 { return .invalidMedia @@ -130,7 +130,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } } - func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal { + func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal { return Signal { subscriber in let request = MTRequest() @@ -159,6 +159,13 @@ class Download: NSObject, MTRequestMessageServiceDelegate { request.dependsOnPasswordEntry = false request.shouldContinueExecutionWithErrorContext = { errorContext in + guard let errorContext = errorContext else { + return true + } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } + return true } @@ -295,7 +302,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { |> retryRequest } - func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true) -> Signal { + func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal { return Signal { subscriber in let request = MTRequest() request.expectedResponseSize = expectedResponseSize ?? 0 @@ -314,6 +321,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } @@ -344,7 +354,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } } - func requestWithAdditionalData(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> { + func requestWithAdditionalData(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> { return Signal { subscriber in let request = MTRequest() request.expectedResponseSize = expectedResponseSize ?? 0 @@ -363,6 +373,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } @@ -396,7 +409,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } } - func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> { + 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)> { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() @@ -416,6 +429,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } diff --git a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift index 5c9a473a3a..83d0c4b40c 100644 --- a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift +++ b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift @@ -104,14 +104,14 @@ private struct DownloadWrapper { self.useMainConnection = useMainConnection } - func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), MTRpcError> { + func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?, onFloodWaitError: @escaping (String) -> Void) -> Signal<(T, NetworkResponseInfo), MTRpcError> { let target: MultiplexedRequestTarget if self.isCdn { target = .cdn(Int(self.datacenterId)) } else { target = .main(Int(self.datacenterId)) } - return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, expectedResponseSize: expectedResponseSize) + return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize) |> mapError { error, _ -> MTRpcError in return error } @@ -192,7 +192,7 @@ private final class MultipartCdnHashSource { clusterContext = ClusterContext(disposable: disposable) self.clusterContexts[offset] = clusterContext - disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil) + 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 }) |> map { partHashes, _ -> [Int64: Data] in var parsedPartHashes: [Int64: Data] = [:] for part in partHashes { @@ -322,7 +322,7 @@ private enum MultipartFetchSource { } } - func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> { + func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool, onFloodWaitError: @escaping (String) -> Void) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> { var resourceReferenceValue: MediaResourceReference? switch resourceReference { case .forceRevalidate: @@ -348,7 +348,9 @@ private enum MultipartFetchSource { case .revalidate: return .fail(.revalidateMediaReference) case let .location(parsedLocation): - return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit)) + return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in + onFloodWaitError(error) + }) |> mapError { error -> MultipartFetchDownloadError in if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { return .revalidateMediaReference @@ -380,7 +382,9 @@ private enum MultipartFetchSource { } } case let .web(_, location): - return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit)) + return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in + onFloodWaitError(error) + }) |> mapError { error -> MultipartFetchDownloadError in if error.errorDescription == "WEBFILE_NOT_AVAILABLE" { return .webfileNotAvailable @@ -404,7 +408,9 @@ private enum MultipartFetchSource { updatedLength += 1 } - let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength)) + let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength), onFloodWaitError: { error in + onFloodWaitError(error) + }) |> mapError { _ -> MultipartFetchDownloadError in return .generic } @@ -723,6 +729,13 @@ private final class MultipartFetchManager { } } + + private func processFloodWaitError(error: String) { + if error.hasPrefix("FLOOD_PREMIUM_WAIT") { + self.network.addNetworkSpeedLimitedEvent(event: .download) + } + } + func checkState() { guard let currentIntervals = self.currentIntervals else { return @@ -836,7 +849,15 @@ private final class MultipartFetchManager { } let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound) - 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 queue = self.queue + let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground, onFloodWaitError: { [weak self] error in + queue.async { + guard let self else { + return + } + self.processFloodWaitError(error: error) + } + }) |> deliverOn(self.queue) let partDisposable = MetaDisposable() self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable) @@ -919,7 +940,7 @@ private final class MultipartFetchManager { case let .cdn(_, _, fileToken, _, _, _, masterDownload, _): if !strongSelf.reuploadingToCdn { strongSelf.reuploadingToCdn = true - let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil) + 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 }) |> map { result, _ -> [Api.FileHash] in return result } diff --git a/submodules/TelegramCore/Sources/Network/MultipartUpload.swift b/submodules/TelegramCore/Sources/Network/MultipartUpload.swift index 95331fcf9f..3f07e3bb5e 100644 --- a/submodules/TelegramCore/Sources/Network/MultipartUpload.swift +++ b/submodules/TelegramCore/Sources/Network/MultipartUpload.swift @@ -470,12 +470,21 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload fetchedResource = .complete() } + let onFloodWaitError: (String) -> Void = { [weak network] error in + guard let network else { + return + } + if error.hasPrefix("FLOOD_PREMIUM_WAIT") { + network.addNetworkSpeedLimitedEvent(event: .upload) + } + } + let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in switch uploadInterface { case let .download(download): - return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression) + return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError) 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) + 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) } }, progress: { progress in subscriber.putNext(.progress(progress)) diff --git a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift index 7e1a846480..1172b3c739 100644 --- a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift +++ b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift @@ -33,12 +33,13 @@ private final class RequestData { let tag: MediaResourceFetchTag? let continueInBackground: Bool let automaticFloodWait: Bool + let onFloodWaitError: ((String) -> Void)? let expectedResponseSize: Int32? let deserializeResponse: (Buffer) -> Any? let completed: (Any, NetworkResponseInfo) -> Void let error: (MTRpcError, Double) -> Void - init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, 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, onFloodWaitError: ((String) -> Void)?, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) { self.id = id self.consumerId = consumerId self.resourceId = resourceId @@ -47,6 +48,7 @@ private final class RequestData { self.tag = tag self.continueInBackground = continueInBackground self.automaticFloodWait = automaticFloodWait + self.onFloodWaitError = onFloodWaitError self.expectedResponseSize = expectedResponseSize self.payload = payload self.deserializeResponse = deserializeResponse @@ -155,12 +157,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, 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, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable { let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground) let requestId = self.nextId self.nextId += 1 - self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, 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, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in return data.2(buffer) }, completed: { result, info in completed(result, info) @@ -254,7 +256,7 @@ private final class MultiplexedRequestManagerContext { let requestId = request.id selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable)) let queue = self.queue - disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, 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, onFloodWaitError: request.onFloodWaitError, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in queue.async { guard let strongSelf = self else { return @@ -354,13 +356,13 @@ final class MultiplexedRequestManager { return disposable } - func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal { + func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() self.context.with { context in disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in return data.2.parse(buffer) - }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, _ in + }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, _ in if let result = result as? T { subscriber.putNext(result) subscriber.putCompletion() @@ -375,13 +377,13 @@ final class MultiplexedRequestManager { } } - func requestWithAdditionalInfo(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> { + func requestWithAdditionalInfo(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> { return Signal { subscriber in let disposable = MetaDisposable() self.context.with { context in disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in return data.2.parse(buffer) - }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, info in + }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, info in if let result = result as? T { subscriber.putNext((result, info)) subscriber.putCompletion() diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift index 2914559d88..4d82d66fba 100644 --- a/submodules/TelegramCore/Sources/Network/Network.swift +++ b/submodules/TelegramCore/Sources/Network/Network.swift @@ -459,7 +459,7 @@ public struct NetworkInitializationArguments { private let cloudDataContext = Atomic(value: nil) #endif -func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool) -> Signal { +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 { return Signal { subscriber in let queue = Queue() queue.async { @@ -612,6 +612,11 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures, useExperimentalFeatures: useExperimentalFeatures) + + if let data = appConfiguration.data, let notifyInterval = data["upload_premium_speedup_notify_period"] as? Double { + network.updateNetworkSpeedLimitedEventNotifyInterval(value: notifyInterval) + } + appDataUpdatedImpl = { [weak network] data in guard let data = data else { return @@ -734,6 +739,22 @@ public enum NetworkRequestResult { case progress(Float, Int32) } +private final class NetworkSpeedLimitedEventState { + var notifyInterval: Double = 60.0 * 60.0 + var lastNotifyTimestamp: Double = 0.0 + + func add(event: NetworkSpeedLimitedEvent) -> Bool { + let timestamp = CFAbsoluteTimeGetCurrent() + + if self.lastNotifyTimestamp + self.notifyInterval < timestamp { + self.lastNotifyTimestamp = timestamp + return true + } else { + return false + } + } +} + public final class Network: NSObject, MTRequestMessageServiceDelegate { public let encryptionProvider: EncryptionProvider @@ -766,6 +787,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { return self._connectionStatus.get() |> distinctUntilChanged } + public var networkSpeedLimitedEvents: Signal { + return self.networkSpeedLimitedEventPipe.signal() + } + private let networkSpeedLimitedEventPipe = ValuePipe() + private let networkSpeedLimitedEventState = Atomic(value: NetworkSpeedLimitedEventState()) + public func dropConnectionStatus() { _connectionStatus.set(.single(.waitingForNetwork)) } @@ -826,18 +853,18 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { let array = NSMutableArray() if let result = result { switch result { - case let .cdnConfig(publicKeys): - for key in publicKeys { - switch key { - case let .cdnPublicKey(dcId, publicKey): - if id == Int(dcId) { - let dict = NSMutableDictionary() - dict["key"] = publicKey - dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey) - array.add(dict) - } + case let .cdnConfig(publicKeys): + for key in publicKeys { + switch key { + case let .cdnPublicKey(dcId, publicKey): + if id == Int(dcId) { + let dict = NSMutableDictionary() + dict["key"] = publicKey + dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey) + array.add(dict) } } + } } } return array @@ -867,12 +894,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { let isCdn: Bool let isMedia: Bool = true switch target { - case let .main(id): - datacenterId = id - isCdn = false - case let .cdn(id): - datacenterId = id - isCdn = true + case let .main(id): + datacenterId = id + isCdn = false + case let .cdn(id): + datacenterId = id + isCdn = true } return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground) } @@ -880,7 +907,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { }) let shouldKeepConnectionSignal = self.shouldKeepConnection.get() - |> distinctUntilChanged |> deliverOn(queue) + |> distinctUntilChanged |> deliverOn(queue) self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in if let strongSelf = self { if value { @@ -967,11 +994,11 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address) /*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false) - if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) { - } else { - let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false) - self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false) - }*/ + if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) { + } else { + let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false) + self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false) + }*/ let currentSchemes = self.context.transportSchemesForDatacenter(withId: Int(datacenterId), media: false, enforceMedia: false, isProxy: false) var found = false @@ -988,7 +1015,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { } } - public func requestWithAdditionalInfo(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal, MTRpcError> { + public func requestWithAdditionalInfo(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal, MTRpcError> { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() @@ -1006,6 +1033,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } @@ -1056,8 +1086,8 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { } } } - - public func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal { + + public func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() @@ -1075,6 +1105,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } @@ -1113,6 +1146,21 @@ 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(signal: Signal) -> Signal { diff --git a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift index de6f1b9359..cc2ec49b8b 100644 --- a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift @@ -6,7 +6,7 @@ import SwiftSignalKit extension PeerStatusSettings { init(apiSettings: Api.PeerSettings) { switch apiSettings { - case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate, _, _): + case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl): var result = PeerStatusSettings.Flags() if (flags & (1 << 1)) != 0 { result.insert(.canAddContact) @@ -32,7 +32,15 @@ extension PeerStatusSettings { if (flags & (1 << 8)) != 0 { result.insert(.suggestAddMembers) } - self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0) + + var managingBot: ManagingBot? + if let businessBotId { + let businessBotPaused = (flags & (1 << 11)) != 0 + let businessBotCanReply = (flags & (1 << 12)) != 0 + managingBot = ManagingBot(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(businessBotId)), manageUrl: businessBotManageUrl, isPaused: businessBotPaused, canReply: businessBotCanReply) + } + + self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0, managingBot: managingBot) } } } diff --git a/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift b/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift index bd375d0806..06ca47960a 100644 --- a/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PrivacySettings.swift @@ -35,8 +35,8 @@ public final class SelectivePrivacyPeer: Equatable { public enum SelectivePrivacySettings: Equatable { case enableEveryone(disableFor: [PeerId: SelectivePrivacyPeer]) - case enableContacts(enableFor: [PeerId: SelectivePrivacyPeer], disableFor: [PeerId: SelectivePrivacyPeer]) - case disableEveryone(enableFor: [PeerId: SelectivePrivacyPeer], enableForCloseFriends: Bool) + case enableContacts(enableFor: [PeerId: SelectivePrivacyPeer], disableFor: [PeerId: SelectivePrivacyPeer], enableForPremium: Bool) + case disableEveryone(enableFor: [PeerId: SelectivePrivacyPeer], enableForCloseFriends: Bool, enableForPremium: Bool) public static func ==(lhs: SelectivePrivacySettings, rhs: SelectivePrivacySettings) -> Bool { switch lhs { @@ -46,14 +46,14 @@ public enum SelectivePrivacySettings: Equatable { } else { return false } - case let .enableContacts(enableFor, disableFor): - if case .enableContacts(enableFor, disableFor) = rhs { + case let .enableContacts(enableFor, disableFor, enableForPremium): + if case .enableContacts(enableFor, disableFor, enableForPremium) = rhs { return true } else { return false } - case let .disableEveryone(enableFor, enableForCloseFriends): - if case .disableEveryone(enableFor, enableForCloseFriends) = rhs { + case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremium): + if case .disableEveryone(enableFor, enableForCloseFriends, enableForPremium) = rhs { return true } else { return false @@ -63,10 +63,10 @@ public enum SelectivePrivacySettings: Equatable { func withEnabledPeers(_ peers: [PeerId: SelectivePrivacyPeer]) -> SelectivePrivacySettings { switch self { - case let .disableEveryone(enableFor, enableForCloseFriends): - return .disableEveryone(enableFor: enableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }), enableForCloseFriends: enableForCloseFriends) - case let .enableContacts(enableFor, disableFor): - return .enableContacts(enableFor: enableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }), disableFor: disableFor) + case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremium): + return .disableEveryone(enableFor: enableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }), enableForCloseFriends: enableForCloseFriends, enableForPremium: enableForPremium) + case let .enableContacts(enableFor, disableFor, enableForPremium): + return .enableContacts(enableFor: enableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }), disableFor: disableFor, enableForPremium: enableForPremium) case .enableEveryone: return self } @@ -76,17 +76,28 @@ public enum SelectivePrivacySettings: Equatable { switch self { case .disableEveryone: return self - case let .enableContacts(enableFor, disableFor): - return .enableContacts(enableFor: enableFor, disableFor: disableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs })) + case let .enableContacts(enableFor, disableFor, enableForPremium): + return .enableContacts(enableFor: enableFor, disableFor: disableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs }), enableForPremium: enableForPremium) case let .enableEveryone(disableFor): return .enableEveryone(disableFor: disableFor.merging(peers, uniquingKeysWith: { lhs, rhs in lhs })) } } + func withEnableForPremium(_ enableForPremium: Bool) -> SelectivePrivacySettings { + switch self { + case let .disableEveryone(enableFor, enableForCloseFriends, _): + return .disableEveryone(enableFor: enableFor, enableForCloseFriends: enableForCloseFriends, enableForPremium: enableForPremium) + case let .enableContacts(enableFor, disableFor, _): + return .enableContacts(enableFor: enableFor, disableFor: disableFor, enableForPremium: enableForPremium) + case .enableEveryone: + return self + } + } + func withEnableForCloseFriends(_ enableForCloseFriends: Bool) -> SelectivePrivacySettings { switch self { - case let .disableEveryone(enableFor, _): - return .disableEveryone(enableFor: enableFor, enableForCloseFriends: enableForCloseFriends) + case let .disableEveryone(enableFor, _, enableForPremium): + return .disableEveryone(enableFor: enableFor, enableForCloseFriends: enableForCloseFriends, enableForPremium: enableForPremium) case .enableContacts: return self case .enableEveryone: @@ -174,18 +185,19 @@ public struct AccountPrivacySettings: Equatable { extension SelectivePrivacySettings { init(apiRules: [Api.PrivacyRule], peers: [PeerId: SelectivePrivacyPeer]) { - var current: SelectivePrivacySettings = .disableEveryone(enableFor: [:], enableForCloseFriends: false) + var current: SelectivePrivacySettings = .disableEveryone(enableFor: [:], enableForCloseFriends: false, enableForPremium: false) var disableFor: [PeerId: SelectivePrivacyPeer] = [:] var enableFor: [PeerId: SelectivePrivacyPeer] = [:] var enableForCloseFriends: Bool = false + let enableForPremium: Bool = false for rule in apiRules { switch rule { case .privacyValueAllowAll: current = .enableEveryone(disableFor: [:]) case .privacyValueAllowContacts: - current = .enableContacts(enableFor: [:], disableFor: [:]) + current = .enableContacts(enableFor: [:], disableFor: [:], enableForPremium: false) case let .privacyValueAllowUsers(users): for id in users { if let peer = peers[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))] { @@ -223,7 +235,7 @@ extension SelectivePrivacySettings { } } - self = current.withEnabledPeers(enableFor).withDisabledPeers(disableFor).withEnableForCloseFriends(enableForCloseFriends) + self = current.withEnabledPeers(enableFor).withDisabledPeers(disableFor).withEnableForCloseFriends(enableForCloseFriends).withEnableForPremium(enableForPremium) } } diff --git a/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift index acd550c523..37e1406e1f 100644 --- a/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift +++ b/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift @@ -360,11 +360,11 @@ final class ChatHistoryPreloadManager { guard let strongSelf = self else { return } - #if DEBUG + /*#if DEBUG if "".isEmpty { return } - #endif + #endif*/ var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = [] for item in loadItems { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index cfef517df3..7c8fac5dd5 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -551,7 +551,7 @@ public final class CachedChannelData: CachedPeerData { var peerIds = Set() if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift index 5959310a0b..eb8391bec0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift @@ -203,7 +203,7 @@ public final class CachedGroupData: CachedPeerData { self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i") self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo] if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 5e9a6f657b..e6cb200972 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -558,7 +558,7 @@ public final class CachedUserData: CachedPeerData { self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift index ed6ca78314..07748e0da2 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift @@ -19,25 +19,42 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { } + 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 geoDistance: Int32? public var requestChatTitle: String? public var requestChatDate: Int32? public var requestChatIsChannel: Bool? + public var managingBot: ManagingBot? public init() { self.flags = PeerStatusSettings.Flags() self.geoDistance = nil self.requestChatTitle = nil self.requestChatDate = nil + self.managingBot = nil } - public init(flags: PeerStatusSettings.Flags, geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil) { + public init(flags: PeerStatusSettings.Flags, geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil, managingBot: ManagingBot? = nil) { self.flags = flags self.geoDistance = geoDistance self.requestChatTitle = requestChatTitle self.requestChatDate = requestChatDate self.requestChatIsChannel = requestChatIsChannel + self.managingBot = managingBot } public init(decoder: PostboxDecoder) { @@ -46,6 +63,7 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle") self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate") self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel") + self.managingBot = decoder.decodeCodable(ManagingBot.self, forKey: "managingBot") } public func encode(_ encoder: PostboxEncoder) { @@ -70,6 +88,11 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "requestChatIsChannel") } + if let managingBot = self.managingBot { + encoder.encodeCodable(managingBot, forKey: "managingBot") + } else { + encoder.encodeNil(forKey: "managingBot") + } } public func contains(_ member: PeerStatusSettings.Flags) -> Bool { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift index 78150dc1d7..be745fbed8 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift @@ -93,7 +93,7 @@ public final class CachedSecretChatData: CachedPeerData { public init(decoder: PostboxDecoder) { if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 9ed2063e1b..ca97ac3ea4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -1585,6 +1585,34 @@ public extension TelegramEngine.EngineData.Item { } } + public struct ChatManagingBot: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public typealias Result = PeerStatusSettings.ManagingBot? + + fileprivate var id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + var key: PostboxViewKey { + return .cachedPeerData(peerId: self.id) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? CachedPeerDataView else { + preconditionFailure() + } + if let cachedData = view.cachedPeerData as? CachedUserData { + return cachedData.peerStatusSettings?.managingBot + } else { + return nil + } + } + } + public struct BotBiometricsState: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = TelegramBotBiometricsState diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift index 55e5779f21..855940e197 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift @@ -358,3 +358,43 @@ func _internal_updateBotBiometricsState(account: Account, peerId: EnginePeer.Id, } |> ignoreValues } + +func _internal_toggleChatManagingBotIsPaused(account: Account, chatId: EnginePeer.Id) -> Signal { + return account.postbox.transaction { transaction -> Void in + transaction.updatePeerCachedData(peerIds: Set([chatId]), update: { _, current in + guard let current = current as? CachedUserData else { + return current + } + + if var peerStatusSettings = current.peerStatusSettings { + if let managingBot = peerStatusSettings.managingBot { + peerStatusSettings.managingBot?.isPaused = !managingBot.isPaused + } + + return current.withUpdatedPeerStatusSettings(peerStatusSettings) + } else { + return current + } + }) + } + |> ignoreValues +} + +func _internal_removeChatManagingBot(account: Account, chatId: EnginePeer.Id) -> Signal { + return account.postbox.transaction { transaction -> Void in + transaction.updatePeerCachedData(peerIds: Set([chatId]), update: { _, current in + guard let current = current as? CachedUserData else { + return current + } + + if var peerStatusSettings = current.peerStatusSettings { + peerStatusSettings.managingBot = nil + + return current.withUpdatedPeerStatusSettings(peerStatusSettings) + } else { + return current + } + }) + } + |> ignoreValues +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift index 1deae7bbed..04ffbec511 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift @@ -214,7 +214,7 @@ func _internal_dismissPeerStatusOptions(account: Account, peerId: PeerId) -> Sig if let current = current as? CachedUserData { var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings() peerStatusSettings.flags = [] - return current.withUpdatedPeerStatusSettings(PeerStatusSettings(flags: [])) + return current.withUpdatedPeerStatusSettings(peerStatusSettings) } else if let current = current as? CachedGroupData { var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings() peerStatusSettings.flags = [] diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 6980bbab07..6b35401d3f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1494,6 +1494,14 @@ public extension TelegramEngine { public func updateBotBiometricsState(peerId: EnginePeer.Id, update: @escaping (TelegramBotBiometricsState) -> TelegramBotBiometricsState) { let _ = _internal_updateBotBiometricsState(account: self.account, peerId: peerId, update: update).startStandalone() } + + public func toggleChatManagingBotIsPaused(chatId: EnginePeer.Id) { + let _ = _internal_toggleChatManagingBotIsPaused(account: self.account, chatId: chatId).startStandalone() + } + + public func removeChatManagingBot(chatId: EnginePeer.Id) { + let _ = _internal_removeChatManagingBot(account: self.account, chatId: chatId).startStandalone() + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 775b5d7c2e..1c7e4349a2 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -56,12 +56,12 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, accountP var peerStatusSettings: PeerStatusSettings if let peer = transaction.getPeer(peer.id), let associatedPeerId = peer.associatedPeerId, !transaction.isPeerContact(peerId: associatedPeerId) { if let peer = peer as? TelegramSecretChat, case .creator = peer.role { - peerStatusSettings = PeerStatusSettings(flags: []) + peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil) } else { - peerStatusSettings = PeerStatusSettings(flags: [.canReport]) + peerStatusSettings = PeerStatusSettings(flags: [.canReport], managingBot: nil) } } else { - peerStatusSettings = PeerStatusSettings(flags: []) + peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil) } transaction.updatePeerCachedData(peerIds: [peer.id], update: { peerId, current in @@ -246,7 +246,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } switch fullUser { - case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .userFull(_, _, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: userFullNotifySettings)]) } @@ -258,7 +258,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee previous = CachedUserData() } switch fullUser { - case let .userFull(userFullFlags, _, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions, userWallpaper, stories, businessWorkHours, businessLocation, greetingMessage, awayMessage): + case let .userFull(userFullFlags, _, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions, userWallpaper, stories, businessWorkHours, businessLocation, greetingMessage, awayMessage, _): let _ = stories let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:)) let isBlocked = (userFullFlags & (1 << 0)) != 0 diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift index 87e3c6d59b..deb32f81a4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Privacy/UpdatedAccountPrivacySettings.swift @@ -412,7 +412,7 @@ func _internal_updateSelectiveAccountPrivacySettings(account: Account, type: Upd return account.postbox.transaction { transaction -> Signal in var rules: [Api.InputPrivacyRule] = [] switch settings { - case let .disableEveryone(enableFor, enableForCloseFriends): + case let .disableEveryone(enableFor, enableForCloseFriends, enableForPremium): let enablePeers = apiUserAndGroupIds(peerIds: enableFor) if !enablePeers.users.isEmpty { @@ -426,7 +426,8 @@ func _internal_updateSelectiveAccountPrivacySettings(account: Account, type: Upd if enableForCloseFriends { rules.append(.inputPrivacyValueAllowCloseFriends) } - case let .enableContacts(enableFor, disableFor): + let _ = enableForPremium + case let .enableContacts(enableFor, disableFor, enableForPremium): let enablePeers = apiUserAndGroupIds(peerIds: enableFor) let disablePeers = apiUserAndGroupIds(peerIds: disableFor) @@ -445,6 +446,7 @@ func _internal_updateSelectiveAccountPrivacySettings(account: Account, type: Upd } rules.append(Api.InputPrivacyRule.inputPrivacyValueAllowContacts) + let _ = enableForPremium case let .enableEveryone(disableFor): let disablePeers = apiUserAndGroupIds(peerIds: disableFor) diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 5892c6150a..a32a83ba44 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -436,9 +436,12 @@ swift_library( "//submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen", "//submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen", "//submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen", + "//submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen", "//submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController", "//submodules/TelegramUI/Components/Settings/CollectibleItemInfoScreen", "//submodules/TelegramUI/Components/StickerPickerScreen", + "//submodules/TelegramUI/Components/Chat/ChatEmptyNode", + "//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD new file mode 100644 index 0000000000..c9ad95f646 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD @@ -0,0 +1,37 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatEmptyNode", + module_name = "ChatEmptyNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/TelegramPresentationData", + "//submodules/AppBundle", + "//submodules/LocalizedPeerData", + "//submodules/TelegramStringFormatting", + "//submodules/AccountContext", + "//submodules/ChatPresentationInterfaceState", + "//submodules/WallpaperBackgroundNode", + "//submodules/ComponentFlow", + "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/Chat/ChatLoadingNode", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/Markdown", + "//submodules/ReactionSelectionNode", + "//submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift similarity index 92% rename from submodules/TelegramUI/Sources/ChatEmptyNode.swift rename to submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift index b81dd01bc0..0de95fdd6c 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift @@ -19,6 +19,7 @@ import MultilineTextComponent import BalancedTextComponent import Markdown import ReactionSelectionNode +import ChatMediaInputStickerGridItem private protocol ChatEmptyNodeContent { func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize @@ -79,11 +80,11 @@ private final class ChatEmptyNodeRegularChatContent: ASDisplayNode, ChatEmptyNod } } -protocol ChatEmptyNodeStickerContentNode: ASDisplayNode { +public protocol ChatEmptyNodeStickerContentNode: ASDisplayNode { var stickerNode: ChatMediaInputStickerGridItemNode { get } } -final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate { +public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate { private let context: AccountContext private let interaction: ChatPanelInterfaceInteraction? @@ -91,15 +92,16 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke private let textNode: ImmediateTextNode private var stickerItem: ChatMediaInputStickerGridItem? - let stickerNode: ChatMediaInputStickerGridItemNode + public var stickerNode: ChatMediaInputStickerGridItemNode private var currentTheme: PresentationTheme? private var currentStrings: PresentationStrings? private var didSetupSticker = false private let disposable = MetaDisposable() + private var currentCustomStickerFile: TelegramMediaFile? - init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { + public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { self.context = context self.interaction = interaction @@ -126,7 +128,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke self.addSubnode(self.stickerNode) } - override func didLoad() { + override public func didLoad() { super.didLoad() let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:))) @@ -138,7 +140,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke self.disposable.dispose() } - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } @@ -149,18 +151,29 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, []) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let isFirstTime = self.currentTheme == nil + if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings - - let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) - + } + + var customStickerFile: TelegramMediaFile? + + let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) + if case let .emptyChat(emptyChat) = subject, case let .customGreeting(stickerFile, title, text) = emptyChat { + customStickerFile = stickerFile + self.titleNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: serviceColor.primaryText) + self.textNode.attributedText = NSAttributedString(string: text, font: messageFont, textColor: serviceColor.primaryText) + } else { self.titleNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_EmptyPlaceholder, font: titleFont, textColor: serviceColor.primaryText) - self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Conversation_GreetingText, font: messageFont, textColor: serviceColor.primaryText) } + let previousCustomStickerFile = self.currentCustomStickerFile + self.currentCustomStickerFile = customStickerFile + let stickerSize: CGSize let inset: CGFloat if size.width == 320.0 { @@ -170,11 +183,13 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke stickerSize = CGSize(width: 160.0, height: 160.0) inset = 15.0 } - if let item = self.stickerItem { + if let item = self.stickerItem, previousCustomStickerFile == customStickerFile { self.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true) - } else if !self.didSetupSticker { + } else if !self.didSetupSticker || previousCustomStickerFile != customStickerFile { let sticker: Signal - if let preloadedSticker = interfaceState.greetingData?.sticker { + if let customStickerFile { + sticker = .single(customStickerFile) + } else if let preloadedSticker = interfaceState.greetingData?.sticker { sticker = preloadedSticker } else { sticker = self.context.engine.stickers.randomGreetingSticker() @@ -183,6 +198,19 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke } } + if !isFirstTime, case let .emptyChat(emptyChat) = subject, case .customGreeting = emptyChat { + let previousStickerNode = self.stickerNode + previousStickerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousStickerNode] _ in + previousStickerNode?.removeFromSupernode() + }) + previousStickerNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false) + + self.stickerNode = ChatMediaInputStickerGridItemNode() + self.addSubnode(self.stickerNode) + self.stickerNode.layer.animateSpring(from: 0.001 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) + self.stickerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + self.didSetupSticker = true self.disposable.set((sticker |> deliverOnMainQueue).startStrict(next: { [weak self] sticker in @@ -216,6 +244,10 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke let stickerPackItem = StickerPackItem(index: index, file: sticker, indexKeys: []) let item = ChatMediaInputStickerGridItem(context: strongSelf.context, collectionId: collectionId, stickerPackInfo: nil, index: ItemCollectionViewEntryIndex(collectionIndex: 0, collectionId: collectionId, itemIndex: index), stickerItem: stickerPackItem, canManagePeerSpecificPack: nil, interfaceInteraction: nil, inputNodeInteraction: inputNodeInteraction, hasAccessory: false, theme: interfaceState.theme, large: true, selected: {}) strongSelf.stickerItem = item + + if isFirstTime { + + } strongSelf.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true) strongSelf.stickerNode.isVisibleInGrid = true strongSelf.stickerNode.updateIsPanelVisible(true) @@ -252,7 +284,7 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke } } -final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate { +public final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerContentNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate { private let context: AccountContext private let interaction: ChatPanelInterfaceInteraction? @@ -260,7 +292,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC private let textNode: ImmediateTextNode private var stickerItem: ChatMediaInputStickerGridItem? - let stickerNode: ChatMediaInputStickerGridItemNode + public let stickerNode: ChatMediaInputStickerGridItemNode private var currentTheme: PresentationTheme? private var currentStrings: PresentationStrings? @@ -268,7 +300,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC private var didSetupSticker = false private let disposable = MetaDisposable() - init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { + public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { self.context = context self.interaction = interaction @@ -295,7 +327,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC self.addSubnode(self.stickerNode) } - override func didLoad() { + override public func didLoad() { super.didLoad() let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.stickerTapGesture(_:))) @@ -307,7 +339,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC self.disposable.dispose() } - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } @@ -318,7 +350,7 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC let _ = self.interaction?.sendSticker(.standalone(media: stickerItem.stickerItem.file), false, self.view, self.stickerNode.bounds, nil, []) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -844,7 +876,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC } } -final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate { +public final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, UIGestureRecognizerDelegate { private let context: AccountContext private let titleNode: ImmediateTextNode @@ -855,7 +887,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, private let iconView: ComponentView - init(context: AccountContext) { + public init(context: AccountContext) { self.context = context self.titleNode = ImmediateTextNode() @@ -880,7 +912,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, self.addSubnode(self.textNode) } - func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme @@ -955,7 +987,7 @@ final class ChatEmptyNodeTopicChatContent: ASDisplayNode, ChatEmptyNodeContent, } } -final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNodeContent { +public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNodeContent { private let isPremiumDisabled: Bool private let interaction: ChatPanelInterfaceInteraction? @@ -969,7 +1001,7 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod private var currentTheme: PresentationTheme? private var currentStrings: PresentationStrings? - init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { + public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { let premiumConfiguration = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }) self.isPremiumDisabled = premiumConfiguration.isPremiumDisabled @@ -1016,7 +1048,7 @@ final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatEmptyNod } } - func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: ChatEmptyNode.Subject, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { let serviceColor = serviceMessageColorComponents(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper) let maxWidth = min(200.0, size.width) @@ -1139,9 +1171,18 @@ private enum ChatEmptyNodeContentType: Equatable { case premiumRequired } -final class ChatEmptyNode: ASDisplayNode { - enum Subject { - case emptyChat(ChatHistoryNodeLoadState.EmptyType) +public final class ChatEmptyNode: ASDisplayNode { + public enum Subject { + public enum EmptyType: Equatable { + case generic + case joined + case clearedHistory + case topic + case botInfo + case customGreeting(sticker: TelegramMediaFile?, title: String, text: String) + } + + case emptyChat(EmptyType) case detailsPlaceholder } private let context: AccountContext @@ -1159,7 +1200,7 @@ final class ChatEmptyNode: ASDisplayNode { private var content: (ChatEmptyNodeContentType, ASDisplayNode & ChatEmptyNodeContent)? - init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { + public init(context: AccountContext, interaction: ChatPanelInterfaceInteraction?) { self.context = context self.interaction = interaction @@ -1172,14 +1213,14 @@ final class ChatEmptyNode: ASDisplayNode { self.addSubnode(self.backgroundNode) } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard let result = super.hitTest(point, with: event) else { return nil } return result } - func animateFromLoadingNode(_ loadingNode: ChatLoadingNode) { + public func animateFromLoadingNode(_ loadingNode: ChatLoadingNode) { guard let (_, node) = self.content else { return } @@ -1204,7 +1245,7 @@ final class ChatEmptyNode: ASDisplayNode { } } - func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { + public func updateLayout(interfaceState: ChatPresentationInterfaceState, subject: Subject, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { self.wallpaperBackgroundNode = backgroundNode if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { @@ -1224,7 +1265,9 @@ final class ChatEmptyNode: ASDisplayNode { case .detailsPlaceholder: contentType = .regular case let .emptyChat(emptyType): - if case .customChatContents = interfaceState.subject { + if case .customGreeting = emptyType { + contentType = .greeting + } else if case .customChatContents = interfaceState.subject { contentType = .cloud } else if case .replyThread = interfaceState.chatLocation { if case .topic = emptyType { diff --git a/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD new file mode 100644 index 0000000000..9c809d2612 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/BUILD @@ -0,0 +1,30 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatMediaInputStickerGridItem", + module_name = "ChatMediaInputStickerGridItem", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/AsyncDisplayKit", + "//submodules/Postbox", + "//submodules/TelegramPresentationData", + "//submodules/StickerResources", + "//submodules/AccountContext", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", + "//submodules/ShimmerEffect", + "//submodules/TelegramUI/Components/ChatControllerInteraction", + "//submodules/ChatPresentationInterfaceState", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/Sources/ChatMediaInputStickerGridItem.swift similarity index 84% rename from submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift rename to submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/Sources/ChatMediaInputStickerGridItem.swift index b1d3e896aa..3d2a80eea9 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputStickerGridItem.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMediaInputStickerGridItem/Sources/ChatMediaInputStickerGridItem.swift @@ -14,25 +14,25 @@ import ShimmerEffect import ChatControllerInteraction import ChatPresentationInterfaceState -enum ChatMediaInputStickerGridSectionAccessory { +public enum ChatMediaInputStickerGridSectionAccessory { case none case setup case clear } -final class ChatMediaInputStickerGridSection: GridSection { - let collectionId: ItemCollectionId - let collectionInfo: StickerPackCollectionInfo? - let accessory: ChatMediaInputStickerGridSectionAccessory - let interaction: ChatMediaInputNodeInteraction - let theme: PresentationTheme - let height: CGFloat = 26.0 +public final class ChatMediaInputStickerGridSection: GridSection { + public let collectionId: ItemCollectionId + public let collectionInfo: StickerPackCollectionInfo? + public let accessory: ChatMediaInputStickerGridSectionAccessory + public let interaction: ChatMediaInputNodeInteraction + public let theme: PresentationTheme + public let height: CGFloat = 26.0 - var hashValue: Int { + public var hashValue: Int { return self.collectionId.hashValue } - init(collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) { + public init(collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) { self.collectionId = collectionId self.collectionInfo = collectionInfo self.accessory = accessory @@ -40,7 +40,7 @@ final class ChatMediaInputStickerGridSection: GridSection { self.interaction = interaction } - func isEqual(to: GridSection) -> Bool { + public func isEqual(to: GridSection) -> Bool { if let to = to as? ChatMediaInputStickerGridSection { return self.collectionId == to.collectionId && self.theme === to.theme } else { @@ -48,20 +48,20 @@ final class ChatMediaInputStickerGridSection: GridSection { } } - func node() -> ASDisplayNode { + public func node() -> ASDisplayNode { return ChatMediaInputStickerGridSectionNode(collectionInfo: self.collectionInfo, accessory: self.accessory, theme: self.theme, interaction: self.interaction) } } private let sectionTitleFont = Font.medium(12.0) -final class ChatMediaInputStickerGridSectionNode: ASDisplayNode { - let titleNode: ASTextNode - let setupNode: HighlightableButtonNode? - let interaction: ChatMediaInputNodeInteraction - let accessory: ChatMediaInputStickerGridSectionAccessory +public final class ChatMediaInputStickerGridSectionNode: ASDisplayNode { + public let titleNode: ASTextNode + public let setupNode: HighlightableButtonNode? + public let interaction: ChatMediaInputNodeInteraction + public let accessory: ChatMediaInputStickerGridSectionAccessory - init(collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) { + public init(collectionInfo: StickerPackCollectionInfo?, accessory: ChatMediaInputStickerGridSectionAccessory, theme: PresentationTheme, interaction: ChatMediaInputNodeInteraction) { self.interaction = interaction self.titleNode = ASTextNode() self.titleNode.isUserInteractionEnabled = false @@ -91,7 +91,7 @@ final class ChatMediaInputStickerGridSectionNode: ASDisplayNode { self.setupNode?.addTarget(self, action: #selector(self.setupPressed), forControlEvents: .touchUpInside) } - override func layout() { + override public func layout() { super.layout() let bounds = self.bounds @@ -116,20 +116,20 @@ final class ChatMediaInputStickerGridSectionNode: ASDisplayNode { } } -final class ChatMediaInputStickerGridItem: GridItem { - let context: AccountContext - let index: ItemCollectionViewEntryIndex - let stickerItem: StickerPackItem - let selected: () -> Void - let interfaceInteraction: ChatControllerInteraction? - let inputNodeInteraction: ChatMediaInputNodeInteraction - let theme: PresentationTheme - let large: Bool - let isLocked: Bool +public final class ChatMediaInputStickerGridItem: GridItem { + public let context: AccountContext + public let index: ItemCollectionViewEntryIndex + public let stickerItem: StickerPackItem + public let selected: () -> Void + public let interfaceInteraction: ChatControllerInteraction? + public let inputNodeInteraction: ChatMediaInputNodeInteraction + public let theme: PresentationTheme + public let large: Bool + public let isLocked: Bool - let section: GridSection? + public let section: GridSection? - init(context: AccountContext, collectionId: ItemCollectionId, stickerPackInfo: StickerPackCollectionInfo?, index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, canManagePeerSpecificPack: Bool?, interfaceInteraction: ChatControllerInteraction?, inputNodeInteraction: ChatMediaInputNodeInteraction, hasAccessory: Bool, theme: PresentationTheme, large: Bool = false, isLocked: Bool = false, selected: @escaping () -> Void) { + public init(context: AccountContext, collectionId: ItemCollectionId, stickerPackInfo: StickerPackCollectionInfo?, index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, canManagePeerSpecificPack: Bool?, interfaceInteraction: ChatControllerInteraction?, inputNodeInteraction: ChatMediaInputNodeInteraction, hasAccessory: Bool, theme: PresentationTheme, large: Bool = false, isLocked: Bool = false, selected: @escaping () -> Void) { self.context = context self.index = index self.stickerItem = stickerItem @@ -145,7 +145,7 @@ final class ChatMediaInputStickerGridItem: GridItem { self.section = ChatMediaInputStickerGridSection(collectionId: collectionId, collectionInfo: stickerPackInfo, accessory: accessory, theme: theme, interaction: inputNodeInteraction) } - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { + public func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { let node = ChatMediaInputStickerGridItemNode() node.interfaceInteraction = self.interfaceInteraction node.inputNodeInteraction = self.inputNodeInteraction @@ -153,7 +153,7 @@ final class ChatMediaInputStickerGridItem: GridItem { return node } - func update(node: GridItemNode) { + public func update(node: GridItemNode) { guard let node = node as? ChatMediaInputStickerGridItemNode else { assertionFailure() return @@ -164,26 +164,26 @@ final class ChatMediaInputStickerGridItem: GridItem { } } -final class ChatMediaInputStickerGridItemNode: GridItemNode { +public final class ChatMediaInputStickerGridItemNode: GridItemNode { private var currentState: (AccountContext, StickerPackItem, CGSize)? private var currentSize: CGSize? - let imageNode: TransformImageNode - private(set) var animationNode: AnimatedStickerNode? - private(set) var placeholderNode: StickerShimmerEffectNode? + public let imageNode: TransformImageNode + public private(set) var animationNode: AnimatedStickerNode? + public private(set) var placeholderNode: StickerShimmerEffectNode? private var lockBackground: UIVisualEffectView? private var lockTintView: UIView? private var lockIconNode: ASImageNode? - var isLocked: Bool? + public var isLocked: Bool? private var didSetUpAnimationNode = false private var item: ChatMediaInputStickerGridItem? private let stickerFetchedDisposable = MetaDisposable() - var currentIsPreviewing = false + public var currentIsPreviewing = false - override var isVisibleInGrid: Bool { + override public var isVisibleInGrid: Bool { didSet { self.updateVisibility() } @@ -192,15 +192,15 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { private var isPanelVisible = false private var isPlaying = false - var interfaceInteraction: ChatControllerInteraction? - var inputNodeInteraction: ChatMediaInputNodeInteraction? - var selected: (() -> Void)? + public var interfaceInteraction: ChatControllerInteraction? + public var inputNodeInteraction: ChatMediaInputNodeInteraction? + public var selected: (() -> Void)? - var stickerPackItem: StickerPackItem? { + public var stickerPackItem: StickerPackItem? { return self.currentState?.1 } - override init() { + override public init() { self.imageNode = TransformImageNode() self.placeholderNode = StickerShimmerEffectNode() self.placeholderNode?.isUserInteractionEnabled = false @@ -244,13 +244,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { } } - override func didLoad() { + override public func didLoad() { super.didLoad() self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) } - override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) { + override public func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) { guard let item = item as? ChatMediaInputStickerGridItem else { return } @@ -392,13 +392,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { } } - override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { + override public func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) { if let placeholderNode = self.placeholderNode { placeholderNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: absoluteRect.minX + placeholderNode.frame.minX, y: absoluteRect.minY + placeholderNode.frame.minY), size: placeholderNode.frame.size), within: containerSize) } } - @objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) { + @objc private func imageNodeTap(_ recognizer: UITapGestureRecognizer) { if self.imageNode.layer.animation(forKey: "opacity") != nil { return } @@ -411,18 +411,18 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { } } - func transitionNode() -> ASDisplayNode? { + public func transitionNode() -> ASDisplayNode? { return self.imageNode } - func updateIsPanelVisible(_ isPanelVisible: Bool) { + public func updateIsPanelVisible(_ isPanelVisible: Bool) { if self.isPanelVisible != isPanelVisible { self.isPanelVisible = isPanelVisible self.updateVisibility() } } - func updateVisibility() { + public func updateVisibility() { guard let item = self.item else { return } @@ -444,7 +444,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { } } - func updatePreviewing(animated: Bool) { + public func updatePreviewing(animated: Bool) { var isPreviewing = false if let (_, item, _) = self.currentState, let interaction = self.inputNodeInteraction { isPreviewing = interaction.previewedStickerPackItemFile?.id == item.file.id diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift index 69bc656b17..ef3ac7d6ff 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift @@ -156,6 +156,18 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess } } + if authorTitle == nil { + for attribute in message.attributes { + if let attribute = attribute as? InlineBusinessBotMessageAttribute { + if let title = attribute.title { + authorTitle = title + } else if let peerId = attribute.peerId, let peer = message.peers[peerId] { + authorTitle = peer.debugDisplayTitle + } + } + } + } + if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { authorTitle = nil } diff --git a/submodules/TelegramUI/Components/EmojiActionIconComponent/BUILD b/submodules/TelegramUI/Components/EmojiActionIconComponent/BUILD new file mode 100644 index 0000000000..abd044bb77 --- /dev/null +++ b/submodules/TelegramUI/Components/EmojiActionIconComponent/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "EmojiActionIconComponent", + module_name = "EmojiActionIconComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/AccountContext", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/EmojiActionIconComponent/Sources/EmojiActionIconComponent.swift b/submodules/TelegramUI/Components/EmojiActionIconComponent/Sources/EmojiActionIconComponent.swift new file mode 100644 index 0000000000..76d434634c --- /dev/null +++ b/submodules/TelegramUI/Components/EmojiActionIconComponent/Sources/EmojiActionIconComponent.swift @@ -0,0 +1,107 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramCore +import EmojiStatusComponent +import AccountContext + +public final class EmojiActionIconComponent: Component { + public let context: AccountContext + public let color: UIColor + public let fileId: Int64? + public let file: TelegramMediaFile? + + public init( + context: AccountContext, + color: UIColor, + fileId: Int64?, + file: TelegramMediaFile? + ) { + self.context = context + self.color = color + self.fileId = fileId + self.file = file + } + + public static func ==(lhs: EmojiActionIconComponent, rhs: EmojiActionIconComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.color != rhs.color { + return false + } + if lhs.fileId != rhs.fileId { + return false + } + if lhs.file != rhs.file { + return false + } + return true + } + + public final class View: UIView { + private var icon: ComponentView? + + func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + let size = CGSize(width: 24.0, height: 24.0) + + if let fileId = component.fileId { + let icon: ComponentView + if let current = self.icon { + icon = current + } else { + icon = ComponentView() + self.icon = icon + } + let content: EmojiStatusComponent.AnimationContent + if let file = component.file { + content = .file(file: file) + } else { + content = .customEmoji(fileId: fileId) + } + let _ = icon.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + content: .animation( + content: content, + size: size, + placeholderColor: .lightGray, + themeColor: component.color, + loopMode: .forever + ), + isVisibleForAnimations: false, + action: nil + )), + environment: {}, + containerSize: size + ) + let iconFrame = CGRect(origin: CGPoint(), size: size) + if let iconView = icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + iconView.frame = iconFrame + } + } else { + if let icon = self.icon { + self.icon = nil + icon.view?.removeFromSuperview() + } + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift index d2c964545d..dee2bf8e5c 100644 --- a/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift +++ b/submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent/Sources/ListMultilineTextFieldItemComponent.swift @@ -11,6 +11,7 @@ import AccountContext public final class ListMultilineTextFieldItemComponent: Component { public final class ExternalState { public fileprivate(set) var hasText: Bool = false + public fileprivate(set) var text: NSAttributedString = NSAttributedString() public init() { } @@ -127,7 +128,7 @@ public final class ListMultilineTextFieldItemComponent: Component { } } - public final class View: UIView, UITextFieldDelegate, ListSectionComponent.ChildView, ComponentTaggedView { + public final class View: UIView, ListSectionComponent.ChildView, ComponentTaggedView { private let textField = ComponentView() private let textFieldExternalState = TextFieldComponent.ExternalState() @@ -156,17 +157,6 @@ public final class ListMultilineTextFieldItemComponent: Component { preconditionFailure() } - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - return true - } - - @objc private func textDidChange() { - if !self.isUpdating { - self.state?.updated(transition: self.component?.textUpdateTransition ?? .immediate) - } - self.component?.updated?(self.currentText) - } - public func setText(text: String, updateState: Bool) { if let textFieldView = self.textField.view as? TextFieldComponent.View { //TODO @@ -206,6 +196,7 @@ public final class ListMultilineTextFieldItemComponent: Component { transition: transition, component: AnyComponent(TextFieldComponent( context: component.context, + theme: component.theme, strings: component.strings, externalState: self.textFieldExternalState, fontSize: 17.0, @@ -266,6 +257,7 @@ public final class ListMultilineTextFieldItemComponent: Component { self.separatorInset = 16.0 component.externalState?.hasText = self.textFieldExternalState.hasText + component.externalState?.text = self.textFieldExternalState.text return size } diff --git a/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift b/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift index 714250a551..9071b79f60 100644 --- a/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift +++ b/submodules/TelegramUI/Components/ListSectionComponent/Sources/ListSectionComponent.swift @@ -24,6 +24,7 @@ public final class ListSectionComponent: Component { public let header: AnyComponent? public let footer: AnyComponent? public let items: [AnyComponentWithIdentity] + public let itemUpdateOrder: [AnyHashable]? public let displaySeparators: Bool public let extendsItemHighlightToSection: Bool @@ -33,6 +34,7 @@ public final class ListSectionComponent: Component { header: AnyComponent?, footer: AnyComponent?, items: [AnyComponentWithIdentity], + itemUpdateOrder: [AnyHashable]? = nil, displaySeparators: Bool = true, extendsItemHighlightToSection: Bool = false ) { @@ -41,6 +43,7 @@ public final class ListSectionComponent: Component { self.header = header self.footer = footer self.items = items + self.itemUpdateOrder = itemUpdateOrder self.displaySeparators = displaySeparators self.extendsItemHighlightToSection = extendsItemHighlightToSection } @@ -61,6 +64,9 @@ public final class ListSectionComponent: Component { if lhs.items != rhs.items { return false } + if lhs.itemUpdateOrder != rhs.itemUpdateOrder { + return false + } if lhs.displaySeparators != rhs.displaySeparators { return false } @@ -204,7 +210,41 @@ public final class ListSectionComponent: Component { var innerContentHeight: CGFloat = 0.0 var validItemIds: [AnyHashable] = [] + + struct ReadyItem { + var index: Int + var itemId: AnyHashable + var itemView: ItemView + var itemTransition: Transition + var itemSize: CGSize + + init(index: Int, itemId: AnyHashable, itemView: ItemView, itemTransition: Transition, itemSize: CGSize) { + self.index = index + self.itemId = itemId + self.itemView = itemView + self.itemTransition = itemTransition + self.itemSize = itemSize + } + } + + var readyItems: [ReadyItem] = [] + var itemUpdateOrder: [Int] = [] + if let itemUpdateOrderValue = component.itemUpdateOrder { + for id in itemUpdateOrderValue { + if let index = component.items.firstIndex(where: { $0.id == id }) { + if !itemUpdateOrder.contains(index) { + itemUpdateOrder.append(index) + } + } + } + } for i in 0 ..< component.items.count { + if !itemUpdateOrder.contains(i) { + itemUpdateOrder.append(i) + } + } + + for i in itemUpdateOrder { let item = component.items[i] let itemId = item.id validItemIds.append(itemId) @@ -226,17 +266,29 @@ public final class ListSectionComponent: Component { environment: {}, containerSize: CGSize(width: availableSize.width, height: availableSize.height) ) - let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: innerContentHeight), size: itemSize) - if let itemComponentView = itemView.contents.view { + + readyItems.append(ReadyItem( + index: i, + itemId: itemId, + itemView: itemView, + itemTransition: itemTransition, + itemSize: itemSize + )) + } + + for readyItem in readyItems.sorted(by: { $0.index < $1.index }) { + let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: innerContentHeight), size: readyItem.itemSize) + if let itemComponentView = readyItem.itemView.contents.view { if itemComponentView.superview == nil { - itemView.addSubview(itemComponentView) - self.contentItemContainerView.addSubview(itemView) - self.contentSeparatorContainerLayer.addSublayer(itemView.separatorLayer) - self.contentHighlightContainerLayer.addSublayer(itemView.highlightLayer) - transition.animateAlpha(view: itemView, from: 0.0, to: 1.0) - transition.animateAlpha(layer: itemView.separatorLayer, from: 0.0, to: 1.0) - transition.animateAlpha(layer: itemView.highlightLayer, from: 0.0, to: 1.0) + readyItem.itemView.addSubview(itemComponentView) + self.contentItemContainerView.addSubview(readyItem.itemView) + self.contentSeparatorContainerLayer.addSublayer(readyItem.itemView.separatorLayer) + self.contentHighlightContainerLayer.addSublayer(readyItem.itemView.highlightLayer) + transition.animateAlpha(view: readyItem.itemView, from: 0.0, to: 1.0) + transition.animateAlpha(layer: readyItem.itemView.separatorLayer, from: 0.0, to: 1.0) + transition.animateAlpha(layer: readyItem.itemView.highlightLayer, from: 0.0, to: 1.0) + let itemId = readyItem.itemId if let itemComponentView = itemComponentView as? ChildView { itemComponentView.customUpdateIsHighlighted = { [weak self] isHighlighted in guard let self else { @@ -250,20 +302,20 @@ public final class ListSectionComponent: Component { if let itemComponentView = itemComponentView as? ChildView { separatorInset = itemComponentView.separatorInset } - itemTransition.setFrame(view: itemView, frame: itemFrame) + readyItem.itemTransition.setFrame(view: readyItem.itemView, frame: itemFrame) - let itemSeparatorTopOffset: CGFloat = i == 0 ? 0.0 : -UIScreenPixel + let itemSeparatorTopOffset: CGFloat = readyItem.index == 0 ? 0.0 : -UIScreenPixel let itemHighlightFrame = CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.minY + itemSeparatorTopOffset), size: CGSize(width: itemFrame.width, height: itemFrame.height - itemSeparatorTopOffset)) - itemTransition.setFrame(layer: itemView.highlightLayer, frame: itemHighlightFrame) + readyItem.itemTransition.setFrame(layer: readyItem.itemView.highlightLayer, frame: itemHighlightFrame) - itemTransition.setFrame(view: itemComponentView, frame: CGRect(origin: CGPoint(), size: itemFrame.size)) + readyItem.itemTransition.setFrame(view: itemComponentView, frame: CGRect(origin: CGPoint(), size: itemFrame.size)) let itemSeparatorFrame = CGRect(origin: CGPoint(x: separatorInset, y: itemFrame.maxY - UIScreenPixel), size: CGSize(width: availableSize.width - separatorInset, height: UIScreenPixel)) - itemTransition.setFrame(layer: itemView.separatorLayer, frame: itemSeparatorFrame) + readyItem.itemTransition.setFrame(layer: readyItem.itemView.separatorLayer, frame: itemSeparatorFrame) let separatorAlpha: CGFloat if component.displaySeparators { - if i != component.items.count - 1 { + if readyItem.index != component.items.count - 1 { separatorAlpha = 1.0 } else { separatorAlpha = 0.0 @@ -271,11 +323,12 @@ public final class ListSectionComponent: Component { } else { separatorAlpha = 0.0 } - itemTransition.setAlpha(layer: itemView.separatorLayer, alpha: separatorAlpha) - itemView.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor + readyItem.itemTransition.setAlpha(layer: readyItem.itemView.separatorLayer, alpha: separatorAlpha) + readyItem.itemView.separatorLayer.backgroundColor = component.theme.list.itemBlocksSeparatorColor.cgColor } - innerContentHeight += itemSize.height + innerContentHeight += readyItem.itemSize.height } + var removedItemIds: [AnyHashable] = [] for (id, itemView) in self.itemViews { if !validItemIds.contains(id) { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 6f0b61ee14..a8dc2403cc 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -448,7 +448,7 @@ final class MediaEditorScreenComponent: Component { self.currentInputMode = .text if hasFirstResponder(self) { if let view = self.inputPanel.view as? MessageInputPanelComponent.View { - self.nextTransitionUserData = TextFieldComponent.AnimationHint(kind: .textFocusChanged) + self.nextTransitionUserData = TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged) if view.isActive { view.deactivateInput(force: true) } else { @@ -456,7 +456,7 @@ final class MediaEditorScreenComponent: Component { } } } else { - self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(kind: .textFocusChanged))) + self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged))) } } diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index c4b9b0d6c1..e8fcbc188c 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -776,6 +776,7 @@ public final class MessageInputPanelComponent: Component { transition: .immediate, component: AnyComponent(TextFieldComponent( context: component.context, + theme: component.theme, strings: component.strings, externalState: self.textFieldExternalState, fontSize: 17.0, diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/BUILD b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/BUILD new file mode 100644 index 0000000000..02989c42f4 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/BUILD @@ -0,0 +1,48 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "BusinessIntroSetupScreen", + module_name = "BusinessIntroSetupScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/AccountContext", + "//submodules/PresentationDataUtils", + "//submodules/Markdown", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Components/ViewControllerComponent", + "//submodules/Components/BundleIconComponent", + "//submodules/Components/MultilineTextComponent", + "//submodules/Components/BalancedTextComponent", + "//submodules/TelegramUI/Components/ListSectionComponent", + "//submodules/TelegramUI/Components/ListActionItemComponent", + "//submodules/TelegramUI/Components/ListMultilineTextFieldItemComponent", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/PlainButtonComponent", + "//submodules/LocationUI", + "//submodules/AppBundle", + "//submodules/Geocoding", + "//submodules/TelegramUI/Components/Chat/ChatEmptyNode", + "//submodules/WallpaperBackgroundNode", + "//submodules/ChatPresentationInterfaceState", + "//submodules/TelegramUI/Components/EntityKeyboard", + "//submodules/TelegramUI/Components/PeerAllowedReactionsScreen", + "//submodules/TelegramUI/Components/EmojiActionIconComponent", + "//submodules/TelegramUI/Components/TextFieldComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift new file mode 100644 index 0000000000..20aaed3bd5 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift @@ -0,0 +1,699 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import PresentationDataUtils +import AccountContext +import ComponentFlow +import ViewControllerComponent +import MultilineTextComponent +import BalancedTextComponent +import ListSectionComponent +import ListActionItemComponent +import ListMultilineTextFieldItemComponent +import BundleIconComponent +import LottieComponent +import EntityKeyboard +import PeerAllowedReactionsScreen +import EmojiActionIconComponent +import TextFieldComponent + +final class BusinessIntroSetupScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + + init( + context: AccountContext + ) { + self.context = context + } + + static func ==(lhs: BusinessIntroSetupScreenComponent, rhs: BusinessIntroSetupScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + + return true + } + + private final class ScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + } + + final class View: UIView, UIScrollViewDelegate { + private let topOverscrollLayer = SimpleLayer() + private let scrollView: ScrollView + + private let navigationTitle = ComponentView() + private let introContent = ComponentView() + private let introSection = ComponentView() + private let deleteSection = ComponentView() + + private var isUpdating: Bool = false + + private var component: BusinessIntroSetupScreenComponent? + private(set) weak var state: EmptyComponentState? + private var environment: EnvironmentType? + + private let introPlaceholderTag = NSObject() + private let titleInputState = ListMultilineTextFieldItemComponent.ExternalState() + private let titleInputTag = NSObject() + private var resetTitle: String? + private let textInputState = ListMultilineTextFieldItemComponent.ExternalState() + private let textInputTag = NSObject() + private var resetText: String? + + private var recenterOnTag: NSObject? + + private var stickerFile: TelegramMediaFile? + private var stickerContent: EmojiPagerContentComponent? + private var stickerContentDisposable: Disposable? + private var displayStickerInput: Bool = false + private var stickerSelectionControl: ComponentView? + + override init(frame: CGRect) { + self.scrollView = ScrollView() + self.scrollView.showsVerticalScrollIndicator = true + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.scrollsToTop = false + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.contentInsetAdjustmentBehavior = .never + if #available(iOS 13.0, *) { + self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false + } + self.scrollView.alwaysBounceVertical = true + + super.init(frame: frame) + + self.scrollView.delegate = self + self.addSubview(self.scrollView) + + self.scrollView.layer.addSublayer(self.topOverscrollLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.stickerContentDisposable?.dispose() + } + + func scrollToTop() { + self.scrollView.setContentOffset(CGPoint(), animated: true) + } + + func attemptNavigation(complete: @escaping () -> Void) -> Bool { + guard let component = self.component, let environment = self.environment else { + return true + } + let _ = component + let _ = environment + + return true + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.updateScrolling(transition: .immediate) + } + + private var scrolledUp = true + private func updateScrolling(transition: Transition) { + let navigationRevealOffsetY: CGFloat = 0.0 + + let navigationAlphaDistance: CGFloat = 16.0 + let navigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentOffset.y - navigationRevealOffsetY) / navigationAlphaDistance)) + if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { + transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha) + transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha) + } + + var scrolledUp = false + if navigationAlpha < 0.5 { + scrolledUp = true + } else if navigationAlpha > 0.5 { + scrolledUp = false + } + + if self.scrolledUp != scrolledUp { + self.scrolledUp = scrolledUp + if !self.isUpdating { + self.state?.updated() + } + } + + if let navigationTitleView = self.navigationTitle.view { + transition.setAlpha(view: navigationTitleView, alpha: 1.0) + } + } + + func update(component: BusinessIntroSetupScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } + + if self.component == nil { + } + + if self.stickerContentDisposable == nil { + let stickerContent = EmojiPagerContentComponent.stickerInputData( + context: component.context, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer, + stickerNamespaces: [Namespaces.ItemCollection.CloudStickerPacks], + stickerOrderedItemListCollectionIds: [Namespaces.OrderedItemList.CloudSavedStickers, Namespaces.OrderedItemList.CloudRecentStickers, Namespaces.OrderedItemList.CloudAllPremiumStickers], + chatPeerId: nil, + hasSearch: false, + hasTrending: false, + forceHasPremium: true + ) + self.stickerContentDisposable = (stickerContent + |> deliverOnMainQueue).start(next: { [weak self] stickerContent in + guard let self else { + return + } + self.stickerContent = stickerContent + + stickerContent.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( + performItemAction: { [weak self] _, item, _, _, _, _ in + guard let self else { + return + } + guard let itemFile = item.itemFile else { + return + } + + self.stickerFile = itemFile + self.displayStickerInput = false + + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.25)) + } + }, + deleteBackwards: { + }, + openStickerSettings: { + }, + openFeatured: { + }, + openSearch: { + }, + addGroupAction: { _, _, _ in + }, + clearGroup: { _ in + }, + editAction: { _ in + }, + pushController: { c in + }, + presentController: { c in + }, + presentGlobalOverlayController: { c in + }, + navigationController: { + return nil + }, + requestUpdate: { _ in + }, + updateSearchQuery: { _ in + }, + updateScrollingToItemGroup: { + }, + onScroll: {}, + chatPeerId: nil, + peekBehavior: nil, + customLayout: nil, + externalBackground: nil, + externalExpansionView: nil, + customContentView: nil, + useOpaqueTheme: true, + hideBackground: false, + stateContext: nil, + addImage: nil + ) + + if !self.isUpdating { + self.state?.updated(transition: .immediate) + } + }) + } + + let environment = environment[EnvironmentType.self].value + let themeUpdated = self.environment?.theme !== environment.theme + self.environment = environment + + self.component = component + self.state = state + + let alphaTransition: Transition + if !transition.animation.isImmediate { + alphaTransition = .easeInOut(duration: 0.25) + } else { + alphaTransition = .immediate + } + + if themeUpdated { + self.backgroundColor = environment.theme.list.blocksBackgroundColor + } + + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + let _ = alphaTransition + let _ = presentationData + + //TODO:localize + let navigationTitleSize = self.navigationTitle.update( + transition: transition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "Intro", font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)), + horizontalAlignment: .center + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.statusBarHeight + floor((environment.navigationHeight - environment.statusBarHeight - navigationTitleSize.height) / 2.0)), size: navigationTitleSize) + if let navigationTitleView = self.navigationTitle.view { + if navigationTitleView.superview == nil { + if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar { + navigationBar.view.addSubview(navigationTitleView) + } + } + transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame) + } + + let bottomContentInset: CGFloat = 24.0 + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let sectionSpacing: CGFloat = 24.0 + + var contentHeight: CGFloat = 0.0 + + contentHeight += environment.navigationHeight + contentHeight += 26.0 + + self.recenterOnTag = nil + if let hint = transition.userData(TextFieldComponent.AnimationHint.self), let targetView = hint.view { + if let titleView = self.introSection.findTaggedView(tag: self.titleInputTag) { + if targetView.isDescendant(of: titleView) { + self.recenterOnTag = self.titleInputTag + } + } + if let textView = self.introSection.findTaggedView(tag: self.textInputTag) { + if targetView.isDescendant(of: textView) { + self.recenterOnTag = self.textInputTag + } + } + } + + var introSectionItems: [AnyComponentWithIdentity] = [] + introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(Rectangle(color: .clear, height: 346.0, tag: self.introPlaceholderTag)))) + introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent( + externalState: self.titleInputState, + context: component.context, + theme: environment.theme, + strings: environment.strings, + initialText: "", + resetText: self.resetTitle.flatMap { + return ListMultilineTextFieldItemComponent.ResetText(value: $0) + }, + placeholder: "Enter Title", + autocapitalizationType: .none, + autocorrectionType: .no, + characterLimit: 32, + allowEmptyLines: false, + updated: { _ in + }, + textUpdateTransition: .spring(duration: 0.4), + tag: self.titleInputTag + )))) + self.resetTitle = nil + introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListMultilineTextFieldItemComponent( + externalState: self.textInputState, + context: component.context, + theme: environment.theme, + strings: environment.strings, + initialText: "", + resetText: self.resetText.flatMap { + return ListMultilineTextFieldItemComponent.ResetText(value: $0) + }, + placeholder: "Enter Message", + autocapitalizationType: .none, + autocorrectionType: .no, + characterLimit: 70, + allowEmptyLines: false, + updated: { _ in + }, + textUpdateTransition: .spring(duration: 0.4), + tag: self.textInputTag + )))) + self.resetText = nil + + let stickerIcon: ListActionItemComponent.Icon + if let stickerFile = self.stickerFile { + stickerIcon = ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(EmojiActionIconComponent( + context: component.context, + color: environment.theme.list.itemPrimaryTextColor, + fileId: stickerFile.fileId.id, + file: stickerFile + )))) + } else { + stickerIcon = ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 1, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Random", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )))) + } + + introSectionItems.append(AnyComponentWithIdentity(id: introSectionItems.count, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Choose Sticker", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + icon: stickerIcon, + accessory: .none, + action: { [weak self] _ in + guard let self else { + return + } + + self.displayStickerInput = true + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.5)) + } + } + )))) + let introSectionSize = self.introSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "CUSTOMIZE YOUR INTRO", + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: environment.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + footer: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "You can customize the message people see before they start a chat with you.", + font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), + textColor: environment.theme.list.freeTextColor + )), + maximumNumberOfLines: 0 + )), + items: introSectionItems, + itemUpdateOrder: introSectionItems.map(\.id).reversed() + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let introSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: introSectionSize) + if let introSectionView = self.introSection.view { + if introSectionView.superview == nil { + self.scrollView.addSubview(introSectionView) + self.introSection.parentState = state + } + transition.setFrame(view: introSectionView, frame: introSectionFrame) + } + contentHeight += introSectionSize.height + contentHeight += sectionSpacing + + let titleText: String + if self.titleInputState.text.string.isEmpty { + titleText = "No messages here yet..." + } else { + titleText = self.titleInputState.text.string + } + + let textText: String + if self.textInputState.text.string.isEmpty { + textText = "Send a message or tap on the greeting below" + } else { + textText = self.textInputState.text.string + } + + let introContentSize = self.introContent.update( + transition: transition, + component: AnyComponent(ChatIntroItemComponent( + context: component.context, + theme: environment.theme, + strings: environment.strings, + stickerFile: stickerFile, + title: titleText, + text: textText + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + if let introContentView = self.introContent.view { + if introContentView.superview == nil { + if let placeholderView = self.introSection.findTaggedView(tag: self.introPlaceholderTag) { + placeholderView.addSubview(introContentView) + } + } + transition.setFrame(view: introContentView, frame: CGRect(origin: CGPoint(), size: introContentSize)) + } + + let displayDelete = !self.titleInputState.text.string.isEmpty || !self.textInputState.text.string.isEmpty || self.stickerFile != nil + + var deleteSectionHeight: CGFloat = 0.0 + deleteSectionHeight += sectionSpacing + let deleteSectionSize = self.deleteSection.update( + transition: transition, + component: AnyComponent(ListSectionComponent( + theme: environment.theme, + header: nil, + footer: nil, + items: [ + AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Reset to Default", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemDestructiveColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .center, spacing: 2.0, fillWidth: true)), + accessory: nil, + action: { [weak self] _ in + guard let self else { + return + } + + self.resetTitle = "" + self.resetText = "" + self.stickerFile = nil + self.state?.updated(transition: .spring(duration: 0.4)) + } + ))) + ], + displaySeparators: false + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0) + ) + let deleteSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight + deleteSectionHeight), size: deleteSectionSize) + if let deleteSectionView = self.deleteSection.view { + if deleteSectionView.superview == nil { + self.scrollView.addSubview(deleteSectionView) + } + transition.setFrame(view: deleteSectionView, frame: deleteSectionFrame) + + if displayDelete { + alphaTransition.setAlpha(view: deleteSectionView, alpha: 1.0) + } else { + alphaTransition.setAlpha(view: deleteSectionView, alpha: 0.0) + } + } + deleteSectionHeight += deleteSectionSize.height + if displayDelete { + contentHeight += deleteSectionHeight + } + + contentHeight += bottomContentInset + + var inputHeight: CGFloat = environment.inputHeight + if self.displayStickerInput, let stickerContent = self.stickerContent { + let stickerSelectionControl: ComponentView + var animateIn = false + if let current = self.stickerSelectionControl { + stickerSelectionControl = current + } else { + animateIn = true + stickerSelectionControl = ComponentView() + self.stickerSelectionControl = stickerSelectionControl + } + var selectedItems = Set() + if let stickerFile = self.stickerFile { + selectedItems.insert(stickerFile.fileId) + } + let stickerSelectionControlSize = stickerSelectionControl.update( + transition: animateIn ? .immediate : transition, + component: AnyComponent(EmojiSelectionComponent( + theme: environment.theme, + strings: environment.strings, + sideInset: environment.safeInsets.left, + bottomInset: environment.safeInsets.bottom, + deviceMetrics: environment.deviceMetrics, + emojiContent: stickerContent.withSelectedItems(selectedItems), + backgroundIconColor: nil, + backgroundColor: environment.theme.list.itemBlocksBackgroundColor, + separatorColor: environment.theme.list.itemBlocksSeparatorColor, + backspace: nil + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: min(340.0, max(50.0, availableSize.height - 200.0))) + ) + let stickerSelectionControlFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - stickerSelectionControlSize.height), size: stickerSelectionControlSize) + if let stickerSelectionControlView = stickerSelectionControl.view { + if stickerSelectionControlView.superview == nil { + self.addSubview(stickerSelectionControlView) + } + if animateIn { + stickerSelectionControlView.frame = stickerSelectionControlFrame + transition.animatePosition(view: stickerSelectionControlView, from: CGPoint(x: 0.0, y: stickerSelectionControlFrame.height), to: CGPoint(), additive: true) + } else { + transition.setFrame(view: stickerSelectionControlView, frame: stickerSelectionControlFrame) + } + } + inputHeight = stickerSelectionControlSize.height + } else if let stickerSelectionControl = self.stickerSelectionControl { + self.stickerSelectionControl = nil + if let stickerSelectionControlView = stickerSelectionControl.view { + transition.setPosition(view: stickerSelectionControlView, position: CGPoint(x: stickerSelectionControlView.center.x, y: availableSize.height + stickerSelectionControlView.bounds.height * 0.5), completion: { [weak stickerSelectionControlView] _ in + stickerSelectionControlView?.removeFromSuperview() + }) + } + } + + let combinedBottomInset = max(inputHeight, environment.safeInsets.bottom) + contentHeight += combinedBottomInset + + let previousBounds = self.scrollView.bounds + + let contentSize = CGSize(width: availableSize.width, height: contentHeight) + if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) { + self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize) + } + if self.scrollView.contentSize != contentSize { + self.scrollView.contentSize = contentSize + } + let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: 0.0, right: 0.0) + if self.scrollView.scrollIndicatorInsets != scrollInsets { + self.scrollView.scrollIndicatorInsets = scrollInsets + } + + if !previousBounds.isEmpty, !transition.animation.isImmediate { + let bounds = self.scrollView.bounds + if bounds.maxY != previousBounds.maxY { + let offsetY = previousBounds.maxY - bounds.maxY + transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true) + } + } + + if let recenterOnTag = self.recenterOnTag { + self.recenterOnTag = nil + + if let targetView = self.introSection.findTaggedView(tag: recenterOnTag) { + let caretRect = targetView.convert(targetView.bounds, to: self.scrollView) + var scrollViewBounds = self.scrollView.bounds + let minButtonDistance: CGFloat = 16.0 + if -scrollViewBounds.minY + caretRect.maxY > availableSize.height - combinedBottomInset - minButtonDistance { + scrollViewBounds.origin.y = -(availableSize.height - combinedBottomInset - minButtonDistance - caretRect.maxY) + if scrollViewBounds.origin.y < 0.0 { + scrollViewBounds.origin.y = 0.0 + } + } + if self.scrollView.bounds != scrollViewBounds { + transition.setBounds(view: self.scrollView, bounds: scrollViewBounds) + } + } + } + + self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0)) + + self.updateScrolling(transition: transition) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class BusinessIntroSetupScreen: ViewControllerComponentContainer { + private let context: AccountContext + + public init( + context: AccountContext + ) { + self.context = context + + super.init(context: context, component: BusinessIntroSetupScreenComponent( + context: context + ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.title = "" + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) + + self.scrollToTop = { [weak self] in + guard let self, let componentView = self.node.hostView.componentView as? BusinessIntroSetupScreenComponent.View else { + return + } + componentView.scrollToTop() + } + + self.attemptNavigation = { [weak self] complete in + guard let self, let componentView = self.node.hostView.componentView as? BusinessIntroSetupScreenComponent.View else { + return true + } + + return componentView.attemptNavigation(complete: complete) + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + } + + @objc private func cancelPressed() { + self.dismiss() + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift new file mode 100644 index 0000000000..ada5480c79 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/ChatIntroItemComponent.swift @@ -0,0 +1,161 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import ListSectionComponent +import TelegramPresentationData +import AppBundle +import AccountContext +import ChatEmptyNode +import AsyncDisplayKit +import WallpaperBackgroundNode +import ComponentDisplayAdapters +import TelegramCore +import ChatPresentationInterfaceState + +final class ChatIntroItemComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let stickerFile: TelegramMediaFile? + let title: String + let text: String + + init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + stickerFile: TelegramMediaFile?, + title: String, + text: String + ) { + self.context = context + self.theme = theme + self.strings = strings + self.stickerFile = stickerFile + self.title = title + self.text = text + } + + static func ==(lhs: ChatIntroItemComponent, rhs: ChatIntroItemComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.stickerFile != rhs.stickerFile { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.text != rhs.text { + return false + } + return true + } + + final class View: UIView, ListSectionComponent.ChildView { + private var component: ChatIntroItemComponent? + private weak var componentState: EmptyComponentState? + + private var backgroundNode: WallpaperBackgroundNode? + private var emptyNode: ChatEmptyNode? + + var customUpdateIsHighlighted: ((Bool) -> Void)? + private(set) var separatorInset: CGFloat = 0.0 + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ChatIntroItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + self.componentState = state + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }) + + let size = CGSize(width: availableSize.width, height: 346.0) + + let backgroundNode: WallpaperBackgroundNode + if let current = self.backgroundNode { + backgroundNode = current + } else { + backgroundNode = createWallpaperBackgroundNode(context: component.context, forChatDisplay: false) + self.backgroundNode = backgroundNode + self.addSubview(backgroundNode.view) + } + + transition.setFrame(view: backgroundNode.view, frame: CGRect(origin: CGPoint(), size: size)) + backgroundNode.update(wallpaper: presentationData.chatWallpaper, animated: false) + backgroundNode.updateLayout(size: size, displayMode: .aspectFill, transition: transition.containedViewLayoutTransition) + + let emptyNode: ChatEmptyNode + if let current = self.emptyNode { + emptyNode = current + } else { + emptyNode = ChatEmptyNode(context: component.context, interaction: nil) + self.emptyNode = emptyNode + self.addSubview(emptyNode.view) + } + + let interfaceState = ChatPresentationInterfaceState( + chatWallpaper: presentationData.chatWallpaper, + theme: component.theme, + strings: component.strings, + dateTimeFormat: presentationData.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + limitsConfiguration: component.context.currentLimitsConfiguration.with { $0 }, + fontSize: presentationData.chatFontSize, + bubbleCorners: presentationData.chatBubbleCorners, + accountPeerId: component.context.account.peerId, + mode: .standard(.default), + chatLocation: .peer(id: component.context.account.peerId), + subject: nil, + peerNearbyData: nil, + greetingData: nil, + pendingUnpinnedAllMessages: false, + activeGroupCallInfo: nil, + hasActiveGroupCall: false, + importState: nil, + threadData: nil, + isGeneralThreadClosed: nil, + replyMessage: nil, + accountPeerColor: nil + ) + + transition.setFrame(view: emptyNode.view, frame: CGRect(origin: CGPoint(), size: size)) + emptyNode.updateLayout( + interfaceState: interfaceState, + subject: .emptyChat(.customGreeting( + sticker: component.stickerFile, + title: component.title, + text: component.text + )), + loadingNode: nil, + backgroundNode: backgroundNode, + size: size, + insets: UIEdgeInsets(), + transition: .immediate + ) + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift index 95f51e3f2c..5e200654a7 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessLocationSetupScreen/Sources/BusinessLocationSetupScreen.swift @@ -509,7 +509,6 @@ final class BusinessLocationSetupScreenComponent: Component { contentHeight += mapSectionSize.height var deleteSectionHeight: CGFloat = 0.0 - deleteSectionHeight += sectionSpacing let deleteSectionSize = self.deleteSection.update( transition: transition, diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD index 56a50b2be2..59f5675707 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD @@ -51,6 +51,7 @@ swift_library( "//submodules/TelegramUI/Components/GroupStickerPackSetupController", "//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl", "//submodules/TelegramUI/Components/Settings/PeerNameColorItem", + "//submodules/TelegramUI/Components/EmojiActionIconComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index e805cbd4b7..6080858a9b 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -38,100 +38,7 @@ import BundleIconComponent import Markdown import GroupStickerPackSetupController import PeerNameColorItem - -private final class EmojiActionIconComponent: Component { - let context: AccountContext - let color: UIColor - let fileId: Int64? - let file: TelegramMediaFile? - - init( - context: AccountContext, - color: UIColor, - fileId: Int64?, - file: TelegramMediaFile? - ) { - self.context = context - self.color = color - self.fileId = fileId - self.file = file - } - - static func ==(lhs: EmojiActionIconComponent, rhs: EmojiActionIconComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.color != rhs.color { - return false - } - if lhs.fileId != rhs.fileId { - return false - } - if lhs.file != rhs.file { - return false - } - return true - } - - final class View: UIView { - private var icon: ComponentView? - - func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - let size = CGSize(width: 24.0, height: 24.0) - - if let fileId = component.fileId { - let icon: ComponentView - if let current = self.icon { - icon = current - } else { - icon = ComponentView() - self.icon = icon - } - let _ = icon.update( - transition: .immediate, - component: AnyComponent(EmojiStatusComponent( - context: component.context, - animationCache: component.context.animationCache, - animationRenderer: component.context.animationRenderer, - content: .animation( - content: .customEmoji(fileId: fileId), - size: size, - placeholderColor: .lightGray, - themeColor: component.color, - loopMode: .forever - ), - isVisibleForAnimations: false, - action: nil - )), - environment: {}, - containerSize: size - ) - let iconFrame = CGRect(origin: CGPoint(), size: size) - if let iconView = icon.view { - if iconView.superview == nil { - self.addSubview(iconView) - } - iconView.frame = iconFrame - } - } else { - if let icon = self.icon { - self.icon = nil - icon.view?.removeFromSuperview() - } - } - - return size - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} +import EmojiActionIconComponent final class ChannelAppearanceScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index 42c75a2a00..7bc8d4a842 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -209,8 +209,31 @@ final class StoryContentCaptionComponent: Component { override init(frame: CGRect) { self.shadowGradientView = UIImageView() - if let image = StoryContentCaptionComponent.View.shadowImage { - self.shadowGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0)) + if let _ = StoryContentCaptionComponent.View.shadowImage { + let height: CGFloat = 128.0 + let baseGradientAlpha: CGFloat = 0.8 + let numSteps = 8 + let firstStep = 0 + let firstLocation = 0.0 + let colors = (0 ..< numSteps).map { i -> UIColor in + if i < firstStep { + return UIColor(white: 1.0, alpha: 1.0) + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step) + return UIColor(white: 0.0, alpha: baseGradientAlpha * value) + } + } + let locations = (0 ..< numSteps).map { i -> CGFloat in + if i < firstStep { + return 0.0 + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + return (firstLocation + (1.0 - firstLocation) * step) + } + } + + self.shadowGradientView.image = generateGradientImage(size: CGSize(width: 8.0, height: height), colors: colors.reversed(), locations: locations.reversed().map { 1.0 - $0 })!.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(height - 1.0)) } self.scrollViewContainer = UIView() @@ -386,7 +409,8 @@ final class StoryContentCaptionComponent: Component { transition.setBounds(view: self.textSelectionKnobContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.scrollView.bounds.minY), size: CGSize())) - let shadowOverflow: CGFloat = 58.0 + let shadowHeight: CGFloat = self.shadowGradientView.image?.size.height ?? 100.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 shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0)) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 26853ef250..ff1731e6cb 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -905,7 +905,7 @@ public final class StoryItemSetContainerComponent: Component { if hasFirstResponder(self) { view.deactivateInput() } else { - self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(kind: .textFocusChanged))) + self.state?.updated(transition: .spring(duration: 0.4).withUserData(TextFieldComponent.AnimationHint(view: nil, kind: .textFocusChanged))) } } } @@ -2690,7 +2690,7 @@ public final class StoryItemSetContainerComponent: Component { self.bottomContentGradientLayer.colors = colors self.bottomContentGradientLayer.type = .axial - self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3) + self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.8) } let wasPanning = self.component?.isPanning ?? false diff --git a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift index 7fa657812c..e5fad16c8a 100644 --- a/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift +++ b/submodules/TelegramUI/Components/TextFieldComponent/Sources/TextFieldComponent.swift @@ -27,6 +27,7 @@ public final class TextFieldComponent: Component { public final class ExternalState { public fileprivate(set) var isEditing: Bool = false public fileprivate(set) var hasText: Bool = false + public fileprivate(set) var text: NSAttributedString = NSAttributedString() public fileprivate(set) var textLength: Int = 0 public var initialText: NSAttributedString? @@ -73,9 +74,11 @@ public final class TextFieldComponent: Component { case textFocusChanged } + public weak var view: View? public let kind: Kind - public init(kind: Kind) { + public init(view: View?, kind: Kind) { + self.view = view self.kind = kind } } @@ -87,6 +90,7 @@ public final class TextFieldComponent: Component { } public let context: AccountContext + public let theme: PresentationTheme public let strings: PresentationStrings public let externalState: ExternalState public let fontSize: CGFloat @@ -105,6 +109,7 @@ public final class TextFieldComponent: Component { public init( context: AccountContext, + theme: PresentationTheme, strings: PresentationStrings, externalState: ExternalState, fontSize: CGFloat, @@ -122,6 +127,7 @@ public final class TextFieldComponent: Component { paste: @escaping (PasteData) -> Void ) { self.context = context + self.theme = theme self.strings = strings self.externalState = externalState self.fontSize = fontSize @@ -140,6 +146,12 @@ public final class TextFieldComponent: Component { } public static func ==(lhs: TextFieldComponent, rhs: TextFieldComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } if lhs.strings !== rhs.strings { return false } @@ -219,7 +231,6 @@ public final class TextFieldComponent: Component { self.textView.translatesAutoresizingMaskIntoConstraints = false self.textView.backgroundColor = nil self.textView.layer.isOpaque = false - self.textView.keyboardAppearance = .dark self.textView.indicatorStyle = .white self.textView.scrollIndicatorInsets = UIEdgeInsets(top: 9.0, left: 0.0, bottom: 9.0, right: 0.0) @@ -232,10 +243,6 @@ public final class TextFieldComponent: Component { self.textView.customDelegate = self self.addSubview(self.textView) - if #available(iOS 13.0, *) { - self.textView.overrideUserInterfaceStyle = .dark - } - self.textView.typingAttributes = [ NSAttributedString.Key.font: Font.regular(17.0), NSAttributedString.Key.foregroundColor: UIColor.white @@ -265,7 +272,7 @@ public final class TextFieldComponent: Component { self.updateEntities() if currentAttributedText != updatedAttributedText && !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textChanged))) } } @@ -278,7 +285,7 @@ public final class TextFieldComponent: Component { return state.insertText(text) } if !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textChanged))) } } @@ -291,7 +298,7 @@ public final class TextFieldComponent: Component { return TextFieldComponent.InputState(inputText: text, selectionRange: selectionRange) } if !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textChanged))) } } @@ -319,7 +326,7 @@ public final class TextFieldComponent: Component { } } if !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textChanged))) } component.paste(.text) return false @@ -390,7 +397,7 @@ public final class TextFieldComponent: Component { self.updateEntities() if !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textChanged))) } } @@ -412,7 +419,7 @@ public final class TextFieldComponent: Component { return } if !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(kind: .textFocusChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textFocusChanged))) } if component.isOneLineWhenUnfocused { Queue.mainQueue().justDispatch { @@ -423,7 +430,7 @@ public final class TextFieldComponent: Component { public func chatInputTextNodeDidFinishEditing() { if !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(kind: .textFocusChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.5, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textFocusChanged))) } } @@ -724,7 +731,7 @@ public final class TextFieldComponent: Component { } } - let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkColorPresentationTheme) + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: component.theme) let updatedPresentationData: (initial: PresentationData, signal: Signal) = (presentationData, .single(presentationData)) let controller = chatTextLinkEditController(sharedContext: component.context.sharedContext, updatedPresentationData: updatedPresentationData, account: component.context.account, text: text.string, link: link, apply: { [weak self] link in if let self { @@ -757,7 +764,7 @@ public final class TextFieldComponent: Component { return TextFieldComponent.InputState(inputText: string, selectionRange: string.length ..< string.length) } if updateState && !self.isUpdating { - self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(kind: .textChanged))) + self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring)).withUserData(AnimationHint(view: self, kind: .textChanged))) } } @@ -1048,9 +1055,17 @@ public final class TextFieldComponent: Component { self.isUpdating = false } + let previousComponent = self.component self.component = component self.state = state + if previousComponent?.theme !== component.theme { + self.textView.keyboardAppearance = component.theme.overallDarkAppearance ? .dark : .light + if #available(iOS 13.0, *) { + self.textView.overrideUserInterfaceStyle = component.theme.overallDarkAppearance ? .dark : .light + } + } + if let initialText = component.externalState.initialText { component.externalState.initialText = nil self.updateInputState { _ in @@ -1128,6 +1143,7 @@ public final class TextFieldComponent: Component { component.externalState.hasText = self.textView.textStorage.length != 0 component.externalState.isEditing = isEditing component.externalState.textLength = self.textView.textStorage.string.count + component.externalState.text = NSAttributedString(attributedString: self.textView.textStorage) if let inputView = component.customInputView { if self.textView.inputView == nil { diff --git a/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json new file mode 100644 index 0000000000..9d3f9124cb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "more.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf b/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf new file mode 100644 index 0000000000..82da70ea58 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf @@ -0,0 +1,79 @@ +%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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Contents.json new file mode 100644 index 0000000000..5d2b29a449 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Icon.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Icon.png b/submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Icon.png new file mode 100644 index 0000000000..3c1a635d77 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Icon.png differ diff --git a/submodules/TelegramUI/Resources/Animations/anim_speed_low.tgs b/submodules/TelegramUI/Resources/Animations/anim_speed_low.tgs new file mode 100644 index 0000000000..bb9b6f86c6 Binary files /dev/null and b/submodules/TelegramUI/Resources/Animations/anim_speed_low.tgs differ diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2680cff310..53551f799d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -121,6 +121,8 @@ import TopMessageReactions import PeerInfoScreen import AudioWaveform import PeerNameColorScreen +import ChatEmptyNode +import ChatMediaInputStickerGridItem public enum ChatControllerPeekActions { case standard @@ -586,6 +588,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)? var performOpenURL: ((Message?, String, Promise?) -> Void)? + var networkSpeedEventsDisposable: Disposable? + public var alwaysShowSearchResultsAsList: Bool = false { didSet { self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) @@ -4907,6 +4911,31 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } + let managingBot: Signal + if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser { + managingBot = self.context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.ChatManagingBot(id: peerId) + ) + |> mapToSignal { result -> Signal 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 + } + + return ChatManagingBot(bot: botPeer, isPaused: result.isPaused, canReply: result.canReply, settingsUrl: result.manageUrl) + } + } + |> distinctUntilChanged + } else { + managingBot = .single(nil) + } + do { let peerId = chatLocationPeerId if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId { @@ -5295,10 +5324,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G threadInfo, hasSearchTags, hasSavedChats, - isPremiumRequiredForMessaging - ).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in + isPremiumRequiredForMessaging, + managingBot + ).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in if let strongSelf = self { - if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging { + 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 { return } @@ -5393,7 +5423,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var contactStatus: ChatContactStatus? if let peer = peerView.peers[peerView.peerId] { if let cachedData = peerView.cachedData as? CachedUserData { - contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) + contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot) } else if let cachedData = peerView.cachedData as? CachedGroupData { var invitedBy: Peer? if let invitedByPeerId = cachedData.invitedBy { @@ -5401,7 +5431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G invitedBy = peer } } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) } else if let cachedData = peerView.cachedData as? CachedChannelData { var canReportIrrelevantLocation = true if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { @@ -5416,7 +5446,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G invitedBy = peer } } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) } var peers = SimpleDictionary() @@ -5519,6 +5549,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } + if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil { + didDisplayActionsPanel = true + } if strongSelf.presentationInterfaceState.search != nil && strongSelf.presentationInterfaceState.hasSearchTags { didDisplayActionsPanel = true } @@ -5539,6 +5572,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } + if let contactStatus, contactStatus.managingBot != nil { + displayActionsPanel = true + } if strongSelf.presentationInterfaceState.search != nil && hasSearchTags { displayActionsPanel = true } @@ -5872,9 +5908,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G hasScheduledMessages, hasSearchTags, hasSavedChats, - isPremiumRequiredForMessaging + isPremiumRequiredForMessaging, + managingBot ) - |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in + |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in if let strongSelf = self { strongSelf.hasScheduledMessages = hasScheduledMessages @@ -5884,7 +5921,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let peer = peerView.peers[peerView.peerId] { copyProtectionEnabled = peer.isCopyProtectionEnabled if let cachedData = peerView.cachedData as? CachedUserData { - contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil) + contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot) } else if let cachedData = peerView.cachedData as? CachedGroupData { var invitedBy: Peer? if let invitedByPeerId = cachedData.invitedBy { @@ -5892,7 +5929,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G invitedBy = peer } } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) } else if let cachedData = peerView.cachedData as? CachedChannelData { var canReportIrrelevantLocation = true if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member { @@ -5905,7 +5942,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G invitedBy = peer } } - contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy) + contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot) } var peers = SimpleDictionary() @@ -6109,6 +6146,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } + if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil { + didDisplayActionsPanel = true + } var displayActionsPanel = false if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { @@ -6126,6 +6166,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } + if let contactStatus, contactStatus.managingBot != nil { + displayActionsPanel = true + } if displayActionsPanel != didDisplayActionsPanel { animated = true @@ -6797,6 +6840,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.preloadSavedMessagesChatsDisposable?.dispose() self.recorderDataDisposable.dispose() self.displaySendWhenOnlineTipDisposable.dispose() + self.networkSpeedEventsDisposable?.dispose() } deallocate() } @@ -11686,6 +11730,57 @@ 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() } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index bb6444f4e8..fd487a266f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -42,6 +42,7 @@ import UIKitRuntimeUtils import ChatInlineSearchResultsListComponent import ComponentDisplayAdapters import ComponentFlow +import ChatEmptyNode final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -990,7 +991,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.emptyNode = emptyNode self.historyNodeContainer.supernode?.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer) if let (size, insets) = self.validEmptyNodeLayout { - emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(emptyType), loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate) + let mappedType: ChatEmptyNode.Subject.EmptyType + switch emptyType { + case .generic: + mappedType = .generic + case .joined: + mappedType = .joined + case .clearedHistory: + mappedType = .clearedHistory + case .topic: + mappedType = .topic + case .botInfo: + mappedType = .botInfo + } + emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(mappedType), loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate) } if animated { emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -1842,7 +1856,20 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { emptyNodeInsets.bottom += inputPanelsHeight self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets) if let emptyNode = self.emptyNode, let emptyType = self.emptyType { - emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(emptyType), loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition) + let mappedType: ChatEmptyNode.Subject.EmptyType + switch emptyType { + case .generic: + mappedType = .generic + case .joined: + mappedType = .joined + case .clearedHistory: + mappedType = .clearedHistory + case .topic: + mappedType = .topic + case .botInfo: + mappedType = .botInfo + } + emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, subject: .emptyChat(mappedType), loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition) transition.updateFrame(node: emptyNode, frame: contentBounds) emptyNode.update(rect: contentBounds, within: contentBounds.size, transition: transition) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index d90cc96063..694a5266a2 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -117,32 +117,44 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat } var displayActionsPanel = false - if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.suggestAddMembers) { + if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus { + if let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + displayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.suggestAddMembers) { + displayActionsPanel = true + } + } + if peerStatusSettings.requestChatTitle != nil { displayActionsPanel = true } } - if peerStatusSettings.requestChatTitle != nil { - displayActionsPanel = true - } } - if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) { - if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode { - return currentPanel - } else if let controllerInteraction = controllerInteraction { - let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer) - panel.interfaceInteraction = interfaceInteraction - return panel + if (selectedContext == nil || selectedContext! <= .pinnedMessage) { + if displayActionsPanel { + if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode { + return currentPanel + } else if let controllerInteraction = controllerInteraction { + let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer) + panel.interfaceInteraction = interfaceInteraction + return panel + } + } else if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, contactStatus.managingBot != nil { + if let currentPanel = currentPanel as? ChatManagingBotTitlePanelNode { + return currentPanel + } else { + let panel = ChatManagingBotTitlePanelNode(context: context) + panel.interfaceInteraction = interfaceInteraction + return panel + } } } diff --git a/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift new file mode 100644 index 0000000000..bab56dccb2 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift @@ -0,0 +1,448 @@ +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() + private let text = ComponentView() + private var avatarNode: AvatarNode? + private let actionButton = ComponentView() + private let settingsButton = ComponentView() + + 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, 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) + ) + + var maxTextWidth: CGFloat = availableSize.width - leftInset - avatarDiameter - avatarTextSpacing - rightInset - settingsButtonSize.width - 8.0 + if component.managesChat { + maxTextWidth -= actionButtonSize.width - actionAndSettingsButtonsSpacing + } + + 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: component.managesChat ? "bot manages this chat" : "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) + transition.setAlpha(view: actionButtonView, alpha: component.managesChat ? 1.0 : 0.0) + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, 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() + + 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.peers.toggleChatManagingBotIsPaused(chatId: chatPeerId) + } + + private func openSettingsMenu(sourceView: UIView) { + guard let interfaceInteraction = self.interfaceInteraction else { + return + } + guard let chatController = interfaceInteraction.chatController() else { + return + } + guard let chatPeerId = self.chatLocation?.peerId else { + return + } + guard let managingBot = self.managingBot else { + return + } + + 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 + } + self.context.engine.peers.removeChatManagingBot(chatId: chatPeerId) + }))) + 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) + } +} diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 9b4547a611..6033787e87 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -19,6 +19,8 @@ import ChatMessageInstantVideoItemNode import ChatMessageAnimatedStickerItemNode import ChatMessageTransitionNode import ChatMessageBubbleItemNode +import ChatEmptyNode +import ChatMediaInputStickerGridItem private func convertAnimatingSourceRect(_ rect: CGRect, fromView: UIView, toView: UIView?) -> CGRect { if let presentationLayer = fromView.layer.presentation() { diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index bdbd94f00c..a5cc74ff9f 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -213,6 +213,20 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } private func updateTitle() { + var updatedCount: Int = 0 + switch self.contactsNode.contentNode { + case let .contacts(contactsNode): + if let selectionState = contactsNode.selectionState { + updatedCount = selectionState.selectedPeerIndices.count + } + case let .chats(chatsNode): + chatsNode.updateState { state in + updatedCount = state.selectedPeerIds.count + return state + } + break + } + switch self.mode { case .groupCreation: let maxCount: Int32 = self.limitsConfiguration?.maxSupergroupMemberCount ?? 5000 @@ -227,7 +241,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed)) self.rightNavigationButton = rightNavigationButton self.navigationItem.rightBarButtonItem = self.rightNavigationButton - rightNavigationButton.isEnabled = true //count != 0 || self.params.alwaysEnabled + rightNavigationButton.isEnabled = true case .premiumGifting: let maxCount: Int32 = self.limit ?? 10 var count = 0 @@ -263,6 +277,13 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection self.navigationItem.rightBarButtonItem = self.rightNavigationButton rightNavigationButton.isEnabled = self.params.alwaysEnabled } + + switch self.mode { + case .groupCreation, .peerSelection, .chatSelection: + self.rightNavigationButton?.isEnabled = updatedCount != 0 || !self.contactsNode.editableTokens.isEmpty || self.params.alwaysEnabled + case .channelCreation, .premiumGifting, .requestedUsersSelection: + break + } } override func loadDisplayNode() { @@ -355,29 +376,6 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } } - if let updatedCount = updatedCount { - switch strongSelf.mode { - case .groupCreation, .peerSelection, .chatSelection: - strongSelf.rightNavigationButton?.isEnabled = updatedCount != 0 || strongSelf.params.alwaysEnabled - case .channelCreation, .premiumGifting, .requestedUsersSelection: - break - } - - switch strongSelf.mode { - case .groupCreation: - let maxCount: Int32 = strongSelf.limitsConfiguration?.maxSupergroupMemberCount ?? 5000 - strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Compose_NewGroupTitle, counter: "\(updatedCount)/\(maxCount)") - case .premiumGifting: - let maxCount: Int32 = strongSelf.limit ?? 10 - strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.Premium_Gift_ContactSelection_Title, counter: "\(updatedCount)/\(maxCount)") - case .requestedUsersSelection: - let maxCount: Int32 = strongSelf.limit ?? 10 - strongSelf.titleView.title = CounterContollerTitle(title: strongSelf.presentationData.strings.RequestPeer_SelectUsers, counter: "\(updatedCount)/\(maxCount)") - case .peerSelection, .channelCreation, .chatSelection: - break - } - } - if let addedToken = addedToken { strongSelf.contactsNode.editableTokens.append(addedToken) } else if let removedTokenId = removedTokenId { @@ -385,8 +383,13 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection return token.id != removedTokenId } } + + let _ = updatedCount + strongSelf.requestLayout(transition: ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)) + strongSelf.updateTitle() + if displayCountAlert { strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.CreateGroup_SoftUserLimitAlert, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } @@ -503,6 +506,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } } strongSelf.requestLayout(transition: ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)) + + strongSelf.updateTitle() } } @@ -530,6 +535,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } strongSelf.requestLayout(transition: ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)) } + + strongSelf.updateTitle() } self.contactsNode.additionalCategorySelected = { [weak self] id in @@ -593,6 +600,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } } strongSelf.requestLayout(transition: ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)) + + strongSelf.updateTitle() } } self.contactsNode.complete = { [weak self] in diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index 3090f84e46..75da214999 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -140,6 +140,18 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { excludePeers: [], color: nil )) + } else if chatSelection.disableChannels { + chatListFilter = .filter(id: Int32.max, title: "", emoticon: nil, data: ChatListFilterData( + isShared: false, + hasSharedLinks: false, + categories: [.contacts, .nonContacts, .groups, .bots], + excludeMuted: false, + excludeRead: false, + excludeArchived: false, + includePeers: ChatListFilterIncludePeers(), + excludePeers: [], + color: nil + )) } placeholder = placeholderValue @@ -290,7 +302,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { } else { searchChatList = true searchGroups = true - searchChannels = true + searchChannels = !chatSelection.disableChannels } globalSearch = false case .premiumGifting, .requestedUsersSelection: diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 3d3a5bb2db..a2c0cc7964 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -59,6 +59,7 @@ import CollectibleItemInfoScreen import StickerPickerScreen import MediaEditor import MediaEditorScreen +import BusinessIntroSetupScreen private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -1927,6 +1928,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { return QuickReplySetupScreen.initialData(context: context) } + public func makeBusinessIntroSetupScreen(context: AccountContext) -> ViewController { + return BusinessIntroSetupScreen(context: context) + } + public func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController { return CollectibleItemInfoScreen(context: context, initialData: initialData as! CollectibleItemInfoScreen.InitialData) } @@ -2228,7 +2233,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { let presence = current.presence var disabledFor: [PeerId: SelectivePrivacyPeer] = [:] switch presence { - case let .enableEveryone(disabledForValue), let .enableContacts(_, disabledForValue): + case let .enableEveryone(disabledForValue), let .enableContacts(_, disabledForValue, _): disabledFor = disabledForValue default: break diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index aefb623683..75668dbb9c 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -30,6 +30,7 @@ import MediaEditor import PeerInfoScreen import PeerInfoStoryGridScreen import ShareWithPeersScreen +import ChatEmptyNode private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode { private var presentationData: PresentationData diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 4991caf7a8..eeeeb4784a 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1074,8 +1074,8 @@ public final class WebAppController: ViewController, AttachmentContainable { self.requestBiometryAuth() case "web_app_biometry_update_token": var tokenData: Data? - if let json, let tokenDataValue = json["token"] as? Data { - tokenData = tokenDataValue + if let json, let tokenDataValue = json["token"] as? String, !tokenDataValue.isEmpty { + tokenData = tokenDataValue.data(using: .utf8) } self.requestBiometryUpdateToken(tokenData: tokenData) default: @@ -1514,10 +1514,7 @@ public final class WebAppController: ViewController, AttachmentContainable { let appBundleId = self.context.sharedContext.applicationBindings.appBundleId Thread { [weak self] in - var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId) - if key == nil { - key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId) - } + let key = LocalAuth.getOrCreatePrivateKey(baseAppBundleId: appBundleId, keyId: keyId) let decryptedData: LocalAuth.DecryptionResult if let key { @@ -1567,9 +1564,9 @@ public final class WebAppController: ViewController, AttachmentContainable { data["status"] = isAuthorized ? "authorized" : "failed" if isAuthorized { if let tokenData { - data["token"] = tokenData + data["token"] = String(data: tokenData, encoding: .utf8) ?? "" } else { - data["token"] = Data() + data["token"] = "" } } @@ -1593,10 +1590,7 @@ public final class WebAppController: ViewController, AttachmentContainable { if let tokenData { let appBundleId = self.context.sharedContext.applicationBindings.appBundleId Thread { [weak self] in - var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId) - if key == nil { - key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId) - } + let key = LocalAuth.getOrCreatePrivateKey(baseAppBundleId: appBundleId, keyId: keyId) var encryptedData: TelegramBotBiometricsState.OpaqueToken? if let key { @@ -1619,6 +1613,28 @@ public final class WebAppController: ViewController, AttachmentContainable { state.opaqueToken = encryptedData return state }) + + var data: [String: Any] = [:] + data["status"] = "updated" + + guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else { + return + } + guard let jsonDataString = String(data: jsonData, encoding: .utf8) else { + return + } + self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString) + } else { + var data: [String: Any] = [:] + data["status"] = "failed" + + guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else { + return + } + guard let jsonDataString = String(data: jsonData, encoding: .utf8) else { + return + } + self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString) } } }.start() @@ -1628,6 +1644,17 @@ public final class WebAppController: ViewController, AttachmentContainable { state.opaqueToken = nil return state }) + + var data: [String: Any] = [:] + data["status"] = "removed" + + guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else { + return + } + guard let jsonDataString = String(data: jsonData, encoding: .utf8) else { + return + } + self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString) } } } diff --git a/third-party/webrtc/BUILD b/third-party/webrtc/BUILD index a252c01acd..6e03fe41ec 100644 --- a/third-party/webrtc/BUILD +++ b/third-party/webrtc/BUILD @@ -3485,17 +3485,12 @@ fft4g_sources = [ "fft4g/fft4g.cc", ] -opus_headers = [ - "dependencies/third_party/opus/src/include/opus.h", - "dependencies/third_party/opus/src/include/opus_multistream.h", -] - raw_combined_sources = webrtc_sources combined_sources = [ webrtc_source_dir + "/" + path for path in raw_combined_sources ] + arch_specific_sources + [ webrtc_source_dir + "/" + "sdk/" + path for path in ios_sources + ios_objc_sources -] + absl_sources + fft4g_sources + rnnoise_sources + pffft_sources + crc32c_sources + dcsctp_sources + opus_headers +] + absl_sources + fft4g_sources + rnnoise_sources + pffft_sources + crc32c_sources + dcsctp_sources genrule( name = "generate_field_trials_header", @@ -3530,15 +3525,19 @@ opus_header_list = [ "opus.h", ] -'''genrule( +genrule( name = "copy_opus_headers", srcs = [ - "/third-party/opus/Public/opus/{}".format(x) for x in opus_header_list + "//third-party/opus:Public/opus/{}".format(x) for x in opus_header_list ], cmd_bash = """ set -ex - cp $(location /third-party/opus/Public/opus/opus_defines.h) + cp $(location //third-party/opus:Public/opus/opus_defines.h) $(location :opus_headers/third_party/opus/src/include/opus_defines.h) + cp $(location //third-party/opus:Public/opus/opus_multistream.h) $(location :opus_headers/third_party/opus/src/include/opus_multistream.h) + cp $(location //third-party/opus:Public/opus/opus_projection.h) $(location :opus_headers/third_party/opus/src/include/opus_projection.h) + cp $(location //third-party/opus:Public/opus/opus_types.h) $(location :opus_headers/third_party/opus/src/include/opus_types.h) + cp $(location //third-party/opus:Public/opus/opus.h) $(location :opus_headers/third_party/opus/src/include/opus.h) """, outs = [ ":opus_headers/third_party/opus/src/include/{}".format(x) for x in opus_header_list @@ -3546,7 +3545,7 @@ opus_header_list = [ visibility = [ "//visibility:public", ] -)''' +) cc_library( name = "opus_headers", @@ -3583,6 +3582,7 @@ objc_library( "-Ithird-party/webrtc/" + webrtc_source_dir + "/" + "sdk/objc", "-Ithird-party/webrtc/" + webrtc_source_dir + "/" + "sdk/objc/base", "-Ithird-party/webrtc/additional-files", + "-Ithird-party/webrtc/opus_headers", "-DBSD=1", "-DUSE_KISS_FFT", "-DHAVE_PTHREAD", @@ -3612,6 +3612,7 @@ objc_library( "//submodules/ffmpeg:ffmpeg", "//third-party/openh264:openh264", ":field_trials_header", + ":opus_headers", ], sdk_frameworks = [ "AVFoundation", diff --git a/third-party/webrtc/dependencies/third_party/opus/src/include/opus.h b/third-party/webrtc/dependencies/third_party/opus/src/include/opus.h deleted file mode 100644 index eadeda75a0..0000000000 --- a/third-party/webrtc/dependencies/third_party/opus/src/include/opus.h +++ /dev/null @@ -1,1099 +0,0 @@ -/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited - Written by Jean-Marc Valin and Koen Vos */ -/* - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER - OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/** - * @file opus.h - * @brief Opus reference implementation API - */ - -#ifndef OPUS_H -#define OPUS_H - -#include "opus_types.h" -#include "opus_defines.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @mainpage Opus - * - * The Opus codec is designed for interactive speech and audio transmission over the Internet. - * It is designed by the IETF Codec Working Group and incorporates technology from - * Skype's SILK codec and Xiph.Org's CELT codec. - * - * The Opus codec is designed to handle a wide range of interactive audio applications, - * including Voice over IP, videoconferencing, in-game chat, and even remote live music - * performances. It can scale from low bit-rate narrowband speech to very high quality - * stereo music. Its main features are: - - * @li Sampling rates from 8 to 48 kHz - * @li Bit-rates from 6 kb/s to 510 kb/s - * @li Support for both constant bit-rate (CBR) and variable bit-rate (VBR) - * @li Audio bandwidth from narrowband to full-band - * @li Support for speech and music - * @li Support for mono and stereo - * @li Support for multichannel (up to 255 channels) - * @li Frame sizes from 2.5 ms to 60 ms - * @li Good loss robustness and packet loss concealment (PLC) - * @li Floating point and fixed-point implementation - * - * Documentation sections: - * @li @ref opus_encoder - * @li @ref opus_decoder - * @li @ref opus_repacketizer - * @li @ref opus_multistream - * @li @ref opus_libinfo - * @li @ref opus_custom - */ - -/** @defgroup opus_encoder Opus Encoder - * @{ - * - * @brief This page describes the process and functions used to encode Opus. - * - * Since Opus is a stateful codec, the encoding process starts with creating an encoder - * state. This can be done with: - * - * @code - * int error; - * OpusEncoder *enc; - * enc = opus_encoder_create(Fs, channels, application, &error); - * @endcode - * - * From this point, @c enc can be used for encoding an audio stream. An encoder state - * @b must @b not be used for more than one stream at the same time. Similarly, the encoder - * state @b must @b not be re-initialized for each frame. - * - * While opus_encoder_create() allocates memory for the state, it's also possible - * to initialize pre-allocated memory: - * - * @code - * int size; - * int error; - * OpusEncoder *enc; - * size = opus_encoder_get_size(channels); - * enc = malloc(size); - * error = opus_encoder_init(enc, Fs, channels, application); - * @endcode - * - * where opus_encoder_get_size() returns the required size for the encoder state. Note that - * future versions of this code may change the size, so no assumptions should be made about it. - * - * The encoder state is always continuous in memory and only a shallow copy is sufficient - * to copy it (e.g. memcpy()) - * - * It is possible to change some of the encoder's settings using the opus_encoder_ctl() - * interface. All these settings already default to the recommended value, so they should - * only be changed when necessary. The most common settings one may want to change are: - * - * @code - * opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrate)); - * opus_encoder_ctl(enc, OPUS_SET_COMPLEXITY(complexity)); - * opus_encoder_ctl(enc, OPUS_SET_SIGNAL(signal_type)); - * @endcode - * - * where - * - * @arg bitrate is in bits per second (b/s) - * @arg complexity is a value from 1 to 10, where 1 is the lowest complexity and 10 is the highest - * @arg signal_type is either OPUS_AUTO (default), OPUS_SIGNAL_VOICE, or OPUS_SIGNAL_MUSIC - * - * See @ref opus_encoderctls and @ref opus_genericctls for a complete list of parameters that can be set or queried. Most parameters can be set or changed at any time during a stream. - * - * To encode a frame, opus_encode() or opus_encode_float() must be called with exactly one frame (2.5, 5, 10, 20, 40 or 60 ms) of audio data: - * @code - * len = opus_encode(enc, audio_frame, frame_size, packet, max_packet); - * @endcode - * - * where - *
    - *
  • audio_frame is the audio data in opus_int16 (or float for opus_encode_float())
  • - *
  • frame_size is the duration of the frame in samples (per channel)
  • - *
  • packet is the byte array to which the compressed data is written
  • - *
  • max_packet is the maximum number of bytes that can be written in the packet (4000 bytes is recommended). - * Do not use max_packet to control VBR target bitrate, instead use the #OPUS_SET_BITRATE CTL.
  • - *
- * - * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. - * The return value can be negative, which indicates that an error has occurred. If the return value - * is 2 bytes or less, then the packet does not need to be transmitted (DTX). - * - * Once the encoder state if no longer needed, it can be destroyed with - * - * @code - * opus_encoder_destroy(enc); - * @endcode - * - * If the encoder was created with opus_encoder_init() rather than opus_encoder_create(), - * then no action is required aside from potentially freeing the memory that was manually - * allocated for it (calling free(enc) for the example above) - * - */ - -/** Opus encoder state. - * This contains the complete state of an Opus encoder. - * It is position independent and can be freely copied. - * @see opus_encoder_create,opus_encoder_init - */ -typedef struct OpusEncoder OpusEncoder; - -/** Gets the size of an OpusEncoder structure. - * @param[in] channels int: Number of channels. - * This must be 1 or 2. - * @returns The size in bytes. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_encoder_get_size(int channels); - -/** - */ - -/** Allocates and initializes an encoder state. - * There are three coding modes: - * - * @ref OPUS_APPLICATION_VOIP gives best quality at a given bitrate for voice - * signals. It enhances the input signal by high-pass filtering and - * emphasizing formants and harmonics. Optionally it includes in-band - * forward error correction to protect against packet loss. Use this - * mode for typical VoIP applications. Because of the enhancement, - * even at high bitrates the output may sound different from the input. - * - * @ref OPUS_APPLICATION_AUDIO gives best quality at a given bitrate for most - * non-voice signals like music. Use this mode for music and mixed - * (music/voice) content, broadcast, and applications requiring less - * than 15 ms of coding delay. - * - * @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY configures low-delay mode that - * disables the speech-optimized mode in exchange for slightly reduced delay. - * This mode can only be set on an newly initialized or freshly reset encoder - * because it changes the codec delay. - * - * This is useful when the caller knows that the speech-optimized modes will not be needed (use with caution). - * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param [in] channels int: Number of channels (1 or 2) in input signal - * @param [in] application int: Coding mode (one of @ref OPUS_APPLICATION_VOIP, @ref OPUS_APPLICATION_AUDIO, or @ref OPUS_APPLICATION_RESTRICTED_LOWDELAY) - * @param [out] error int*: @ref opus_errorcodes - * @note Regardless of the sampling rate and number channels selected, the Opus encoder - * can switch to a lower audio bandwidth or number of channels if the bitrate - * selected is too low. This also means that it is safe to always use 48 kHz stereo input - * and let the encoder optimize the encoding. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusEncoder *opus_encoder_create( - opus_int32 Fs, - int channels, - int application, - int *error -); - -/** Initializes a previously allocated encoder state - * The memory pointed to by st must be at least the size returned by opus_encoder_get_size(). - * This is intended for applications which use their own allocator instead of malloc. - * @see opus_encoder_create(),opus_encoder_get_size() - * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. - * @param [in] st OpusEncoder*: Encoder state - * @param [in] Fs opus_int32: Sampling rate of input signal (Hz) - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param [in] channels int: Number of channels (1 or 2) in input signal - * @param [in] application int: Coding mode (one of OPUS_APPLICATION_VOIP, OPUS_APPLICATION_AUDIO, or OPUS_APPLICATION_RESTRICTED_LOWDELAY) - * @retval #OPUS_OK Success or @ref opus_errorcodes - */ -OPUS_EXPORT int opus_encoder_init( - OpusEncoder *st, - opus_int32 Fs, - int channels, - int application -) OPUS_ARG_NONNULL(1); - -/** Encodes an Opus frame. - * @param [in] st OpusEncoder*: Encoder state - * @param [in] pcm opus_int16*: Input signal (interleaved if 2 channels). length is frame_size*channels*sizeof(opus_int16) - * @param [in] frame_size int: Number of samples per channel in the - * input signal. - * This must be an Opus frame size for - * the encoder's sampling rate. - * For example, at 48 kHz the permitted - * values are 120, 240, 480, 960, 1920, - * and 2880. - * Passing in a duration of less than - * 10 ms (480 samples at 48 kHz) will - * prevent the encoder from using the LPC - * or hybrid modes. - * @param [out] data unsigned char*: Output payload. - * This must contain storage for at - * least \a max_data_bytes. - * @param [in] max_data_bytes opus_int32: Size of the allocated - * memory for the output - * payload. This may be - * used to impose an upper limit on - * the instant bitrate, but should - * not be used as the only bitrate - * control. Use #OPUS_SET_BITRATE to - * control the bitrate. - * @returns The length of the encoded packet (in bytes) on success or a - * negative error code (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode( - OpusEncoder *st, - const opus_int16 *pcm, - int frame_size, - unsigned char *data, - opus_int32 max_data_bytes -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); - -/** Encodes an Opus frame from floating point input. - * @param [in] st OpusEncoder*: Encoder state - * @param [in] pcm float*: Input in float format (interleaved if 2 channels), with a normal range of +/-1.0. - * Samples with a range beyond +/-1.0 are supported but will - * be clipped by decoders using the integer API and should - * only be used if it is known that the far end supports - * extended dynamic range. - * length is frame_size*channels*sizeof(float) - * @param [in] frame_size int: Number of samples per channel in the - * input signal. - * This must be an Opus frame size for - * the encoder's sampling rate. - * For example, at 48 kHz the permitted - * values are 120, 240, 480, 960, 1920, - * and 2880. - * Passing in a duration of less than - * 10 ms (480 samples at 48 kHz) will - * prevent the encoder from using the LPC - * or hybrid modes. - * @param [out] data unsigned char*: Output payload. - * This must contain storage for at - * least \a max_data_bytes. - * @param [in] max_data_bytes opus_int32: Size of the allocated - * memory for the output - * payload. This may be - * used to impose an upper limit on - * the instant bitrate, but should - * not be used as the only bitrate - * control. Use #OPUS_SET_BITRATE to - * control the bitrate. - * @returns The length of the encoded packet (in bytes) on success or a - * negative error code (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_encode_float( - OpusEncoder *st, - const float *pcm, - int frame_size, - unsigned char *data, - opus_int32 max_data_bytes -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); - -/** Frees an OpusEncoder allocated by opus_encoder_create(). - * @param[in] st OpusEncoder*: State to be freed. - */ -OPUS_EXPORT void opus_encoder_destroy(OpusEncoder *st); - -/** Perform a CTL function on an Opus encoder. - * - * Generally the request and subsequent arguments are generated - * by a convenience macro. - * @param st OpusEncoder*: Encoder state. - * @param request This and all remaining parameters should be replaced by one - * of the convenience macros in @ref opus_genericctls or - * @ref opus_encoderctls. - * @see opus_genericctls - * @see opus_encoderctls - */ -OPUS_EXPORT int opus_encoder_ctl(OpusEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); -/**@}*/ - -/** @defgroup opus_decoder Opus Decoder - * @{ - * - * @brief This page describes the process and functions used to decode Opus. - * - * The decoding process also starts with creating a decoder - * state. This can be done with: - * @code - * int error; - * OpusDecoder *dec; - * dec = opus_decoder_create(Fs, channels, &error); - * @endcode - * where - * @li Fs is the sampling rate and must be 8000, 12000, 16000, 24000, or 48000 - * @li channels is the number of channels (1 or 2) - * @li error will hold the error code in case of failure (or #OPUS_OK on success) - * @li the return value is a newly created decoder state to be used for decoding - * - * While opus_decoder_create() allocates memory for the state, it's also possible - * to initialize pre-allocated memory: - * @code - * int size; - * int error; - * OpusDecoder *dec; - * size = opus_decoder_get_size(channels); - * dec = malloc(size); - * error = opus_decoder_init(dec, Fs, channels); - * @endcode - * where opus_decoder_get_size() returns the required size for the decoder state. Note that - * future versions of this code may change the size, so no assumptions should be made about it. - * - * The decoder state is always continuous in memory and only a shallow copy is sufficient - * to copy it (e.g. memcpy()) - * - * To decode a frame, opus_decode() or opus_decode_float() must be called with a packet of compressed audio data: - * @code - * frame_size = opus_decode(dec, packet, len, decoded, max_size, 0); - * @endcode - * where - * - * @li packet is the byte array containing the compressed data - * @li len is the exact number of bytes contained in the packet - * @li decoded is the decoded audio data in opus_int16 (or float for opus_decode_float()) - * @li max_size is the max duration of the frame in samples (per channel) that can fit into the decoded_frame array - * - * opus_decode() and opus_decode_float() return the number of samples (per channel) decoded from the packet. - * If that value is negative, then an error has occurred. This can occur if the packet is corrupted or if the audio - * buffer is too small to hold the decoded audio. - * - * Opus is a stateful codec with overlapping blocks and as a result Opus - * packets are not coded independently of each other. Packets must be - * passed into the decoder serially and in the correct order for a correct - * decode. Lost packets can be replaced with loss concealment by calling - * the decoder with a null pointer and zero length for the missing packet. - * - * A single codec state may only be accessed from a single thread at - * a time and any required locking must be performed by the caller. Separate - * streams must be decoded with separate decoder states and can be decoded - * in parallel unless the library was compiled with NONTHREADSAFE_PSEUDOSTACK - * defined. - * - */ - -/** Opus decoder state. - * This contains the complete state of an Opus decoder. - * It is position independent and can be freely copied. - * @see opus_decoder_create,opus_decoder_init - */ -typedef struct OpusDecoder OpusDecoder; - -/** Opus DRED decoder. - * This contains the complete state of an Opus DRED decoder. - * It is position independent and can be freely copied. - * @see opus_dred_decoder_create,opus_dred_decoder_init - */ -typedef struct OpusDREDDecoder OpusDREDDecoder; - - -/** Opus DRED state. - * This contains the complete state of an Opus DRED packet. - * It is position independent and can be freely copied. - * @see opus_dred_create,opus_dred_init - */ -typedef struct OpusDRED OpusDRED; - -/** Gets the size of an OpusDecoder structure. - * @param [in] channels int: Number of channels. - * This must be 1 or 2. - * @returns The size in bytes. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_size(int channels); - -/** Allocates and initializes a decoder state. - * @param [in] Fs opus_int32: Sample rate to decode at (Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param [in] channels int: Number of channels (1 or 2) to decode - * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes - * - * Internally Opus stores data at 48000 Hz, so that should be the default - * value for Fs. However, the decoder can efficiently decode to buffers - * at 8, 12, 16, and 24 kHz so if for some reason the caller cannot use - * data at the full sample rate, or knows the compressed data doesn't - * use the full frequency range, it can request decoding at a reduced - * rate. Likewise, the decoder is capable of filling in either mono or - * interleaved stereo pcm buffers, at the caller's request. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusDecoder *opus_decoder_create( - opus_int32 Fs, - int channels, - int *error -); - -/** Initializes a previously allocated decoder state. - * The state must be at least the size returned by opus_decoder_get_size(). - * This is intended for applications which use their own allocator instead of malloc. @see opus_decoder_create,opus_decoder_get_size - * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. - * @param [in] st OpusDecoder*: Decoder state. - * @param [in] Fs opus_int32: Sampling rate to decode to (Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param [in] channels int: Number of channels (1 or 2) to decode - * @retval #OPUS_OK Success or @ref opus_errorcodes - */ -OPUS_EXPORT int opus_decoder_init( - OpusDecoder *st, - opus_int32 Fs, - int channels -) OPUS_ARG_NONNULL(1); - -/** Decode an Opus packet. - * @param [in] st OpusDecoder*: Decoder state - * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss - * @param [in] len opus_int32: Number of bytes in payload* - * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length - * is frame_size*channels*sizeof(opus_int16) - * @param [in] frame_size Number of samples per channel of available space in \a pcm. - * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will - * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), - * then frame_size needs to be exactly the duration of audio that is missing, otherwise the - * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and - * FEC cases, frame_size must be a multiple of 2.5 ms. - * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be - * decoded. If no such data is available, the frame is decoded as if it were lost. - * @returns Number of decoded samples or @ref opus_errorcodes - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode( - OpusDecoder *st, - const unsigned char *data, - opus_int32 len, - opus_int16 *pcm, - int frame_size, - int decode_fec -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); - -/** Decode an Opus packet with floating point output. - * @param [in] st OpusDecoder*: Decoder state - * @param [in] data char*: Input payload. Use a NULL pointer to indicate packet loss - * @param [in] len opus_int32: Number of bytes in payload - * @param [out] pcm float*: Output signal (interleaved if 2 channels). length - * is frame_size*channels*sizeof(float) - * @param [in] frame_size Number of samples per channel of available space in \a pcm. - * If this is less than the maximum packet duration (120ms; 5760 for 48kHz), this function will - * not be capable of decoding some packets. In the case of PLC (data==NULL) or FEC (decode_fec=1), - * then frame_size needs to be exactly the duration of audio that is missing, otherwise the - * decoder will not be in the optimal state to decode the next incoming packet. For the PLC and - * FEC cases, frame_size must be a multiple of 2.5 ms. - * @param [in] decode_fec int: Flag (0 or 1) to request that any in-band forward error correction data be - * decoded. If no such data is available the frame is decoded as if it were lost. - * @returns Number of decoded samples or @ref opus_errorcodes - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decode_float( - OpusDecoder *st, - const unsigned char *data, - opus_int32 len, - float *pcm, - int frame_size, - int decode_fec -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); - -/** Perform a CTL function on an Opus decoder. - * - * Generally the request and subsequent arguments are generated - * by a convenience macro. - * @param st OpusDecoder*: Decoder state. - * @param request This and all remaining parameters should be replaced by one - * of the convenience macros in @ref opus_genericctls or - * @ref opus_decoderctls. - * @see opus_genericctls - * @see opus_decoderctls - */ -OPUS_EXPORT int opus_decoder_ctl(OpusDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); - -/** Frees an OpusDecoder allocated by opus_decoder_create(). - * @param[in] st OpusDecoder*: State to be freed. - */ -OPUS_EXPORT void opus_decoder_destroy(OpusDecoder *st); - -/** Gets the size of an OpusDREDDecoder structure. - * @returns The size in bytes. - */ -OPUS_EXPORT int opus_dred_decoder_get_size(void); - -/** Allocates and initializes an OpusDREDDecoder state. - * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes - */ -OPUS_EXPORT OpusDREDDecoder *opus_dred_decoder_create(int *error); - -/** Initializes an OpusDREDDecoder state. - * @param[in] dec OpusDREDDecoder*: State to be initialized. - */ -OPUS_EXPORT int opus_dred_decoder_init(OpusDREDDecoder *dec); - -/** Frees an OpusDREDDecoder allocated by opus_dred_decoder_create(). - * @param[in] dec OpusDREDDecoder*: State to be freed. - */ -OPUS_EXPORT void opus_dred_decoder_destroy(OpusDREDDecoder *dec); - -/** Perform a CTL function on an Opus DRED decoder. - * - * Generally the request and subsequent arguments are generated - * by a convenience macro. - * @param dred_dec OpusDREDDecoder*: DRED Decoder state. - * @param request This and all remaining parameters should be replaced by one - * of the convenience macros in @ref opus_genericctls or - * @ref opus_decoderctls. - * @see opus_genericctls - * @see opus_decoderctls - */ -OPUS_EXPORT int opus_dred_decoder_ctl(OpusDREDDecoder *dred_dec, int request, ...); - -/** Gets the size of an OpusDRED structure. - * @returns The size in bytes. - */ -OPUS_EXPORT int opus_dred_get_size(void); - -/** Allocates and initializes a DRED state. - * @param [out] error int*: #OPUS_OK Success or @ref opus_errorcodes - */ -OPUS_EXPORT OpusDRED *opus_dred_alloc(int *error); - -/** Frees an OpusDRED allocated by opus_dred_create(). - * @param[in] dec OpusDRED*: State to be freed. - */ -OPUS_EXPORT void opus_dred_free(OpusDRED *dec); - -/** Decode an Opus DRED packet. - * @param [in] dred_dec OpusDRED*: DRED Decoder state - * @param [in] dred OpusDRED*: DRED state - * @param [in] data char*: Input payload - * @param [in] len opus_int32: Number of bytes in payload - * @param [in] max_dred_samples opus_int32: Maximum number of DRED samples that may be needed (if available in the packet). - * @param [in] sampling_rate opus_int32: Sampling rate used for max_dred_samples argument. Needs not match the actual sampling rate of the decoder. - * @param [out] dred_end opus_int32*: Number of non-encoded (silence) samples between the DRED timestamp and the last DRED sample. - * @param [in] defer_processing int: Flag (0 or 1). If set to one, the CPU-intensive part of the DRED decoding is deferred until opus_dred_process() is called. - * @returns Offset (positive) of the first decoded DRED samples, zero if no DRED is present, or @ref opus_errorcodes - */ -OPUS_EXPORT int opus_dred_parse(OpusDREDDecoder *dred_dec, OpusDRED *dred, const unsigned char *data, opus_int32 len, opus_int32 max_dred_samples, opus_int32 sampling_rate, int *dred_end, int defer_processing) OPUS_ARG_NONNULL(1); - -/** Finish decoding an Opus DRED packet. The function only needs to be called if opus_dred_parse() was called with defer_processing=1. - * The source and destination will often be the same DRED state. - * @param [in] dred_dec OpusDRED*: DRED Decoder state - * @param [in] src OpusDRED*: Source DRED state to start the processing from. - * @param [out] dst OpusDRED*: Destination DRED state to store the updated state after processing. - * @returns @ref opus_errorcodes - */ -OPUS_EXPORT int opus_dred_process(OpusDREDDecoder *dred_dec, const OpusDRED *src, OpusDRED *dst); - -/** Decode audio from an Opus DRED packet with floating point output. - * @param [in] st OpusDecoder*: Decoder state - * @param [in] dred OpusDRED*: DRED state - * @param [in] dred_offset opus_int32: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet). - * @param [out] pcm opus_int16*: Output signal (interleaved if 2 channels). length - * is frame_size*channels*sizeof(opus_int16) - * @param [in] frame_size Number of samples per channel to decode in \a pcm. - * frame_size must be a multiple of 2.5 ms. - * @returns Number of decoded samples or @ref opus_errorcodes - */ -OPUS_EXPORT int opus_decoder_dred_decode(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, opus_int16 *pcm, opus_int32 frame_size); - -/** Decode audio from an Opus DRED packet with floating point output. - * @param [in] st OpusDecoder*: Decoder state - * @param [in] dred OpusDRED*: DRED state - * @param [in] dred_offset opus_int32: position of the redundancy to decode (in samples before the beginning of the real audio data in the packet). - * @param [out] pcm float*: Output signal (interleaved if 2 channels). length - * is frame_size*channels*sizeof(float) - * @param [in] frame_size Number of samples per channel to decode in \a pcm. - * frame_size must be a multiple of 2.5 ms. - * @returns Number of decoded samples or @ref opus_errorcodes - */ -OPUS_EXPORT int opus_decoder_dred_decode_float(OpusDecoder *st, const OpusDRED *dred, opus_int32 dred_offset, float *pcm, opus_int32 frame_size); - - -/** Parse an opus packet into one or more frames. - * Opus_decode will perform this operation internally so most applications do - * not need to use this function. - * This function does not copy the frames, the returned pointers are pointers into - * the input packet. - * @param [in] data char*: Opus packet to be parsed - * @param [in] len opus_int32: size of data - * @param [out] out_toc char*: TOC pointer - * @param [out] frames char*[48] encapsulated frames - * @param [out] size opus_int16[48] sizes of the encapsulated frames - * @param [out] payload_offset int*: returns the position of the payload within the packet (in bytes) - * @returns number of frames - */ -OPUS_EXPORT int opus_packet_parse( - const unsigned char *data, - opus_int32 len, - unsigned char *out_toc, - const unsigned char *frames[48], - opus_int16 size[48], - int *payload_offset -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5); - -/** Gets the bandwidth of an Opus packet. - * @param [in] data char*: Opus packet - * @retval OPUS_BANDWIDTH_NARROWBAND Narrowband (4kHz bandpass) - * @retval OPUS_BANDWIDTH_MEDIUMBAND Mediumband (6kHz bandpass) - * @retval OPUS_BANDWIDTH_WIDEBAND Wideband (8kHz bandpass) - * @retval OPUS_BANDWIDTH_SUPERWIDEBAND Superwideband (12kHz bandpass) - * @retval OPUS_BANDWIDTH_FULLBAND Fullband (20kHz bandpass) - * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_bandwidth(const unsigned char *data) OPUS_ARG_NONNULL(1); - -/** Gets the number of samples per frame from an Opus packet. - * @param [in] data char*: Opus packet. - * This must contain at least one byte of - * data. - * @param [in] Fs opus_int32: Sampling rate in Hz. - * This must be a multiple of 400, or - * inaccurate results will be returned. - * @returns Number of samples per frame. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_samples_per_frame(const unsigned char *data, opus_int32 Fs) OPUS_ARG_NONNULL(1); - -/** Gets the number of channels from an Opus packet. - * @param [in] data char*: Opus packet - * @returns Number of channels - * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_channels(const unsigned char *data) OPUS_ARG_NONNULL(1); - -/** Gets the number of frames in an Opus packet. - * @param [in] packet char*: Opus packet - * @param [in] len opus_int32: Length of packet - * @returns Number of frames - * @retval OPUS_BAD_ARG Insufficient data was passed to the function - * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_frames(const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1); - -/** Gets the number of samples of an Opus packet. - * @param [in] packet char*: Opus packet - * @param [in] len opus_int32: Length of packet - * @param [in] Fs opus_int32: Sampling rate in Hz. - * This must be a multiple of 400, or - * inaccurate results will be returned. - * @returns Number of samples - * @retval OPUS_BAD_ARG Insufficient data was passed to the function - * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_get_nb_samples(const unsigned char packet[], opus_int32 len, opus_int32 Fs) OPUS_ARG_NONNULL(1); - -/** Checks whether an Opus packet has LBRR. - * @param [in] packet char*: Opus packet - * @param [in] len opus_int32: Length of packet - * @returns 1 is LBRR is present, 0 otherwise - * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_packet_has_lbrr(const unsigned char packet[], opus_int32 len); - -/** Gets the number of samples of an Opus packet. - * @param [in] dec OpusDecoder*: Decoder state - * @param [in] packet char*: Opus packet - * @param [in] len opus_int32: Length of packet - * @returns Number of samples - * @retval OPUS_BAD_ARG Insufficient data was passed to the function - * @retval OPUS_INVALID_PACKET The compressed data passed is corrupted or of an unsupported type - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_decoder_get_nb_samples(const OpusDecoder *dec, const unsigned char packet[], opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); - -/** Applies soft-clipping to bring a float signal within the [-1,1] range. If - * the signal is already in that range, nothing is done. If there are values - * outside of [-1,1], then the signal is clipped as smoothly as possible to - * both fit in the range and avoid creating excessive distortion in the - * process. - * @param [in,out] pcm float*: Input PCM and modified PCM - * @param [in] frame_size int Number of samples per channel to process - * @param [in] channels int: Number of channels - * @param [in,out] softclip_mem float*: State memory for the soft clipping process (one float per channel, initialized to zero) - */ -OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, float *softclip_mem); - - -/**@}*/ - -/** @defgroup opus_repacketizer Repacketizer - * @{ - * - * The repacketizer can be used to merge multiple Opus packets into a single - * packet or alternatively to split Opus packets that have previously been - * merged. Splitting valid Opus packets is always guaranteed to succeed, - * whereas merging valid packets only succeeds if all frames have the same - * mode, bandwidth, and frame size, and when the total duration of the merged - * packet is no more than 120 ms. The 120 ms limit comes from the - * specification and limits decoder memory requirements at a point where - * framing overhead becomes negligible. - * - * The repacketizer currently only operates on elementary Opus - * streams. It will not manipualte multistream packets successfully, except in - * the degenerate case where they consist of data from a single stream. - * - * The repacketizing process starts with creating a repacketizer state, either - * by calling opus_repacketizer_create() or by allocating the memory yourself, - * e.g., - * @code - * OpusRepacketizer *rp; - * rp = (OpusRepacketizer*)malloc(opus_repacketizer_get_size()); - * if (rp != NULL) - * opus_repacketizer_init(rp); - * @endcode - * - * Then the application should submit packets with opus_repacketizer_cat(), - * extract new packets with opus_repacketizer_out() or - * opus_repacketizer_out_range(), and then reset the state for the next set of - * input packets via opus_repacketizer_init(). - * - * For example, to split a sequence of packets into individual frames: - * @code - * unsigned char *data; - * int len; - * while (get_next_packet(&data, &len)) - * { - * unsigned char out[1276]; - * opus_int32 out_len; - * int nb_frames; - * int err; - * int i; - * err = opus_repacketizer_cat(rp, data, len); - * if (err != OPUS_OK) - * { - * release_packet(data); - * return err; - * } - * nb_frames = opus_repacketizer_get_nb_frames(rp); - * for (i = 0; i < nb_frames; i++) - * { - * out_len = opus_repacketizer_out_range(rp, i, i+1, out, sizeof(out)); - * if (out_len < 0) - * { - * release_packet(data); - * return (int)out_len; - * } - * output_next_packet(out, out_len); - * } - * opus_repacketizer_init(rp); - * release_packet(data); - * } - * @endcode - * - * Alternatively, to combine a sequence of frames into packets that each - * contain up to TARGET_DURATION_MS milliseconds of data: - * @code - * // The maximum number of packets with duration TARGET_DURATION_MS occurs - * // when the frame size is 2.5 ms, for a total of (TARGET_DURATION_MS*2/5) - * // packets. - * unsigned char *data[(TARGET_DURATION_MS*2/5)+1]; - * opus_int32 len[(TARGET_DURATION_MS*2/5)+1]; - * int nb_packets; - * unsigned char out[1277*(TARGET_DURATION_MS*2/2)]; - * opus_int32 out_len; - * int prev_toc; - * nb_packets = 0; - * while (get_next_packet(data+nb_packets, len+nb_packets)) - * { - * int nb_frames; - * int err; - * nb_frames = opus_packet_get_nb_frames(data[nb_packets], len[nb_packets]); - * if (nb_frames < 1) - * { - * release_packets(data, nb_packets+1); - * return nb_frames; - * } - * nb_frames += opus_repacketizer_get_nb_frames(rp); - * // If adding the next packet would exceed our target, or it has an - * // incompatible TOC sequence, output the packets we already have before - * // submitting it. - * // N.B., The nb_packets > 0 check ensures we've submitted at least one - * // packet since the last call to opus_repacketizer_init(). Otherwise a - * // single packet longer than TARGET_DURATION_MS would cause us to try to - * // output an (invalid) empty packet. It also ensures that prev_toc has - * // been set to a valid value. Additionally, len[nb_packets] > 0 is - * // guaranteed by the call to opus_packet_get_nb_frames() above, so the - * // reference to data[nb_packets][0] should be valid. - * if (nb_packets > 0 && ( - * ((prev_toc & 0xFC) != (data[nb_packets][0] & 0xFC)) || - * opus_packet_get_samples_per_frame(data[nb_packets], 48000)*nb_frames > - * TARGET_DURATION_MS*48)) - * { - * out_len = opus_repacketizer_out(rp, out, sizeof(out)); - * if (out_len < 0) - * { - * release_packets(data, nb_packets+1); - * return (int)out_len; - * } - * output_next_packet(out, out_len); - * opus_repacketizer_init(rp); - * release_packets(data, nb_packets); - * data[0] = data[nb_packets]; - * len[0] = len[nb_packets]; - * nb_packets = 0; - * } - * err = opus_repacketizer_cat(rp, data[nb_packets], len[nb_packets]); - * if (err != OPUS_OK) - * { - * release_packets(data, nb_packets+1); - * return err; - * } - * prev_toc = data[nb_packets][0]; - * nb_packets++; - * } - * // Output the final, partial packet. - * if (nb_packets > 0) - * { - * out_len = opus_repacketizer_out(rp, out, sizeof(out)); - * release_packets(data, nb_packets); - * if (out_len < 0) - * return (int)out_len; - * output_next_packet(out, out_len); - * } - * @endcode - * - * An alternate way of merging packets is to simply call opus_repacketizer_cat() - * unconditionally until it fails. At that point, the merged packet can be - * obtained with opus_repacketizer_out() and the input packet for which - * opus_repacketizer_cat() needs to be re-added to a newly reinitialized - * repacketizer state. - */ - -typedef struct OpusRepacketizer OpusRepacketizer; - -/** Gets the size of an OpusRepacketizer structure. - * @returns The size in bytes. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_size(void); - -/** (Re)initializes a previously allocated repacketizer state. - * The state must be at least the size returned by opus_repacketizer_get_size(). - * This can be used for applications which use their own allocator instead of - * malloc(). - * It must also be called to reset the queue of packets waiting to be - * repacketized, which is necessary if the maximum packet duration of 120 ms - * is reached or if you wish to submit packets with a different Opus - * configuration (coding mode, audio bandwidth, frame size, or channel count). - * Failure to do so will prevent a new packet from being added with - * opus_repacketizer_cat(). - * @see opus_repacketizer_create - * @see opus_repacketizer_get_size - * @see opus_repacketizer_cat - * @param rp OpusRepacketizer*: The repacketizer state to - * (re)initialize. - * @returns A pointer to the same repacketizer state that was passed in. - */ -OPUS_EXPORT OpusRepacketizer *opus_repacketizer_init(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); - -/** Allocates memory and initializes the new repacketizer with - * opus_repacketizer_init(). - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusRepacketizer *opus_repacketizer_create(void); - -/** Frees an OpusRepacketizer allocated by - * opus_repacketizer_create(). - * @param[in] rp OpusRepacketizer*: State to be freed. - */ -OPUS_EXPORT void opus_repacketizer_destroy(OpusRepacketizer *rp); - -/** Add a packet to the current repacketizer state. - * This packet must match the configuration of any packets already submitted - * for repacketization since the last call to opus_repacketizer_init(). - * This means that it must have the same coding mode, audio bandwidth, frame - * size, and channel count. - * This can be checked in advance by examining the top 6 bits of the first - * byte of the packet, and ensuring they match the top 6 bits of the first - * byte of any previously submitted packet. - * The total duration of audio in the repacketizer state also must not exceed - * 120 ms, the maximum duration of a single packet, after adding this packet. - * - * The contents of the current repacketizer state can be extracted into new - * packets using opus_repacketizer_out() or opus_repacketizer_out_range(). - * - * In order to add a packet with a different configuration or to add more - * audio beyond 120 ms, you must clear the repacketizer state by calling - * opus_repacketizer_init(). - * If a packet is too large to add to the current repacketizer state, no part - * of it is added, even if it contains multiple frames, some of which might - * fit. - * If you wish to be able to add parts of such packets, you should first use - * another repacketizer to split the packet into pieces and add them - * individually. - * @see opus_repacketizer_out_range - * @see opus_repacketizer_out - * @see opus_repacketizer_init - * @param rp OpusRepacketizer*: The repacketizer state to which to - * add the packet. - * @param[in] data const unsigned char*: The packet data. - * The application must ensure - * this pointer remains valid - * until the next call to - * opus_repacketizer_init() or - * opus_repacketizer_destroy(). - * @param len opus_int32: The number of bytes in the packet data. - * @returns An error code indicating whether or not the operation succeeded. - * @retval #OPUS_OK The packet's contents have been added to the repacketizer - * state. - * @retval #OPUS_INVALID_PACKET The packet did not have a valid TOC sequence, - * the packet's TOC sequence was not compatible - * with previously submitted packets (because - * the coding mode, audio bandwidth, frame size, - * or channel count did not match), or adding - * this packet would increase the total amount of - * audio stored in the repacketizer state to more - * than 120 ms. - */ -OPUS_EXPORT int opus_repacketizer_cat(OpusRepacketizer *rp, const unsigned char *data, opus_int32 len) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2); - - -/** Construct a new packet from data previously submitted to the repacketizer - * state via opus_repacketizer_cat(). - * @param rp OpusRepacketizer*: The repacketizer state from which to - * construct the new packet. - * @param begin int: The index of the first frame in the current - * repacketizer state to include in the output. - * @param end int: One past the index of the last frame in the - * current repacketizer state to include in the - * output. - * @param[out] data const unsigned char*: The buffer in which to - * store the output packet. - * @param maxlen opus_int32: The maximum number of bytes to store in - * the output buffer. In order to guarantee - * success, this should be at least - * 1276 for a single frame, - * or for multiple frames, - * 1277*(end-begin). - * However, 1*(end-begin) plus - * the size of all packet data submitted to - * the repacketizer since the last call to - * opus_repacketizer_init() or - * opus_repacketizer_create() is also - * sufficient, and possibly much smaller. - * @returns The total size of the output packet on success, or an error code - * on failure. - * @retval #OPUS_BAD_ARG [begin,end) was an invalid range of - * frames (begin < 0, begin >= end, or end > - * opus_repacketizer_get_nb_frames()). - * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the - * complete output packet. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out_range(OpusRepacketizer *rp, int begin, int end, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); - -/** Return the total number of frames contained in packet data submitted to - * the repacketizer state so far via opus_repacketizer_cat() since the last - * call to opus_repacketizer_init() or opus_repacketizer_create(). - * This defines the valid range of packets that can be extracted with - * opus_repacketizer_out_range() or opus_repacketizer_out(). - * @param rp OpusRepacketizer*: The repacketizer state containing the - * frames. - * @returns The total number of frames contained in the packet data submitted - * to the repacketizer state. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_repacketizer_get_nb_frames(OpusRepacketizer *rp) OPUS_ARG_NONNULL(1); - -/** Construct a new packet from data previously submitted to the repacketizer - * state via opus_repacketizer_cat(). - * This is a convenience routine that returns all the data submitted so far - * in a single packet. - * It is equivalent to calling - * @code - * opus_repacketizer_out_range(rp, 0, opus_repacketizer_get_nb_frames(rp), - * data, maxlen) - * @endcode - * @param rp OpusRepacketizer*: The repacketizer state from which to - * construct the new packet. - * @param[out] data const unsigned char*: The buffer in which to - * store the output packet. - * @param maxlen opus_int32: The maximum number of bytes to store in - * the output buffer. In order to guarantee - * success, this should be at least - * 1277*opus_repacketizer_get_nb_frames(rp). - * However, - * 1*opus_repacketizer_get_nb_frames(rp) - * plus the size of all packet data - * submitted to the repacketizer since the - * last call to opus_repacketizer_init() or - * opus_repacketizer_create() is also - * sufficient, and possibly much smaller. - * @returns The total size of the output packet on success, or an error code - * on failure. - * @retval #OPUS_BUFFER_TOO_SMALL \a maxlen was insufficient to contain the - * complete output packet. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_repacketizer_out(OpusRepacketizer *rp, unsigned char *data, opus_int32 maxlen) OPUS_ARG_NONNULL(1); - -/** Pads a given Opus packet to a larger size (possibly changing the TOC sequence). - * @param[in,out] data const unsigned char*: The buffer containing the - * packet to pad. - * @param len opus_int32: The size of the packet. - * This must be at least 1. - * @param new_len opus_int32: The desired size of the packet after padding. - * This must be at least as large as len. - * @returns an error code - * @retval #OPUS_OK \a on success. - * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. - * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. - */ -OPUS_EXPORT int opus_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len); - -/** Remove all padding from a given Opus packet and rewrite the TOC sequence to - * minimize space usage. - * @param[in,out] data const unsigned char*: The buffer containing the - * packet to strip. - * @param len opus_int32: The size of the packet. - * This must be at least 1. - * @returns The new size of the output packet on success, or an error code - * on failure. - * @retval #OPUS_BAD_ARG \a len was less than 1. - * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_packet_unpad(unsigned char *data, opus_int32 len); - -/** Pads a given Opus multi-stream packet to a larger size (possibly changing the TOC sequence). - * @param[in,out] data const unsigned char*: The buffer containing the - * packet to pad. - * @param len opus_int32: The size of the packet. - * This must be at least 1. - * @param new_len opus_int32: The desired size of the packet after padding. - * This must be at least 1. - * @param nb_streams opus_int32: The number of streams (not channels) in the packet. - * This must be at least as large as len. - * @returns an error code - * @retval #OPUS_OK \a on success. - * @retval #OPUS_BAD_ARG \a len was less than 1. - * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. - */ -OPUS_EXPORT int opus_multistream_packet_pad(unsigned char *data, opus_int32 len, opus_int32 new_len, int nb_streams); - -/** Remove all padding from a given Opus multi-stream packet and rewrite the TOC sequence to - * minimize space usage. - * @param[in,out] data const unsigned char*: The buffer containing the - * packet to strip. - * @param len opus_int32: The size of the packet. - * This must be at least 1. - * @param nb_streams opus_int32: The number of streams (not channels) in the packet. - * This must be at least 1. - * @returns The new size of the output packet on success, or an error code - * on failure. - * @retval #OPUS_BAD_ARG \a len was less than 1 or new_len was less than len. - * @retval #OPUS_INVALID_PACKET \a data did not contain a valid Opus packet. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_packet_unpad(unsigned char *data, opus_int32 len, int nb_streams); - -/**@}*/ - -#ifdef __cplusplus -} -#endif - -#endif /* OPUS_H */ diff --git a/third-party/webrtc/dependencies/third_party/opus/src/include/opus_defines.h b/third-party/webrtc/dependencies/third_party/opus/src/include/opus_defines.h deleted file mode 100644 index cd8f4dde86..0000000000 --- a/third-party/webrtc/dependencies/third_party/opus/src/include/opus_defines.h +++ /dev/null @@ -1,830 +0,0 @@ -/* Copyright (c) 2010-2011 Xiph.Org Foundation, Skype Limited - Written by Jean-Marc Valin and Koen Vos */ -/* - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER - OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/** - * @file opus_defines.h - * @brief Opus reference implementation constants - */ - -#ifndef OPUS_DEFINES_H -#define OPUS_DEFINES_H - -#include "opus_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** @defgroup opus_errorcodes Error codes - * @{ - */ -/** No error @hideinitializer*/ -#define OPUS_OK 0 -/** One or more invalid/out of range arguments @hideinitializer*/ -#define OPUS_BAD_ARG -1 -/** Not enough bytes allocated in the buffer @hideinitializer*/ -#define OPUS_BUFFER_TOO_SMALL -2 -/** An internal error was detected @hideinitializer*/ -#define OPUS_INTERNAL_ERROR -3 -/** The compressed data passed is corrupted @hideinitializer*/ -#define OPUS_INVALID_PACKET -4 -/** Invalid/unsupported request number @hideinitializer*/ -#define OPUS_UNIMPLEMENTED -5 -/** An encoder or decoder structure is invalid or already freed @hideinitializer*/ -#define OPUS_INVALID_STATE -6 -/** Memory allocation has failed @hideinitializer*/ -#define OPUS_ALLOC_FAIL -7 -/**@}*/ - -/** @cond OPUS_INTERNAL_DOC */ -/**Export control for opus functions */ - -#ifndef OPUS_EXPORT -# if defined(_WIN32) -# if defined(OPUS_BUILD) && defined(DLL_EXPORT) -# define OPUS_EXPORT __declspec(dllexport) -# else -# define OPUS_EXPORT -# endif -# elif defined(__GNUC__) && defined(OPUS_BUILD) -# define OPUS_EXPORT __attribute__ ((visibility ("default"))) -# else -# define OPUS_EXPORT -# endif -#endif - -# if !defined(OPUS_GNUC_PREREQ) -# if defined(__GNUC__)&&defined(__GNUC_MINOR__) -# define OPUS_GNUC_PREREQ(_maj,_min) \ - ((__GNUC__<<16)+__GNUC_MINOR__>=((_maj)<<16)+(_min)) -# else -# define OPUS_GNUC_PREREQ(_maj,_min) 0 -# endif -# endif - -#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) -# if OPUS_GNUC_PREREQ(3,0) -# define OPUS_RESTRICT __restrict__ -# elif (defined(_MSC_VER) && _MSC_VER >= 1400) -# define OPUS_RESTRICT __restrict -# else -# define OPUS_RESTRICT -# endif -#else -# define OPUS_RESTRICT restrict -#endif - -#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) ) -# if OPUS_GNUC_PREREQ(2,7) -# define OPUS_INLINE __inline__ -# elif (defined(_MSC_VER)) -# define OPUS_INLINE __inline -# else -# define OPUS_INLINE -# endif -#else -# define OPUS_INLINE inline -#endif - -/**Warning attributes for opus functions - * NONNULL is not used in OPUS_BUILD to avoid the compiler optimizing out - * some paranoid null checks. */ -#if defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) -# define OPUS_WARN_UNUSED_RESULT __attribute__ ((__warn_unused_result__)) -#else -# define OPUS_WARN_UNUSED_RESULT -#endif -#if !defined(OPUS_BUILD) && defined(__GNUC__) && OPUS_GNUC_PREREQ(3, 4) -# define OPUS_ARG_NONNULL(_x) __attribute__ ((__nonnull__(_x))) -#else -# define OPUS_ARG_NONNULL(_x) -#endif - -/** These are the actual Encoder CTL ID numbers. - * They should not be used directly by applications. - * In general, SETs should be even and GETs should be odd.*/ -#define OPUS_SET_APPLICATION_REQUEST 4000 -#define OPUS_GET_APPLICATION_REQUEST 4001 -#define OPUS_SET_BITRATE_REQUEST 4002 -#define OPUS_GET_BITRATE_REQUEST 4003 -#define OPUS_SET_MAX_BANDWIDTH_REQUEST 4004 -#define OPUS_GET_MAX_BANDWIDTH_REQUEST 4005 -#define OPUS_SET_VBR_REQUEST 4006 -#define OPUS_GET_VBR_REQUEST 4007 -#define OPUS_SET_BANDWIDTH_REQUEST 4008 -#define OPUS_GET_BANDWIDTH_REQUEST 4009 -#define OPUS_SET_COMPLEXITY_REQUEST 4010 -#define OPUS_GET_COMPLEXITY_REQUEST 4011 -#define OPUS_SET_INBAND_FEC_REQUEST 4012 -#define OPUS_GET_INBAND_FEC_REQUEST 4013 -#define OPUS_SET_PACKET_LOSS_PERC_REQUEST 4014 -#define OPUS_GET_PACKET_LOSS_PERC_REQUEST 4015 -#define OPUS_SET_DTX_REQUEST 4016 -#define OPUS_GET_DTX_REQUEST 4017 -#define OPUS_SET_VBR_CONSTRAINT_REQUEST 4020 -#define OPUS_GET_VBR_CONSTRAINT_REQUEST 4021 -#define OPUS_SET_FORCE_CHANNELS_REQUEST 4022 -#define OPUS_GET_FORCE_CHANNELS_REQUEST 4023 -#define OPUS_SET_SIGNAL_REQUEST 4024 -#define OPUS_GET_SIGNAL_REQUEST 4025 -#define OPUS_GET_LOOKAHEAD_REQUEST 4027 -/* #define OPUS_RESET_STATE 4028 */ -#define OPUS_GET_SAMPLE_RATE_REQUEST 4029 -#define OPUS_GET_FINAL_RANGE_REQUEST 4031 -#define OPUS_GET_PITCH_REQUEST 4033 -#define OPUS_SET_GAIN_REQUEST 4034 -#define OPUS_GET_GAIN_REQUEST 4045 /* Should have been 4035 */ -#define OPUS_SET_LSB_DEPTH_REQUEST 4036 -#define OPUS_GET_LSB_DEPTH_REQUEST 4037 -#define OPUS_GET_LAST_PACKET_DURATION_REQUEST 4039 -#define OPUS_SET_EXPERT_FRAME_DURATION_REQUEST 4040 -#define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041 -#define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042 -#define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043 -/* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */ -#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046 -#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047 -#define OPUS_GET_IN_DTX_REQUEST 4049 -#define OPUS_SET_DRED_DURATION_REQUEST 4050 -#define OPUS_GET_DRED_DURATION_REQUEST 4051 -#define OPUS_SET_DNN_BLOB_REQUEST 4052 -/*#define OPUS_GET_DNN_BLOB_REQUEST 4053 */ - -/** Defines for the presence of extended APIs. */ -#define OPUS_HAVE_OPUS_PROJECTION_H - -/* Macros to trigger compilation errors when the wrong types are provided to a CTL */ -#define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x)) - -#ifdef DISABLE_PTR_CHECK -/* Disable checks to prevent ubsan from complaining about NULL checks - in test_opus_api. */ -#define __opus_check_int_ptr(ptr) (ptr) -#define __opus_check_uint_ptr(ptr) (ptr) -#define __opus_check_uint8_ptr(ptr) (ptr) -#define __opus_check_val16_ptr(ptr) (ptr) -#define __opus_check_void_ptr(ptr) (ptr) -#else -#define __opus_check_int_ptr(ptr) ((ptr) + ((ptr) - (opus_int32*)(ptr))) -#define __opus_check_uint_ptr(ptr) ((ptr) + ((ptr) - (opus_uint32*)(ptr))) -#define __opus_check_uint8_ptr(ptr) ((ptr) + ((ptr) - (opus_uint8*)(ptr))) -#define __opus_check_val16_ptr(ptr) ((ptr) + ((ptr) - (opus_val16*)(ptr))) -#define __opus_check_void_ptr(x) ((void)((void *)0 == (x)), (x)) -#endif -/** @endcond */ - -/** @defgroup opus_ctlvalues Pre-defined values for CTL interface - * @see opus_genericctls, opus_encoderctls - * @{ - */ -/* Values for the various encoder CTLs */ -#define OPUS_AUTO -1000 /**opus_int32: Allowed values: 0-10, inclusive. - * - * @hideinitializer */ -#define OPUS_SET_COMPLEXITY(x) OPUS_SET_COMPLEXITY_REQUEST, __opus_check_int(x) -/** Gets the encoder's complexity configuration. - * @see OPUS_SET_COMPLEXITY - * @param[out] x opus_int32 *: Returns a value in the range 0-10, - * inclusive. - * @hideinitializer */ -#define OPUS_GET_COMPLEXITY(x) OPUS_GET_COMPLEXITY_REQUEST, __opus_check_int_ptr(x) - -/** Configures the bitrate in the encoder. - * Rates from 500 to 512000 bits per second are meaningful, as well as the - * special values #OPUS_AUTO and #OPUS_BITRATE_MAX. - * The value #OPUS_BITRATE_MAX can be used to cause the codec to use as much - * rate as it can, which is useful for controlling the rate by adjusting the - * output buffer size. - * @see OPUS_GET_BITRATE - * @param[in] x opus_int32: Bitrate in bits per second. The default - * is determined based on the number of - * channels and the input sampling rate. - * @hideinitializer */ -#define OPUS_SET_BITRATE(x) OPUS_SET_BITRATE_REQUEST, __opus_check_int(x) -/** Gets the encoder's bitrate configuration. - * @see OPUS_SET_BITRATE - * @param[out] x opus_int32 *: Returns the bitrate in bits per second. - * The default is determined based on the - * number of channels and the input - * sampling rate. - * @hideinitializer */ -#define OPUS_GET_BITRATE(x) OPUS_GET_BITRATE_REQUEST, __opus_check_int_ptr(x) - -/** Enables or disables variable bitrate (VBR) in the encoder. - * The configured bitrate may not be met exactly because frames must - * be an integer number of bytes in length. - * @see OPUS_GET_VBR - * @see OPUS_SET_VBR_CONSTRAINT - * @param[in] x opus_int32: Allowed values: - *
- *
0
Hard CBR. For LPC/hybrid modes at very low bit-rate, this can - * cause noticeable quality degradation.
- *
1
VBR (default). The exact type of VBR is controlled by - * #OPUS_SET_VBR_CONSTRAINT.
- *
- * @hideinitializer */ -#define OPUS_SET_VBR(x) OPUS_SET_VBR_REQUEST, __opus_check_int(x) -/** Determine if variable bitrate (VBR) is enabled in the encoder. - * @see OPUS_SET_VBR - * @see OPUS_GET_VBR_CONSTRAINT - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
0
Hard CBR.
- *
1
VBR (default). The exact type of VBR may be retrieved via - * #OPUS_GET_VBR_CONSTRAINT.
- *
- * @hideinitializer */ -#define OPUS_GET_VBR(x) OPUS_GET_VBR_REQUEST, __opus_check_int_ptr(x) - -/** Enables or disables constrained VBR in the encoder. - * This setting is ignored when the encoder is in CBR mode. - * @warning Only the MDCT mode of Opus currently heeds the constraint. - * Speech mode ignores it completely, hybrid mode may fail to obey it - * if the LPC layer uses more bitrate than the constraint would have - * permitted. - * @see OPUS_GET_VBR_CONSTRAINT - * @see OPUS_SET_VBR - * @param[in] x opus_int32: Allowed values: - *
- *
0
Unconstrained VBR.
- *
1
Constrained VBR (default). This creates a maximum of one - * frame of buffering delay assuming a transport with a - * serialization speed of the nominal bitrate.
- *
- * @hideinitializer */ -#define OPUS_SET_VBR_CONSTRAINT(x) OPUS_SET_VBR_CONSTRAINT_REQUEST, __opus_check_int(x) -/** Determine if constrained VBR is enabled in the encoder. - * @see OPUS_SET_VBR_CONSTRAINT - * @see OPUS_GET_VBR - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
0
Unconstrained VBR.
- *
1
Constrained VBR (default).
- *
- * @hideinitializer */ -#define OPUS_GET_VBR_CONSTRAINT(x) OPUS_GET_VBR_CONSTRAINT_REQUEST, __opus_check_int_ptr(x) - -/** Configures mono/stereo forcing in the encoder. - * This can force the encoder to produce packets encoded as either mono or - * stereo, regardless of the format of the input audio. This is useful when - * the caller knows that the input signal is currently a mono source embedded - * in a stereo stream. - * @see OPUS_GET_FORCE_CHANNELS - * @param[in] x opus_int32: Allowed values: - *
- *
#OPUS_AUTO
Not forced (default)
- *
1
Forced mono
- *
2
Forced stereo
- *
- * @hideinitializer */ -#define OPUS_SET_FORCE_CHANNELS(x) OPUS_SET_FORCE_CHANNELS_REQUEST, __opus_check_int(x) -/** Gets the encoder's forced channel configuration. - * @see OPUS_SET_FORCE_CHANNELS - * @param[out] x opus_int32 *: - *
- *
#OPUS_AUTO
Not forced (default)
- *
1
Forced mono
- *
2
Forced stereo
- *
- * @hideinitializer */ -#define OPUS_GET_FORCE_CHANNELS(x) OPUS_GET_FORCE_CHANNELS_REQUEST, __opus_check_int_ptr(x) - -/** Configures the maximum bandpass that the encoder will select automatically. - * Applications should normally use this instead of #OPUS_SET_BANDWIDTH - * (leaving that set to the default, #OPUS_AUTO). This allows the - * application to set an upper bound based on the type of input it is - * providing, but still gives the encoder the freedom to reduce the bandpass - * when the bitrate becomes too low, for better overall quality. - * @see OPUS_GET_MAX_BANDWIDTH - * @param[in] x opus_int32: Allowed values: - *
- *
OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
- *
OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
- *
OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
- *
OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
- *
OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
- *
- * @hideinitializer */ -#define OPUS_SET_MAX_BANDWIDTH(x) OPUS_SET_MAX_BANDWIDTH_REQUEST, __opus_check_int(x) - -/** Gets the encoder's configured maximum allowed bandpass. - * @see OPUS_SET_MAX_BANDWIDTH - * @param[out] x opus_int32 *: Allowed values: - *
- *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
- *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
- *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
- *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
- *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband (default)
- *
- * @hideinitializer */ -#define OPUS_GET_MAX_BANDWIDTH(x) OPUS_GET_MAX_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) - -/** Sets the encoder's bandpass to a specific value. - * This prevents the encoder from automatically selecting the bandpass based - * on the available bitrate. If an application knows the bandpass of the input - * audio it is providing, it should normally use #OPUS_SET_MAX_BANDWIDTH - * instead, which still gives the encoder the freedom to reduce the bandpass - * when the bitrate becomes too low, for better overall quality. - * @see OPUS_GET_BANDWIDTH - * @param[in] x opus_int32: Allowed values: - *
- *
#OPUS_AUTO
(default)
- *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
- *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
- *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
- *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
- *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
- *
- * @hideinitializer */ -#define OPUS_SET_BANDWIDTH(x) OPUS_SET_BANDWIDTH_REQUEST, __opus_check_int(x) - -/** Configures the type of signal being encoded. - * This is a hint which helps the encoder's mode selection. - * @see OPUS_GET_SIGNAL - * @param[in] x opus_int32: Allowed values: - *
- *
#OPUS_AUTO
(default)
- *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
- *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
- *
- * @hideinitializer */ -#define OPUS_SET_SIGNAL(x) OPUS_SET_SIGNAL_REQUEST, __opus_check_int(x) -/** Gets the encoder's configured signal type. - * @see OPUS_SET_SIGNAL - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
#OPUS_AUTO
(default)
- *
#OPUS_SIGNAL_VOICE
Bias thresholds towards choosing LPC or Hybrid modes.
- *
#OPUS_SIGNAL_MUSIC
Bias thresholds towards choosing MDCT modes.
- *
- * @hideinitializer */ -#define OPUS_GET_SIGNAL(x) OPUS_GET_SIGNAL_REQUEST, __opus_check_int_ptr(x) - - -/** Configures the encoder's intended application. - * The initial value is a mandatory argument to the encoder_create function. - * @see OPUS_GET_APPLICATION - * @param[in] x opus_int32: Returns one of the following values: - *
- *
#OPUS_APPLICATION_VOIP
- *
Process signal for improved speech intelligibility.
- *
#OPUS_APPLICATION_AUDIO
- *
Favor faithfulness to the original input.
- *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
- *
Configure the minimum possible coding delay by disabling certain modes - * of operation.
- *
- * @hideinitializer */ -#define OPUS_SET_APPLICATION(x) OPUS_SET_APPLICATION_REQUEST, __opus_check_int(x) -/** Gets the encoder's configured application. - * @see OPUS_SET_APPLICATION - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
#OPUS_APPLICATION_VOIP
- *
Process signal for improved speech intelligibility.
- *
#OPUS_APPLICATION_AUDIO
- *
Favor faithfulness to the original input.
- *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
- *
Configure the minimum possible coding delay by disabling certain modes - * of operation.
- *
- * @hideinitializer */ -#define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x) - -/** Gets the total samples of delay added by the entire codec. - * This can be queried by the encoder and then the provided number of samples can be - * skipped on from the start of the decoder's output to provide time aligned input - * and output. From the perspective of a decoding application the real data begins this many - * samples late. - * - * The decoder contribution to this delay is identical for all decoders, but the - * encoder portion of the delay may vary from implementation to implementation, - * version to version, or even depend on the encoder's initial configuration. - * Applications needing delay compensation should call this CTL rather than - * hard-coding a value. - * @param[out] x opus_int32 *: Number of lookahead samples - * @hideinitializer */ -#define OPUS_GET_LOOKAHEAD(x) OPUS_GET_LOOKAHEAD_REQUEST, __opus_check_int_ptr(x) - -/** Configures the encoder's use of inband forward error correction (FEC). - * @note This is only applicable to the LPC layer - * @see OPUS_GET_INBAND_FEC - * @param[in] x opus_int32: Allowed values: - *
- *
0
Disable inband FEC (default).
- *
1
Inband FEC enabled. If the packet loss rate is sufficiently high, Opus will automatically switch to SILK even at high rates to enable use of that FEC.
- *
2
Inband FEC enabled, but does not necessarily switch to SILK if we have music.
- *
- * @hideinitializer */ -#define OPUS_SET_INBAND_FEC(x) OPUS_SET_INBAND_FEC_REQUEST, __opus_check_int(x) -/** Gets encoder's configured use of inband forward error correction. - * @see OPUS_SET_INBAND_FEC - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
0
Inband FEC disabled (default).
- *
1
Inband FEC enabled. If the packet loss rate is sufficiently high, Opus will automatically switch to SILK even at high rates to enable use of that FEC.
- *
2
Inband FEC enabled, but does not necessarily switch to SILK if we have music.
- *
- * @hideinitializer */ -#define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x) - -/** Configures the encoder's expected packet loss percentage. - * Higher values trigger progressively more loss resistant behavior in the encoder - * at the expense of quality at a given bitrate in the absence of packet loss, but - * greater quality under loss. - * @see OPUS_GET_PACKET_LOSS_PERC - * @param[in] x opus_int32: Loss percentage in the range 0-100, inclusive (default: 0). - * @hideinitializer */ -#define OPUS_SET_PACKET_LOSS_PERC(x) OPUS_SET_PACKET_LOSS_PERC_REQUEST, __opus_check_int(x) -/** Gets the encoder's configured packet loss percentage. - * @see OPUS_SET_PACKET_LOSS_PERC - * @param[out] x opus_int32 *: Returns the configured loss percentage - * in the range 0-100, inclusive (default: 0). - * @hideinitializer */ -#define OPUS_GET_PACKET_LOSS_PERC(x) OPUS_GET_PACKET_LOSS_PERC_REQUEST, __opus_check_int_ptr(x) - -/** Configures the encoder's use of discontinuous transmission (DTX). - * @note This is only applicable to the LPC layer - * @see OPUS_GET_DTX - * @param[in] x opus_int32: Allowed values: - *
- *
0
Disable DTX (default).
- *
1
Enabled DTX.
- *
- * @hideinitializer */ -#define OPUS_SET_DTX(x) OPUS_SET_DTX_REQUEST, __opus_check_int(x) -/** Gets encoder's configured use of discontinuous transmission. - * @see OPUS_SET_DTX - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
0
DTX disabled (default).
- *
1
DTX enabled.
- *
- * @hideinitializer */ -#define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x) -/** Configures the depth of signal being encoded. - * - * This is a hint which helps the encoder identify silence and near-silence. - * It represents the number of significant bits of linear intensity below - * which the signal contains ignorable quantization or other noise. - * - * For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting - * for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate - * for 16-bit linear pcm input with opus_encode_float(). - * - * When using opus_encode() instead of opus_encode_float(), or when libopus - * is compiled for fixed-point, the encoder uses the minimum of the value - * set here and the value 16. - * - * @see OPUS_GET_LSB_DEPTH - * @param[in] x opus_int32: Input precision in bits, between 8 and 24 - * (default: 24). - * @hideinitializer */ -#define OPUS_SET_LSB_DEPTH(x) OPUS_SET_LSB_DEPTH_REQUEST, __opus_check_int(x) -/** Gets the encoder's configured signal depth. - * @see OPUS_SET_LSB_DEPTH - * @param[out] x opus_int32 *: Input precision in bits, between 8 and - * 24 (default: 24). - * @hideinitializer */ -#define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x) - -/** Configures the encoder's use of variable duration frames. - * When variable duration is enabled, the encoder is free to use a shorter frame - * size than the one requested in the opus_encode*() call. - * It is then the user's responsibility - * to verify how much audio was encoded by checking the ToC byte of the encoded - * packet. The part of the audio that was not encoded needs to be resent to the - * encoder for the next call. Do not use this option unless you really - * know what you are doing. - * @see OPUS_GET_EXPERT_FRAME_DURATION - * @param[in] x opus_int32: Allowed values: - *
- *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
- *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
- *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
- *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
- *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
- *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
- *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
- *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
- *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
- *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
- *
- * @hideinitializer */ -#define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x) -/** Gets the encoder's configured use of variable duration frames. - * @see OPUS_SET_EXPERT_FRAME_DURATION - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
- *
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
- *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
- *
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
- *
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
- *
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
- *
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
- *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
- *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
- *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
- *
- * @hideinitializer */ -#define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x) - -/** If set to 1, disables almost all use of prediction, making frames almost - * completely independent. This reduces quality. - * @see OPUS_GET_PREDICTION_DISABLED - * @param[in] x opus_int32: Allowed values: - *
- *
0
Enable prediction (default).
- *
1
Disable prediction.
- *
- * @hideinitializer */ -#define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x) -/** Gets the encoder's configured prediction status. - * @see OPUS_SET_PREDICTION_DISABLED - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
0
Prediction enabled (default).
- *
1
Prediction disabled.
- *
- * @hideinitializer */ -#define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x) - -/** If non-zero, enables Deep Redundancy (DRED) and use the specified maximum number of 10-ms redundant frames - * @hideinitializer */ -#define OPUS_SET_DRED_DURATION(x) OPUS_SET_DRED_DURATION_REQUEST, __opus_check_int(x) -/** Gets the encoder's configured Deep Redundancy (DRED) maximum number of frames. - * @hideinitializer */ -#define OPUS_GET_DRED_DURATION(x) OPUS_GET_DRED_DURATION_REQUEST, __opus_check_int_ptr(x) - -/** Provide external DNN weights from binary object (only when explicitly built without the weights) - * @hideinitializer */ -#define OPUS_SET_DNN_BLOB(data, len) OPUS_SET_DNN_BLOB_REQUEST, __opus_check_void_ptr(data), __opus_check_int(len) - - -/**@}*/ - -/** @defgroup opus_genericctls Generic CTLs - * - * These macros are used with the \c opus_decoder_ctl and - * \c opus_encoder_ctl calls to generate a particular - * request. - * - * When called on an \c OpusDecoder they apply to that - * particular decoder instance. When called on an - * \c OpusEncoder they apply to the corresponding setting - * on that encoder instance, if present. - * - * Some usage examples: - * - * @code - * int ret; - * opus_int32 pitch; - * ret = opus_decoder_ctl(dec_ctx, OPUS_GET_PITCH(&pitch)); - * if (ret == OPUS_OK) return ret; - * - * opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE); - * opus_decoder_ctl(dec_ctx, OPUS_RESET_STATE); - * - * opus_int32 enc_bw, dec_bw; - * opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&enc_bw)); - * opus_decoder_ctl(dec_ctx, OPUS_GET_BANDWIDTH(&dec_bw)); - * if (enc_bw != dec_bw) { - * printf("packet bandwidth mismatch!\n"); - * } - * @endcode - * - * @see opus_encoder, opus_decoder_ctl, opus_encoder_ctl, opus_decoderctls, opus_encoderctls - * @{ - */ - -/** Resets the codec state to be equivalent to a freshly initialized state. - * This should be called when switching streams in order to prevent - * the back to back decoding from giving different results from - * one at a time decoding. - * @hideinitializer */ -#define OPUS_RESET_STATE 4028 - -/** Gets the final state of the codec's entropy coder. - * This is used for testing purposes, - * The encoder and decoder state should be identical after coding a payload - * (assuming no data corruption or software bugs) - * - * @param[out] x opus_uint32 *: Entropy coder state - * - * @hideinitializer */ -#define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x) - -/** Gets the encoder's configured bandpass or the decoder's last bandpass. - * @see OPUS_SET_BANDWIDTH - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
#OPUS_AUTO
(default)
- *
#OPUS_BANDWIDTH_NARROWBAND
4 kHz passband
- *
#OPUS_BANDWIDTH_MEDIUMBAND
6 kHz passband
- *
#OPUS_BANDWIDTH_WIDEBAND
8 kHz passband
- *
#OPUS_BANDWIDTH_SUPERWIDEBAND
12 kHz passband
- *
#OPUS_BANDWIDTH_FULLBAND
20 kHz passband
- *
- * @hideinitializer */ -#define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) - -/** Gets the sampling rate the encoder or decoder was initialized with. - * This simply returns the Fs value passed to opus_encoder_init() - * or opus_decoder_init(). - * @param[out] x opus_int32 *: Sampling rate of encoder or decoder. - * @hideinitializer - */ -#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x) - -/** If set to 1, disables the use of phase inversion for intensity stereo, - * improving the quality of mono downmixes, but slightly reducing normal - * stereo quality. Disabling phase inversion in the decoder does not comply - * with RFC 6716, although it does not cause any interoperability issue and - * is expected to become part of the Opus standard once RFC 6716 is updated - * by draft-ietf-codec-opus-update. - * @see OPUS_GET_PHASE_INVERSION_DISABLED - * @param[in] x opus_int32: Allowed values: - *
- *
0
Enable phase inversion (default).
- *
1
Disable phase inversion.
- *
- * @hideinitializer */ -#define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x) -/** Gets the encoder's configured phase inversion status. - * @see OPUS_SET_PHASE_INVERSION_DISABLED - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
0
Stereo phase inversion enabled (default).
- *
1
Stereo phase inversion disabled.
- *
- * @hideinitializer */ -#define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x) -/** Gets the DTX state of the encoder. - * Returns whether the last encoded frame was either a comfort noise update - * during DTX or not encoded because of DTX. - * @param[out] x opus_int32 *: Returns one of the following values: - *
- *
0
The encoder is not in DTX.
- *
1
The encoder is in DTX.
- *
- * @hideinitializer */ -#define OPUS_GET_IN_DTX(x) OPUS_GET_IN_DTX_REQUEST, __opus_check_int_ptr(x) - -/**@}*/ - -/** @defgroup opus_decoderctls Decoder related CTLs - * @see opus_genericctls, opus_encoderctls, opus_decoder - * @{ - */ - -/** Configures decoder gain adjustment. - * Scales the decoded output by a factor specified in Q8 dB units. - * This has a maximum range of -32768 to 32767 inclusive, and returns - * OPUS_BAD_ARG otherwise. The default is zero indicating no adjustment. - * This setting survives decoder reset. - * - * gain = pow(10, x/(20.0*256)) - * - * @param[in] x opus_int32: Amount to scale PCM signal by in Q8 dB units. - * @hideinitializer */ -#define OPUS_SET_GAIN(x) OPUS_SET_GAIN_REQUEST, __opus_check_int(x) -/** Gets the decoder's configured gain adjustment. @see OPUS_SET_GAIN - * - * @param[out] x opus_int32 *: Amount to scale PCM signal by in Q8 dB units. - * @hideinitializer */ -#define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x) - -/** Gets the duration (in samples) of the last packet successfully decoded or concealed. - * @param[out] x opus_int32 *: Number of samples (at current sampling rate). - * @hideinitializer */ -#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x) - -/** Gets the pitch of the last decoded frame, if available. - * This can be used for any post-processing algorithm requiring the use of pitch, - * e.g. time stretching/shortening. If the last frame was not voiced, or if the - * pitch was not coded in the frame, then zero is returned. - * - * This CTL is only implemented for decoder instances. - * - * @param[out] x opus_int32 *: pitch period at 48 kHz (or 0 if not available) - * - * @hideinitializer */ -#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x) - -/**@}*/ - -/** @defgroup opus_libinfo Opus library information functions - * @{ - */ - -/** Converts an opus error code into a human readable string. - * - * @param[in] error int: Error number - * @returns Error string - */ -OPUS_EXPORT const char *opus_strerror(int error); - -/** Gets the libopus version string. - * - * Applications may look for the substring "-fixed" in the version string to - * determine whether they have a fixed-point or floating-point build at - * runtime. - * - * @returns Version string - */ -OPUS_EXPORT const char *opus_get_version_string(void); -/**@}*/ - -#ifdef __cplusplus -} -#endif - -#endif /* OPUS_DEFINES_H */ diff --git a/third-party/webrtc/dependencies/third_party/opus/src/include/opus_multistream.h b/third-party/webrtc/dependencies/third_party/opus/src/include/opus_multistream.h deleted file mode 100644 index 824cc55ac5..0000000000 --- a/third-party/webrtc/dependencies/third_party/opus/src/include/opus_multistream.h +++ /dev/null @@ -1,660 +0,0 @@ -/* Copyright (c) 2011 Xiph.Org Foundation - Written by Jean-Marc Valin */ -/* - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER - OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/** - * @file opus_multistream.h - * @brief Opus reference implementation multistream API - */ - -#ifndef OPUS_MULTISTREAM_H -#define OPUS_MULTISTREAM_H - -#include "opus.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** @cond OPUS_INTERNAL_DOC */ - -/** Macros to trigger compilation errors when the wrong types are provided to a - * CTL. */ -/**@{*/ -#define __opus_check_encstate_ptr(ptr) ((ptr) + ((ptr) - (OpusEncoder**)(ptr))) -#define __opus_check_decstate_ptr(ptr) ((ptr) + ((ptr) - (OpusDecoder**)(ptr))) -/**@}*/ - -/** These are the actual encoder and decoder CTL ID numbers. - * They should not be used directly by applications. - * In general, SETs should be even and GETs should be odd.*/ -/**@{*/ -#define OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST 5120 -#define OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST 5122 -/**@}*/ - -/** @endcond */ - -/** @defgroup opus_multistream_ctls Multistream specific encoder and decoder CTLs - * - * These are convenience macros that are specific to the - * opus_multistream_encoder_ctl() and opus_multistream_decoder_ctl() - * interface. - * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, and - * @ref opus_decoderctls may be applied to a multistream encoder or decoder as - * well. - * In addition, you may retrieve the encoder or decoder state for an specific - * stream via #OPUS_MULTISTREAM_GET_ENCODER_STATE or - * #OPUS_MULTISTREAM_GET_DECODER_STATE and apply CTLs to it individually. - */ -/**@{*/ - -/** Gets the encoder state for an individual stream of a multistream encoder. - * @param[in] x opus_int32: The index of the stream whose encoder you - * wish to retrieve. - * This must be non-negative and less than - * the streams parameter used - * to initialize the encoder. - * @param[out] y OpusEncoder**: Returns a pointer to the given - * encoder state. - * @retval OPUS_BAD_ARG The index of the requested stream was out of range. - * @hideinitializer - */ -#define OPUS_MULTISTREAM_GET_ENCODER_STATE(x,y) OPUS_MULTISTREAM_GET_ENCODER_STATE_REQUEST, __opus_check_int(x), __opus_check_encstate_ptr(y) - -/** Gets the decoder state for an individual stream of a multistream decoder. - * @param[in] x opus_int32: The index of the stream whose decoder you - * wish to retrieve. - * This must be non-negative and less than - * the streams parameter used - * to initialize the decoder. - * @param[out] y OpusDecoder**: Returns a pointer to the given - * decoder state. - * @retval OPUS_BAD_ARG The index of the requested stream was out of range. - * @hideinitializer - */ -#define OPUS_MULTISTREAM_GET_DECODER_STATE(x,y) OPUS_MULTISTREAM_GET_DECODER_STATE_REQUEST, __opus_check_int(x), __opus_check_decstate_ptr(y) - -/**@}*/ - -/** @defgroup opus_multistream Opus Multistream API - * @{ - * - * The multistream API allows individual Opus streams to be combined into a - * single packet, enabling support for up to 255 channels. Unlike an - * elementary Opus stream, the encoder and decoder must negotiate the channel - * configuration before the decoder can successfully interpret the data in the - * packets produced by the encoder. Some basic information, such as packet - * duration, can be computed without any special negotiation. - * - * The format for multistream Opus packets is defined in - * RFC 7845 - * and is based on the self-delimited Opus framing described in Appendix B of - * RFC 6716. - * Normal Opus packets are just a degenerate case of multistream Opus packets, - * and can be encoded or decoded with the multistream API by setting - * streams to 1 when initializing the encoder or - * decoder. - * - * Multistream Opus streams can contain up to 255 elementary Opus streams. - * These may be either "uncoupled" or "coupled", indicating that the decoder - * is configured to decode them to either 1 or 2 channels, respectively. - * The streams are ordered so that all coupled streams appear at the - * beginning. - * - * A mapping table defines which decoded channel i - * should be used for each input/output (I/O) channel j. This table is - * typically provided as an unsigned char array. - * Let i = mapping[j] be the index for I/O channel j. - * If i < 2*coupled_streams, then I/O channel j is - * encoded as the left channel of stream (i/2) if i - * is even, or as the right channel of stream (i/2) if - * i is odd. Otherwise, I/O channel j is encoded as - * mono in stream (i - coupled_streams), unless it has the special - * value 255, in which case it is omitted from the encoding entirely (the - * decoder will reproduce it as silence). Each value i must either - * be the special value 255 or be less than streams + coupled_streams. - * - * The output channels specified by the encoder - * should use the - * Vorbis - * channel ordering. A decoder may wish to apply an additional permutation - * to the mapping the encoder used to achieve a different output channel - * order (e.g. for outputting in WAV order). - * - * Each multistream packet contains an Opus packet for each stream, and all of - * the Opus packets in a single multistream packet must have the same - * duration. Therefore the duration of a multistream packet can be extracted - * from the TOC sequence of the first stream, which is located at the - * beginning of the packet, just like an elementary Opus stream: - * - * @code - * int nb_samples; - * int nb_frames; - * nb_frames = opus_packet_get_nb_frames(data, len); - * if (nb_frames < 1) - * return nb_frames; - * nb_samples = opus_packet_get_samples_per_frame(data, 48000) * nb_frames; - * @endcode - * - * The general encoding and decoding process proceeds exactly the same as in - * the normal @ref opus_encoder and @ref opus_decoder APIs. - * See their documentation for an overview of how to use the corresponding - * multistream functions. - */ - -/** Opus multistream encoder state. - * This contains the complete state of a multistream Opus encoder. - * It is position independent and can be freely copied. - * @see opus_multistream_encoder_create - * @see opus_multistream_encoder_init - */ -typedef struct OpusMSEncoder OpusMSEncoder; - -/** Opus multistream decoder state. - * This contains the complete state of a multistream Opus decoder. - * It is position independent and can be freely copied. - * @see opus_multistream_decoder_create - * @see opus_multistream_decoder_init - */ -typedef struct OpusMSDecoder OpusMSDecoder; - -/**\name Multistream encoder functions */ -/**@{*/ - -/** Gets the size of an OpusMSEncoder structure. - * @param streams int: The total number of streams to encode from the - * input. - * This must be no more than 255. - * @param coupled_streams int: Number of coupled (2 channel) streams - * to encode. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * encoded channels (streams + - * coupled_streams) must be no - * more than 255. - * @returns The size in bytes on success, or a negative error code - * (see @ref opus_errorcodes) on error. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_encoder_get_size( - int streams, - int coupled_streams -); - -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_surround_encoder_get_size( - int channels, - int mapping_family -); - - -/** Allocates and initializes a multistream encoder state. - * Call opus_multistream_encoder_destroy() to release - * this object when finished. - * @param Fs opus_int32: Sampling rate of the input signal (in Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param channels int: Number of channels in the input signal. - * This must be at most 255. - * It may be greater than the number of - * coded channels (streams + - * coupled_streams). - * @param streams int: The total number of streams to encode from the - * input. - * This must be no more than the number of channels. - * @param coupled_streams int: Number of coupled (2 channel) streams - * to encode. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * encoded channels (streams + - * coupled_streams) must be no - * more than the number of input channels. - * @param[in] mapping const unsigned char[channels]: Mapping from - * encoded channels to input channels, as described in - * @ref opus_multistream. As an extra constraint, the - * multistream encoder does not allow encoding coupled - * streams for which one channel is unused since this - * is never a good idea. - * @param application int: The target encoder application. - * This must be one of the following: - *
- *
#OPUS_APPLICATION_VOIP
- *
Process signal for improved speech intelligibility.
- *
#OPUS_APPLICATION_AUDIO
- *
Favor faithfulness to the original input.
- *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
- *
Configure the minimum possible coding delay by disabling certain modes - * of operation.
- *
- * @param[out] error int *: Returns #OPUS_OK on success, or an error - * code (see @ref opus_errorcodes) on - * failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_encoder_create( - opus_int32 Fs, - int channels, - int streams, - int coupled_streams, - const unsigned char *mapping, - int application, - int *error -) OPUS_ARG_NONNULL(5); - -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSEncoder *opus_multistream_surround_encoder_create( - opus_int32 Fs, - int channels, - int mapping_family, - int *streams, - int *coupled_streams, - unsigned char *mapping, - int application, - int *error -) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); - -/** Initialize a previously allocated multistream encoder state. - * The memory pointed to by \a st must be at least the size returned by - * opus_multistream_encoder_get_size(). - * This is intended for applications which use their own allocator instead of - * malloc. - * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. - * @see opus_multistream_encoder_create - * @see opus_multistream_encoder_get_size - * @param st OpusMSEncoder*: Multistream encoder state to initialize. - * @param Fs opus_int32: Sampling rate of the input signal (in Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param channels int: Number of channels in the input signal. - * This must be at most 255. - * It may be greater than the number of - * coded channels (streams + - * coupled_streams). - * @param streams int: The total number of streams to encode from the - * input. - * This must be no more than the number of channels. - * @param coupled_streams int: Number of coupled (2 channel) streams - * to encode. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * encoded channels (streams + - * coupled_streams) must be no - * more than the number of input channels. - * @param[in] mapping const unsigned char[channels]: Mapping from - * encoded channels to input channels, as described in - * @ref opus_multistream. As an extra constraint, the - * multistream encoder does not allow encoding coupled - * streams for which one channel is unused since this - * is never a good idea. - * @param application int: The target encoder application. - * This must be one of the following: - *
- *
#OPUS_APPLICATION_VOIP
- *
Process signal for improved speech intelligibility.
- *
#OPUS_APPLICATION_AUDIO
- *
Favor faithfulness to the original input.
- *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
- *
Configure the minimum possible coding delay by disabling certain modes - * of operation.
- *
- * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) - * on failure. - */ -OPUS_EXPORT int opus_multistream_encoder_init( - OpusMSEncoder *st, - opus_int32 Fs, - int channels, - int streams, - int coupled_streams, - const unsigned char *mapping, - int application -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); - -OPUS_EXPORT int opus_multistream_surround_encoder_init( - OpusMSEncoder *st, - opus_int32 Fs, - int channels, - int mapping_family, - int *streams, - int *coupled_streams, - unsigned char *mapping, - int application -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6) OPUS_ARG_NONNULL(7); - -/** Encodes a multistream Opus frame. - * @param st OpusMSEncoder*: Multistream encoder state. - * @param[in] pcm const opus_int16*: The input signal as interleaved - * samples. - * This must contain - * frame_size*channels - * samples. - * @param frame_size int: Number of samples per channel in the input - * signal. - * This must be an Opus frame size for the - * encoder's sampling rate. - * For example, at 48 kHz the permitted values - * are 120, 240, 480, 960, 1920, and 2880. - * Passing in a duration of less than 10 ms - * (480 samples at 48 kHz) will prevent the - * encoder from using the LPC or hybrid modes. - * @param[out] data unsigned char*: Output payload. - * This must contain storage for at - * least \a max_data_bytes. - * @param [in] max_data_bytes opus_int32: Size of the allocated - * memory for the output - * payload. This may be - * used to impose an upper limit on - * the instant bitrate, but should - * not be used as the only bitrate - * control. Use #OPUS_SET_BITRATE to - * control the bitrate. - * @returns The length of the encoded packet (in bytes) on success or a - * negative error code (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode( - OpusMSEncoder *st, - const opus_int16 *pcm, - int frame_size, - unsigned char *data, - opus_int32 max_data_bytes -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); - -/** Encodes a multistream Opus frame from floating point input. - * @param st OpusMSEncoder*: Multistream encoder state. - * @param[in] pcm const float*: The input signal as interleaved - * samples with a normal range of - * +/-1.0. - * Samples with a range beyond +/-1.0 - * are supported but will be clipped by - * decoders using the integer API and - * should only be used if it is known - * that the far end supports extended - * dynamic range. - * This must contain - * frame_size*channels - * samples. - * @param frame_size int: Number of samples per channel in the input - * signal. - * This must be an Opus frame size for the - * encoder's sampling rate. - * For example, at 48 kHz the permitted values - * are 120, 240, 480, 960, 1920, and 2880. - * Passing in a duration of less than 10 ms - * (480 samples at 48 kHz) will prevent the - * encoder from using the LPC or hybrid modes. - * @param[out] data unsigned char*: Output payload. - * This must contain storage for at - * least \a max_data_bytes. - * @param [in] max_data_bytes opus_int32: Size of the allocated - * memory for the output - * payload. This may be - * used to impose an upper limit on - * the instant bitrate, but should - * not be used as the only bitrate - * control. Use #OPUS_SET_BITRATE to - * control the bitrate. - * @returns The length of the encoded packet (in bytes) on success or a - * negative error code (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_encode_float( - OpusMSEncoder *st, - const float *pcm, - int frame_size, - unsigned char *data, - opus_int32 max_data_bytes -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); - -/** Frees an OpusMSEncoder allocated by - * opus_multistream_encoder_create(). - * @param st OpusMSEncoder*: Multistream encoder state to be freed. - */ -OPUS_EXPORT void opus_multistream_encoder_destroy(OpusMSEncoder *st); - -/** Perform a CTL function on a multistream Opus encoder. - * - * Generally the request and subsequent arguments are generated by a - * convenience macro. - * @param st OpusMSEncoder*: Multistream encoder state. - * @param request This and all remaining parameters should be replaced by one - * of the convenience macros in @ref opus_genericctls, - * @ref opus_encoderctls, or @ref opus_multistream_ctls. - * @see opus_genericctls - * @see opus_encoderctls - * @see opus_multistream_ctls - */ -OPUS_EXPORT int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); - -/**@}*/ - -/**\name Multistream decoder functions */ -/**@{*/ - -/** Gets the size of an OpusMSDecoder structure. - * @param streams int: The total number of streams coded in the - * input. - * This must be no more than 255. - * @param coupled_streams int: Number streams to decode as coupled - * (2 channel) streams. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * coded channels (streams + - * coupled_streams) must be no - * more than 255. - * @returns The size in bytes on success, or a negative error code - * (see @ref opus_errorcodes) on error. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_multistream_decoder_get_size( - int streams, - int coupled_streams -); - -/** Allocates and initializes a multistream decoder state. - * Call opus_multistream_decoder_destroy() to release - * this object when finished. - * @param Fs opus_int32: Sampling rate to decode at (in Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param channels int: Number of channels to output. - * This must be at most 255. - * It may be different from the number of coded - * channels (streams + - * coupled_streams). - * @param streams int: The total number of streams coded in the - * input. - * This must be no more than 255. - * @param coupled_streams int: Number of streams to decode as coupled - * (2 channel) streams. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * coded channels (streams + - * coupled_streams) must be no - * more than 255. - * @param[in] mapping const unsigned char[channels]: Mapping from - * coded channels to output channels, as described in - * @ref opus_multistream. - * @param[out] error int *: Returns #OPUS_OK on success, or an error - * code (see @ref opus_errorcodes) on - * failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusMSDecoder *opus_multistream_decoder_create( - opus_int32 Fs, - int channels, - int streams, - int coupled_streams, - const unsigned char *mapping, - int *error -) OPUS_ARG_NONNULL(5); - -/** Intialize a previously allocated decoder state object. - * The memory pointed to by \a st must be at least the size returned by - * opus_multistream_encoder_get_size(). - * This is intended for applications which use their own allocator instead of - * malloc. - * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. - * @see opus_multistream_decoder_create - * @see opus_multistream_deocder_get_size - * @param st OpusMSEncoder*: Multistream encoder state to initialize. - * @param Fs opus_int32: Sampling rate to decode at (in Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param channels int: Number of channels to output. - * This must be at most 255. - * It may be different from the number of coded - * channels (streams + - * coupled_streams). - * @param streams int: The total number of streams coded in the - * input. - * This must be no more than 255. - * @param coupled_streams int: Number of streams to decode as coupled - * (2 channel) streams. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * coded channels (streams + - * coupled_streams) must be no - * more than 255. - * @param[in] mapping const unsigned char[channels]: Mapping from - * coded channels to output channels, as described in - * @ref opus_multistream. - * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) - * on failure. - */ -OPUS_EXPORT int opus_multistream_decoder_init( - OpusMSDecoder *st, - opus_int32 Fs, - int channels, - int streams, - int coupled_streams, - const unsigned char *mapping -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); - -/** Decode a multistream Opus packet. - * @param st OpusMSDecoder*: Multistream decoder state. - * @param[in] data const unsigned char*: Input payload. - * Use a NULL - * pointer to indicate packet - * loss. - * @param len opus_int32: Number of bytes in payload. - * @param[out] pcm opus_int16*: Output signal, with interleaved - * samples. - * This must contain room for - * frame_size*channels - * samples. - * @param frame_size int: The number of samples per channel of - * available space in \a pcm. - * If this is less than the maximum packet duration - * (120 ms; 5760 for 48kHz), this function will not be capable - * of decoding some packets. In the case of PLC (data==NULL) - * or FEC (decode_fec=1), then frame_size needs to be exactly - * the duration of audio that is missing, otherwise the - * decoder will not be in the optimal state to decode the - * next incoming packet. For the PLC and FEC cases, frame_size - * must be a multiple of 2.5 ms. - * @param decode_fec int: Flag (0 or 1) to request that any in-band - * forward error correction data be decoded. - * If no such data is available, the frame is - * decoded as if it were lost. - * @returns Number of samples decoded on success or a negative error code - * (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode( - OpusMSDecoder *st, - const unsigned char *data, - opus_int32 len, - opus_int16 *pcm, - int frame_size, - int decode_fec -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); - -/** Decode a multistream Opus packet with floating point output. - * @param st OpusMSDecoder*: Multistream decoder state. - * @param[in] data const unsigned char*: Input payload. - * Use a NULL - * pointer to indicate packet - * loss. - * @param len opus_int32: Number of bytes in payload. - * @param[out] pcm opus_int16*: Output signal, with interleaved - * samples. - * This must contain room for - * frame_size*channels - * samples. - * @param frame_size int: The number of samples per channel of - * available space in \a pcm. - * If this is less than the maximum packet duration - * (120 ms; 5760 for 48kHz), this function will not be capable - * of decoding some packets. In the case of PLC (data==NULL) - * or FEC (decode_fec=1), then frame_size needs to be exactly - * the duration of audio that is missing, otherwise the - * decoder will not be in the optimal state to decode the - * next incoming packet. For the PLC and FEC cases, frame_size - * must be a multiple of 2.5 ms. - * @param decode_fec int: Flag (0 or 1) to request that any in-band - * forward error correction data be decoded. - * If no such data is available, the frame is - * decoded as if it were lost. - * @returns Number of samples decoded on success or a negative error code - * (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_multistream_decode_float( - OpusMSDecoder *st, - const unsigned char *data, - opus_int32 len, - float *pcm, - int frame_size, - int decode_fec -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); - -/** Perform a CTL function on a multistream Opus decoder. - * - * Generally the request and subsequent arguments are generated by a - * convenience macro. - * @param st OpusMSDecoder*: Multistream decoder state. - * @param request This and all remaining parameters should be replaced by one - * of the convenience macros in @ref opus_genericctls, - * @ref opus_decoderctls, or @ref opus_multistream_ctls. - * @see opus_genericctls - * @see opus_decoderctls - * @see opus_multistream_ctls - */ -OPUS_EXPORT int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); - -/** Frees an OpusMSDecoder allocated by - * opus_multistream_decoder_create(). - * @param st OpusMSDecoder: Multistream decoder state to be freed. - */ -OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st); - -/**@}*/ - -/**@}*/ - -#ifdef __cplusplus -} -#endif - -#endif /* OPUS_MULTISTREAM_H */ diff --git a/third-party/webrtc/dependencies/third_party/opus/src/include/opus_projection.h b/third-party/webrtc/dependencies/third_party/opus/src/include/opus_projection.h deleted file mode 100644 index 9dabf4e85c..0000000000 --- a/third-party/webrtc/dependencies/third_party/opus/src/include/opus_projection.h +++ /dev/null @@ -1,568 +0,0 @@ -/* Copyright (c) 2017 Google Inc. - Written by Andrew Allen */ -/* - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER - OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -/** - * @file opus_projection.h - * @brief Opus projection reference API - */ - -#ifndef OPUS_PROJECTION_H -#define OPUS_PROJECTION_H - -#include "opus_multistream.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** @cond OPUS_INTERNAL_DOC */ - -/** These are the actual encoder and decoder CTL ID numbers. - * They should not be used directly by applications.c - * In general, SETs should be even and GETs should be odd.*/ -/**@{*/ -#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST 6001 -#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST 6003 -#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST 6005 -/**@}*/ - - -/** @endcond */ - -/** @defgroup opus_projection_ctls Projection specific encoder and decoder CTLs - * - * These are convenience macros that are specific to the - * opus_projection_encoder_ctl() and opus_projection_decoder_ctl() - * interface. - * The CTLs from @ref opus_genericctls, @ref opus_encoderctls, - * @ref opus_decoderctls, and @ref opus_multistream_ctls may be applied to a - * projection encoder or decoder as well. - */ -/**@{*/ - -/** Gets the gain (in dB. S7.8-format) of the demixing matrix from the encoder. - * @param[out] x opus_int32 *: Returns the gain (in dB. S7.8-format) - * of the demixing matrix. - * @hideinitializer - */ -#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_GAIN_REQUEST, __opus_check_int_ptr(x) - - -/** Gets the size in bytes of the demixing matrix from the encoder. - * @param[out] x opus_int32 *: Returns the size in bytes of the - * demixing matrix. - * @hideinitializer - */ -#define OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE(x) OPUS_PROJECTION_GET_DEMIXING_MATRIX_SIZE_REQUEST, __opus_check_int_ptr(x) - - -/** Copies the demixing matrix to the supplied pointer location. - * @param[out] x unsigned char *: Returns the demixing matrix to the - * supplied pointer location. - * @param y opus_int32: The size in bytes of the reserved memory at the - * pointer location. - * @hideinitializer - */ -#define OPUS_PROJECTION_GET_DEMIXING_MATRIX(x,y) OPUS_PROJECTION_GET_DEMIXING_MATRIX_REQUEST, x, __opus_check_int(y) - - -/**@}*/ - -/** Opus projection encoder state. - * This contains the complete state of a projection Opus encoder. - * It is position independent and can be freely copied. - * @see opus_projection_ambisonics_encoder_create - */ -typedef struct OpusProjectionEncoder OpusProjectionEncoder; - - -/** Opus projection decoder state. - * This contains the complete state of a projection Opus decoder. - * It is position independent and can be freely copied. - * @see opus_projection_decoder_create - * @see opus_projection_decoder_init - */ -typedef struct OpusProjectionDecoder OpusProjectionDecoder; - - -/**\name Projection encoder functions */ -/**@{*/ - -/** Gets the size of an OpusProjectionEncoder structure. - * @param channels int: The total number of input channels to encode. - * This must be no more than 255. - * @param mapping_family int: The mapping family to use for selecting - * the appropriate projection. - * @returns The size in bytes on success, or a negative error code - * (see @ref opus_errorcodes) on error. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_ambisonics_encoder_get_size( - int channels, - int mapping_family -); - - -/** Allocates and initializes a projection encoder state. - * Call opus_projection_encoder_destroy() to release - * this object when finished. - * @param Fs opus_int32: Sampling rate of the input signal (in Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param channels int: Number of channels in the input signal. - * This must be at most 255. - * It may be greater than the number of - * coded channels (streams + - * coupled_streams). - * @param mapping_family int: The mapping family to use for selecting - * the appropriate projection. - * @param[out] streams int *: The total number of streams that will - * be encoded from the input. - * @param[out] coupled_streams int *: Number of coupled (2 channel) - * streams that will be encoded from the input. - * @param application int: The target encoder application. - * This must be one of the following: - *
- *
#OPUS_APPLICATION_VOIP
- *
Process signal for improved speech intelligibility.
- *
#OPUS_APPLICATION_AUDIO
- *
Favor faithfulness to the original input.
- *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
- *
Configure the minimum possible coding delay by disabling certain modes - * of operation.
- *
- * @param[out] error int *: Returns #OPUS_OK on success, or an error - * code (see @ref opus_errorcodes) on - * failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionEncoder *opus_projection_ambisonics_encoder_create( - opus_int32 Fs, - int channels, - int mapping_family, - int *streams, - int *coupled_streams, - int application, - int *error -) OPUS_ARG_NONNULL(4) OPUS_ARG_NONNULL(5); - - -/** Initialize a previously allocated projection encoder state. - * The memory pointed to by \a st must be at least the size returned by - * opus_projection_ambisonics_encoder_get_size(). - * This is intended for applications which use their own allocator instead of - * malloc. - * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. - * @see opus_projection_ambisonics_encoder_create - * @see opus_projection_ambisonics_encoder_get_size - * @param st OpusProjectionEncoder*: Projection encoder state to initialize. - * @param Fs opus_int32: Sampling rate of the input signal (in Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param channels int: Number of channels in the input signal. - * This must be at most 255. - * It may be greater than the number of - * coded channels (streams + - * coupled_streams). - * @param streams int: The total number of streams to encode from the - * input. - * This must be no more than the number of channels. - * @param coupled_streams int: Number of coupled (2 channel) streams - * to encode. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * encoded channels (streams + - * coupled_streams) must be no - * more than the number of input channels. - * @param application int: The target encoder application. - * This must be one of the following: - *
- *
#OPUS_APPLICATION_VOIP
- *
Process signal for improved speech intelligibility.
- *
#OPUS_APPLICATION_AUDIO
- *
Favor faithfulness to the original input.
- *
#OPUS_APPLICATION_RESTRICTED_LOWDELAY
- *
Configure the minimum possible coding delay by disabling certain modes - * of operation.
- *
- * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) - * on failure. - */ -OPUS_EXPORT int opus_projection_ambisonics_encoder_init( - OpusProjectionEncoder *st, - opus_int32 Fs, - int channels, - int mapping_family, - int *streams, - int *coupled_streams, - int application -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(5) OPUS_ARG_NONNULL(6); - - -/** Encodes a projection Opus frame. - * @param st OpusProjectionEncoder*: Projection encoder state. - * @param[in] pcm const opus_int16*: The input signal as interleaved - * samples. - * This must contain - * frame_size*channels - * samples. - * @param frame_size int: Number of samples per channel in the input - * signal. - * This must be an Opus frame size for the - * encoder's sampling rate. - * For example, at 48 kHz the permitted values - * are 120, 240, 480, 960, 1920, and 2880. - * Passing in a duration of less than 10 ms - * (480 samples at 48 kHz) will prevent the - * encoder from using the LPC or hybrid modes. - * @param[out] data unsigned char*: Output payload. - * This must contain storage for at - * least \a max_data_bytes. - * @param [in] max_data_bytes opus_int32: Size of the allocated - * memory for the output - * payload. This may be - * used to impose an upper limit on - * the instant bitrate, but should - * not be used as the only bitrate - * control. Use #OPUS_SET_BITRATE to - * control the bitrate. - * @returns The length of the encoded packet (in bytes) on success or a - * negative error code (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode( - OpusProjectionEncoder *st, - const opus_int16 *pcm, - int frame_size, - unsigned char *data, - opus_int32 max_data_bytes -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); - - -/** Encodes a projection Opus frame from floating point input. - * @param st OpusProjectionEncoder*: Projection encoder state. - * @param[in] pcm const float*: The input signal as interleaved - * samples with a normal range of - * +/-1.0. - * Samples with a range beyond +/-1.0 - * are supported but will be clipped by - * decoders using the integer API and - * should only be used if it is known - * that the far end supports extended - * dynamic range. - * This must contain - * frame_size*channels - * samples. - * @param frame_size int: Number of samples per channel in the input - * signal. - * This must be an Opus frame size for the - * encoder's sampling rate. - * For example, at 48 kHz the permitted values - * are 120, 240, 480, 960, 1920, and 2880. - * Passing in a duration of less than 10 ms - * (480 samples at 48 kHz) will prevent the - * encoder from using the LPC or hybrid modes. - * @param[out] data unsigned char*: Output payload. - * This must contain storage for at - * least \a max_data_bytes. - * @param [in] max_data_bytes opus_int32: Size of the allocated - * memory for the output - * payload. This may be - * used to impose an upper limit on - * the instant bitrate, but should - * not be used as the only bitrate - * control. Use #OPUS_SET_BITRATE to - * control the bitrate. - * @returns The length of the encoded packet (in bytes) on success or a - * negative error code (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_encode_float( - OpusProjectionEncoder *st, - const float *pcm, - int frame_size, - unsigned char *data, - opus_int32 max_data_bytes -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(2) OPUS_ARG_NONNULL(4); - - -/** Frees an OpusProjectionEncoder allocated by - * opus_projection_ambisonics_encoder_create(). - * @param st OpusProjectionEncoder*: Projection encoder state to be freed. - */ -OPUS_EXPORT void opus_projection_encoder_destroy(OpusProjectionEncoder *st); - - -/** Perform a CTL function on a projection Opus encoder. - * - * Generally the request and subsequent arguments are generated by a - * convenience macro. - * @param st OpusProjectionEncoder*: Projection encoder state. - * @param request This and all remaining parameters should be replaced by one - * of the convenience macros in @ref opus_genericctls, - * @ref opus_encoderctls, @ref opus_multistream_ctls, or - * @ref opus_projection_ctls - * @see opus_genericctls - * @see opus_encoderctls - * @see opus_multistream_ctls - * @see opus_projection_ctls - */ -OPUS_EXPORT int opus_projection_encoder_ctl(OpusProjectionEncoder *st, int request, ...) OPUS_ARG_NONNULL(1); - - -/**@}*/ - -/**\name Projection decoder functions */ -/**@{*/ - -/** Gets the size of an OpusProjectionDecoder structure. - * @param channels int: The total number of output channels. - * This must be no more than 255. - * @param streams int: The total number of streams coded in the - * input. - * This must be no more than 255. - * @param coupled_streams int: Number streams to decode as coupled - * (2 channel) streams. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * coded channels (streams + - * coupled_streams) must be no - * more than 255. - * @returns The size in bytes on success, or a negative error code - * (see @ref opus_errorcodes) on error. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT opus_int32 opus_projection_decoder_get_size( - int channels, - int streams, - int coupled_streams -); - - -/** Allocates and initializes a projection decoder state. - * Call opus_projection_decoder_destroy() to release - * this object when finished. - * @param Fs opus_int32: Sampling rate to decode at (in Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param channels int: Number of channels to output. - * This must be at most 255. - * It may be different from the number of coded - * channels (streams + - * coupled_streams). - * @param streams int: The total number of streams coded in the - * input. - * This must be no more than 255. - * @param coupled_streams int: Number of streams to decode as coupled - * (2 channel) streams. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * coded channels (streams + - * coupled_streams) must be no - * more than 255. - * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix - * that mapping from coded channels to output channels, - * as described in @ref opus_projection and - * @ref opus_projection_ctls. - * @param demixing_matrix_size opus_int32: The size in bytes of the - * demixing matrix, as - * described in @ref - * opus_projection_ctls. - * @param[out] error int *: Returns #OPUS_OK on success, or an error - * code (see @ref opus_errorcodes) on - * failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT OpusProjectionDecoder *opus_projection_decoder_create( - opus_int32 Fs, - int channels, - int streams, - int coupled_streams, - unsigned char *demixing_matrix, - opus_int32 demixing_matrix_size, - int *error -) OPUS_ARG_NONNULL(5); - - -/** Intialize a previously allocated projection decoder state object. - * The memory pointed to by \a st must be at least the size returned by - * opus_projection_decoder_get_size(). - * This is intended for applications which use their own allocator instead of - * malloc. - * To reset a previously initialized state, use the #OPUS_RESET_STATE CTL. - * @see opus_projection_decoder_create - * @see opus_projection_deocder_get_size - * @param st OpusProjectionDecoder*: Projection encoder state to initialize. - * @param Fs opus_int32: Sampling rate to decode at (in Hz). - * This must be one of 8000, 12000, 16000, - * 24000, or 48000. - * @param channels int: Number of channels to output. - * This must be at most 255. - * It may be different from the number of coded - * channels (streams + - * coupled_streams). - * @param streams int: The total number of streams coded in the - * input. - * This must be no more than 255. - * @param coupled_streams int: Number of streams to decode as coupled - * (2 channel) streams. - * This must be no larger than the total - * number of streams. - * Additionally, The total number of - * coded channels (streams + - * coupled_streams) must be no - * more than 255. - * @param[in] demixing_matrix const unsigned char[demixing_matrix_size]: Demixing matrix - * that mapping from coded channels to output channels, - * as described in @ref opus_projection and - * @ref opus_projection_ctls. - * @param demixing_matrix_size opus_int32: The size in bytes of the - * demixing matrix, as - * described in @ref - * opus_projection_ctls. - * @returns #OPUS_OK on success, or an error code (see @ref opus_errorcodes) - * on failure. - */ -OPUS_EXPORT int opus_projection_decoder_init( - OpusProjectionDecoder *st, - opus_int32 Fs, - int channels, - int streams, - int coupled_streams, - unsigned char *demixing_matrix, - opus_int32 demixing_matrix_size -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(6); - - -/** Decode a projection Opus packet. - * @param st OpusProjectionDecoder*: Projection decoder state. - * @param[in] data const unsigned char*: Input payload. - * Use a NULL - * pointer to indicate packet - * loss. - * @param len opus_int32: Number of bytes in payload. - * @param[out] pcm opus_int16*: Output signal, with interleaved - * samples. - * This must contain room for - * frame_size*channels - * samples. - * @param frame_size int: The number of samples per channel of - * available space in \a pcm. - * If this is less than the maximum packet duration - * (120 ms; 5760 for 48kHz), this function will not be capable - * of decoding some packets. In the case of PLC (data==NULL) - * or FEC (decode_fec=1), then frame_size needs to be exactly - * the duration of audio that is missing, otherwise the - * decoder will not be in the optimal state to decode the - * next incoming packet. For the PLC and FEC cases, frame_size - * must be a multiple of 2.5 ms. - * @param decode_fec int: Flag (0 or 1) to request that any in-band - * forward error correction data be decoded. - * If no such data is available, the frame is - * decoded as if it were lost. - * @returns Number of samples decoded on success or a negative error code - * (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode( - OpusProjectionDecoder *st, - const unsigned char *data, - opus_int32 len, - opus_int16 *pcm, - int frame_size, - int decode_fec -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); - - -/** Decode a projection Opus packet with floating point output. - * @param st OpusProjectionDecoder*: Projection decoder state. - * @param[in] data const unsigned char*: Input payload. - * Use a NULL - * pointer to indicate packet - * loss. - * @param len opus_int32: Number of bytes in payload. - * @param[out] pcm opus_int16*: Output signal, with interleaved - * samples. - * This must contain room for - * frame_size*channels - * samples. - * @param frame_size int: The number of samples per channel of - * available space in \a pcm. - * If this is less than the maximum packet duration - * (120 ms; 5760 for 48kHz), this function will not be capable - * of decoding some packets. In the case of PLC (data==NULL) - * or FEC (decode_fec=1), then frame_size needs to be exactly - * the duration of audio that is missing, otherwise the - * decoder will not be in the optimal state to decode the - * next incoming packet. For the PLC and FEC cases, frame_size - * must be a multiple of 2.5 ms. - * @param decode_fec int: Flag (0 or 1) to request that any in-band - * forward error correction data be decoded. - * If no such data is available, the frame is - * decoded as if it were lost. - * @returns Number of samples decoded on success or a negative error code - * (see @ref opus_errorcodes) on failure. - */ -OPUS_EXPORT OPUS_WARN_UNUSED_RESULT int opus_projection_decode_float( - OpusProjectionDecoder *st, - const unsigned char *data, - opus_int32 len, - float *pcm, - int frame_size, - int decode_fec -) OPUS_ARG_NONNULL(1) OPUS_ARG_NONNULL(4); - - -/** Perform a CTL function on a projection Opus decoder. - * - * Generally the request and subsequent arguments are generated by a - * convenience macro. - * @param st OpusProjectionDecoder*: Projection decoder state. - * @param request This and all remaining parameters should be replaced by one - * of the convenience macros in @ref opus_genericctls, - * @ref opus_decoderctls, @ref opus_multistream_ctls, or - * @ref opus_projection_ctls. - * @see opus_genericctls - * @see opus_decoderctls - * @see opus_multistream_ctls - * @see opus_projection_ctls - */ -OPUS_EXPORT int opus_projection_decoder_ctl(OpusProjectionDecoder *st, int request, ...) OPUS_ARG_NONNULL(1); - - -/** Frees an OpusProjectionDecoder allocated by - * opus_projection_decoder_create(). - * @param st OpusProjectionDecoder: Projection decoder state to be freed. - */ -OPUS_EXPORT void opus_projection_decoder_destroy(OpusProjectionDecoder *st); - - -/**@}*/ - -/**@}*/ - -#ifdef __cplusplus -} -#endif - -#endif /* OPUS_PROJECTION_H */ diff --git a/third-party/webrtc/dependencies/third_party/opus/src/include/opus_types.h b/third-party/webrtc/dependencies/third_party/opus/src/include/opus_types.h deleted file mode 100644 index 7cf675580f..0000000000 --- a/third-party/webrtc/dependencies/third_party/opus/src/include/opus_types.h +++ /dev/null @@ -1,166 +0,0 @@ -/* (C) COPYRIGHT 1994-2002 Xiph.Org Foundation */ -/* Modified by Jean-Marc Valin */ -/* - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER - OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -/* opus_types.h based on ogg_types.h from libogg */ - -/** - @file opus_types.h - @brief Opus reference implementation types -*/ -#ifndef OPUS_TYPES_H -#define OPUS_TYPES_H - -#define opus_int int /* used for counters etc; at least 16 bits */ -#define opus_int64 long long -#define opus_int8 signed char - -#define opus_uint unsigned int /* used for counters etc; at least 16 bits */ -#define opus_uint64 unsigned long long -#define opus_uint8 unsigned char - -/* Use the real stdint.h if it's there (taken from Paul Hsieh's pstdint.h) */ -#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined(__GNUC__) && (defined(_STDINT_H) || defined(_STDINT_H_)) || defined (HAVE_STDINT_H)) -#include -# undef opus_int64 -# undef opus_int8 -# undef opus_uint64 -# undef opus_uint8 - typedef int8_t opus_int8; - typedef uint8_t opus_uint8; - typedef int16_t opus_int16; - typedef uint16_t opus_uint16; - typedef int32_t opus_int32; - typedef uint32_t opus_uint32; - typedef int64_t opus_int64; - typedef uint64_t opus_uint64; -#elif defined(_WIN32) - -# if defined(__CYGWIN__) -# include <_G_config.h> - typedef _G_int32_t opus_int32; - typedef _G_uint32_t opus_uint32; - typedef _G_int16 opus_int16; - typedef _G_uint16 opus_uint16; -# elif defined(__MINGW32__) - typedef short opus_int16; - typedef unsigned short opus_uint16; - typedef int opus_int32; - typedef unsigned int opus_uint32; -# elif defined(__MWERKS__) - typedef int opus_int32; - typedef unsigned int opus_uint32; - typedef short opus_int16; - typedef unsigned short opus_uint16; -# else - /* MSVC/Borland */ - typedef __int32 opus_int32; - typedef unsigned __int32 opus_uint32; - typedef __int16 opus_int16; - typedef unsigned __int16 opus_uint16; -# endif - -#elif defined(__MACOS__) - -# include - typedef SInt16 opus_int16; - typedef UInt16 opus_uint16; - typedef SInt32 opus_int32; - typedef UInt32 opus_uint32; - -#elif (defined(__APPLE__) && defined(__MACH__)) /* MacOS X Framework build */ - -# include - typedef int16_t opus_int16; - typedef u_int16_t opus_uint16; - typedef int32_t opus_int32; - typedef u_int32_t opus_uint32; - -#elif defined(__BEOS__) - - /* Be */ -# include - typedef int16 opus_int16; - typedef u_int16 opus_uint16; - typedef int32_t opus_int32; - typedef u_int32_t opus_uint32; - -#elif defined (__EMX__) - - /* OS/2 GCC */ - typedef short opus_int16; - typedef unsigned short opus_uint16; - typedef int opus_int32; - typedef unsigned int opus_uint32; - -#elif defined (DJGPP) - - /* DJGPP */ - typedef short opus_int16; - typedef unsigned short opus_uint16; - typedef int opus_int32; - typedef unsigned int opus_uint32; - -#elif defined(R5900) - - /* PS2 EE */ - typedef int opus_int32; - typedef unsigned opus_uint32; - typedef short opus_int16; - typedef unsigned short opus_uint16; - -#elif defined(__SYMBIAN32__) - - /* Symbian GCC */ - typedef signed short opus_int16; - typedef unsigned short opus_uint16; - typedef signed int opus_int32; - typedef unsigned int opus_uint32; - -#elif defined(CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) - - typedef short opus_int16; - typedef unsigned short opus_uint16; - typedef long opus_int32; - typedef unsigned long opus_uint32; - -#elif defined(CONFIG_TI_C6X) - - typedef short opus_int16; - typedef unsigned short opus_uint16; - typedef int opus_int32; - typedef unsigned int opus_uint32; - -#else - - /* Give up, take a reasonable guess */ - typedef short opus_int16; - typedef unsigned short opus_uint16; - typedef int opus_int32; - typedef unsigned int opus_uint32; - -#endif - -#endif /* OPUS_TYPES_H */