mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
e960bbb080
84 changed files with 3324 additions and 4079 deletions
|
|
@ -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<AutomaticBusinessMessageSetupScreenInitialData, NoError>
|
||||
func makeQuickReplySetupScreen(context: AccountContext, initialData: QuickReplySetupScreenInitialData) -> ViewController
|
||||
func makeQuickReplySetupScreenInitialData(context: AccountContext) -> Signal<QuickReplySetupScreenInitialData, NoError>
|
||||
func makeBusinessIntroSetupScreen(context: AccountContext) -> ViewController
|
||||
func makeCollectibleItemInfoScreen(context: AccountContext, initialData: CollectibleItemInfoScreenInitialData) -> ViewController
|
||||
func makeCollectibleItemInfoScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, subject: CollectibleItemInfoScreenSubject) -> Signal<CollectibleItemInfoScreenInitialData?, NoError>
|
||||
func navigateToChatController(_ params: NavigateToChatControllerParams)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Empty>, 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<Empty>, 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,11 +13,13 @@ public final class VStack<ChildEnvironment: Equatable>: CombinedComponent {
|
|||
private let items: [AnyComponentWithIdentity<ChildEnvironment>]
|
||||
private let alignment: VStackAlignment
|
||||
private let spacing: CGFloat
|
||||
private let fillWidth: Bool
|
||||
|
||||
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], alignment: VStackAlignment = .center, spacing: CGFloat) {
|
||||
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], 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<ChildEnvironment>, rhs: VStack<ChildEnvironment>) -> Bool {
|
||||
|
|
@ -30,6 +32,9 @@ public final class VStack<ChildEnvironment: Equatable>: 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<ChildEnvironment: Equatable>: 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)
|
||||
|
|
|
|||
|
|
@ -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<Empty>?
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<Empty>()
|
||||
|
||||
func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, 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<Empty>, 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Int>()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Int>()
|
||||
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<Void, NoError> = 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<Void, NoError> 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<Void, NoError> = peersPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { peers -> Signal<Void, NoError> 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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Api.Bool>) {
|
||||
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<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<UnauthorizedAccount, NoError> in
|
||||
return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false)
|
||||
|> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<UnauthorizedAccount, NoError> in
|
||||
return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false, appConfiguration: appConfiguration)
|
||||
|> map { network in
|
||||
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
|
||||
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
|
||||
|
|
@ -248,7 +248,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
|||
if let accountState = accountState {
|
||||
switch accountState {
|
||||
case let unauthorizedState as UnauthorizedAccountState:
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
|> map { network -> AccountResult in
|
||||
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
|||
return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
|
||||
}
|
||||
|> mapToSignal { phoneNumber in
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
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<TelegramAccountManagerT
|
|||
}
|
||||
}
|
||||
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
|> 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<AccountBackupData?, No
|
|||
}
|
||||
}
|
||||
|
||||
public enum NetworkSpeedLimitedEvent {
|
||||
case upload
|
||||
case download
|
||||
}
|
||||
|
||||
public class Account {
|
||||
static let sharedQueue = Queue(name: "Account-Shared")
|
||||
|
||||
|
|
@ -1519,7 +1524,8 @@ public func standaloneStateManager(
|
|||
proxySettings: proxySettings,
|
||||
networkSettings: networkSettings,
|
||||
phoneNumber: phoneNumber,
|
||||
useRequestTimeoutTimers: false
|
||||
useRequestTimeoutTimers: false,
|
||||
appConfiguration: .defaultValue
|
||||
)
|
||||
|> map { network -> AccountStateManager? in
|
||||
Logger.shared.log("StandaloneStateManager", "received network")
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId)
|
||||
}
|
||||
|
||||
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> {
|
||||
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<Void, UploadPartError> {
|
||||
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
|
||||
if asBigPart {
|
||||
let totalParts: Int32
|
||||
|
|
@ -117,7 +117,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
|
||||
}
|
||||
|
||||
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, 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<Void, UploadPartError> {
|
||||
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<Void, UploadPartError> {
|
||||
return Signal<Void, MTRpcError> { 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<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> {
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<T, MTRpcError> {
|
||||
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<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> {
|
||||
func requestWithAdditionalData<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,14 +104,14 @@ private struct DownloadWrapper {
|
|||
self.useMainConnection = useMainConnection
|
||||
}
|
||||
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), MTRpcError> {
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<T, MTRpcError> {
|
||||
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<T, MTRpcError> {
|
||||
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<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ public struct NetworkInitializationArguments {
|
|||
private let cloudDataContext = Atomic<CloudDataContext?>(value: nil)
|
||||
#endif
|
||||
|
||||
func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool) -> Signal<Network, NoError> {
|
||||
func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool, appConfiguration: AppConfiguration) -> Signal<Network, NoError> {
|
||||
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<T> {
|
|||
case progress(Float, Int32)
|
||||
}
|
||||
|
||||
private final class NetworkSpeedLimitedEventState {
|
||||
var notifyInterval: Double = 60.0 * 60.0
|
||||
var lastNotifyTimestamp: Double = 0.0
|
||||
|
||||
func add(event: NetworkSpeedLimitedEvent) -> Bool {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
if self.lastNotifyTimestamp + self.notifyInterval < timestamp {
|
||||
self.lastNotifyTimestamp = timestamp
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
public let encryptionProvider: EncryptionProvider
|
||||
|
||||
|
|
@ -766,6 +787,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
|||
return self._connectionStatus.get() |> distinctUntilChanged
|
||||
}
|
||||
|
||||
public var networkSpeedLimitedEvents: Signal<NetworkSpeedLimitedEvent, NoError> {
|
||||
return self.networkSpeedLimitedEventPipe.signal()
|
||||
}
|
||||
private let networkSpeedLimitedEventPipe = ValuePipe<NetworkSpeedLimitedEvent>()
|
||||
private let networkSpeedLimitedEventState = Atomic<NetworkSpeedLimitedEventState>(value: NetworkSpeedLimitedEventState())
|
||||
|
||||
public func dropConnectionStatus() {
|
||||
_connectionStatus.set(.single(.waitingForNetwork))
|
||||
}
|
||||
|
|
@ -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<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal<NetworkRequestResult<T>, MTRpcError> {
|
||||
public func requestWithAdditionalInfo<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<NetworkRequestResult<T>, MTRpcError> {
|
||||
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<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> {
|
||||
|
||||
public func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<T, MTRpcError> {
|
||||
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<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -551,7 +551,7 @@ public final class CachedChannelData: CachedPeerData {
|
|||
var peerIds = Set<PeerId>()
|
||||
|
||||
if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") {
|
||||
self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil)
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -358,3 +358,43 @@ func _internal_updateBotBiometricsState(account: Account, peerId: EnginePeer.Id,
|
|||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
func _internal_toggleChatManagingBotIsPaused(account: Account, chatId: EnginePeer.Id) -> Signal<Never, NoError> {
|
||||
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<Never, NoError> {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -412,7 +412,7 @@ func _internal_updateSelectiveAccountPrivacySettings(account: Account, type: Upd
|
|||
return account.postbox.transaction { transaction -> Signal<Void, NoError> 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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": [],
|
||||
|
|
|
|||
37
submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD
Normal file
37
submodules/TelegramUI/Components/Chat/ChatEmptyNode/BUILD
Normal file
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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<TelegramMediaFile?, NoError>
|
||||
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<Empty>
|
||||
|
||||
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 {
|
||||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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<Empty>?
|
||||
|
||||
func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let size = CGSize(width: 24.0, height: 24.0)
|
||||
|
||||
if let fileId = component.fileId {
|
||||
let icon: ComponentView<Empty>
|
||||
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<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Empty>()
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ public final class ListSectionComponent: Component {
|
|||
public let header: AnyComponent<Empty>?
|
||||
public let footer: AnyComponent<Empty>?
|
||||
public let items: [AnyComponentWithIdentity<Empty>]
|
||||
public let itemUpdateOrder: [AnyHashable]?
|
||||
public let displaySeparators: Bool
|
||||
public let extendsItemHighlightToSection: Bool
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ public final class ListSectionComponent: Component {
|
|||
header: AnyComponent<Empty>?,
|
||||
footer: AnyComponent<Empty>?,
|
||||
items: [AnyComponentWithIdentity<Empty>],
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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<Empty>()
|
||||
private let introContent = ComponentView<Empty>()
|
||||
private let introSection = ComponentView<Empty>()
|
||||
private let deleteSection = ComponentView<Empty>()
|
||||
|
||||
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<Empty>?
|
||||
|
||||
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<EnvironmentType>, 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<Empty>] = []
|
||||
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<Empty>
|
||||
var animateIn = false
|
||||
if let current = self.stickerSelectionControl {
|
||||
stickerSelectionControl = current
|
||||
} else {
|
||||
animateIn = true
|
||||
stickerSelectionControl = ComponentView()
|
||||
self.stickerSelectionControl = stickerSelectionControl
|
||||
}
|
||||
var selectedItems = Set<MediaId>()
|
||||
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<EnvironmentType>, 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Empty>, 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<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<Empty>?
|
||||
|
||||
func update(component: EmojiActionIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let size = CGSize(width: 24.0, height: 24.0)
|
||||
|
||||
if let fileId = component.fileId {
|
||||
let icon: ComponentView<Empty>
|
||||
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<Empty>, 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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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, NoError>) = (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 {
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "more.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
79
submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf
vendored
Normal file
79
submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf
vendored
Normal file
|
|
@ -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
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Icon.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Icon.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/PremiumIconBackground.imageset/Icon.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
BIN
submodules/TelegramUI/Resources/Animations/anim_speed_low.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/anim_speed_low.tgs
Normal file
Binary file not shown.
|
|
@ -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<Bool>?) -> 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<ChatManagingBot?, NoError>
|
||||
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<ChatManagingBot?, NoError> in
|
||||
guard let result else {
|
||||
return .single(nil)
|
||||
}
|
||||
return context.engine.data.subscribe(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: result.id)
|
||||
)
|
||||
|> map { botPeer -> ChatManagingBot? in
|
||||
guard let botPeer else {
|
||||
return nil
|
||||
}
|
||||
|
||||
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<PeerId, Peer>()
|
||||
|
|
@ -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<PeerId, Peer>()
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Empty>()
|
||||
private let text = ComponentView<Empty>()
|
||||
private var avatarNode: AvatarNode?
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
private let settingsButton = ComponentView<Empty>()
|
||||
|
||||
private var component: ChatManagingBotTitlePanelComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ChatManagingBotTitlePanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let topInset: CGFloat = 6.0
|
||||
let bottomInset: CGFloat = 6.0
|
||||
let avatarDiameter: CGFloat = 36.0
|
||||
let avatarTextSpacing: CGFloat = 10.0
|
||||
let titleTextSpacing: CGFloat = 1.0
|
||||
let leftInset: CGFloat = component.insets.left + 12.0
|
||||
let rightInset: CGFloat = component.insets.right + 10.0
|
||||
let actionAndSettingsButtonsSpacing: CGFloat = 8.0
|
||||
|
||||
//TODO:localize
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.isPaused ? "START" : "STOP", font: Font.semibold(15.0), textColor: component.theme.list.itemCheckColors.foregroundColor))
|
||||
)),
|
||||
background: AnyComponent(RoundedRectangle(
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
cornerRadius: nil
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
contentInsets: UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.toggleIsPaused()
|
||||
},
|
||||
animateAlpha: true,
|
||||
animateScale: false,
|
||||
animateContents: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 150.0, height: 100.0)
|
||||
)
|
||||
|
||||
let settingsButtonSize = self.settingsButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Context Menu/Customize",
|
||||
tintColor: component.theme.rootController.navigationBar.controlColor
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
minSize: CGSize(width: 1.0, height: 40.0),
|
||||
contentInsets: UIEdgeInsets(top: 0.0, left: 2.0, bottom: 0.0, right: 2.0),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard let settingsButtonView = self.settingsButton.view else {
|
||||
return
|
||||
}
|
||||
component.openSettings(settingsButtonView)
|
||||
},
|
||||
animateAlpha: true,
|
||||
animateScale: false,
|
||||
animateContents: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 150.0, height: 100.0)
|
||||
)
|
||||
|
||||
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<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let context: AccountContext
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let content = ComponentView<Empty>()
|
||||
|
||||
private var chatLocation: ChatLocation?
|
||||
private var theme: PresentationTheme?
|
||||
private var managingBot: ChatManagingBot?
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
}
|
||||
|
||||
private func toggleIsPaused() {
|
||||
guard let chatPeerId = self.chatLocation?.peerId else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = self.context.engine.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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import MediaEditor
|
|||
import PeerInfoScreen
|
||||
import PeerInfoStoryGridScreen
|
||||
import ShareWithPeersScreen
|
||||
import ChatEmptyNode
|
||||
|
||||
private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceholderNode {
|
||||
private var presentationData: PresentationData
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
third-party/webrtc/BUILD
vendored
21
third-party/webrtc/BUILD
vendored
|
|
@ -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",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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 /**<Auto/default setting @hideinitializer*/
|
||||
#define OPUS_BITRATE_MAX -1 /**<Maximum bitrate @hideinitializer*/
|
||||
|
||||
/** Best for most VoIP/videoconference applications where listening quality and intelligibility matter most
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_VOIP 2048
|
||||
/** Best for broadcast/high-fidelity application where the decoded audio should be as close as possible to the input
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_AUDIO 2049
|
||||
/** Only use when lowest-achievable latency is what matters most. Voice-optimized modes cannot be used.
|
||||
* @hideinitializer */
|
||||
#define OPUS_APPLICATION_RESTRICTED_LOWDELAY 2051
|
||||
|
||||
#define OPUS_SIGNAL_VOICE 3001 /**< Signal being encoded is voice */
|
||||
#define OPUS_SIGNAL_MUSIC 3002 /**< Signal being encoded is music */
|
||||
#define OPUS_BANDWIDTH_NARROWBAND 1101 /**< 4 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_MEDIUMBAND 1102 /**< 6 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_WIDEBAND 1103 /**< 8 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_SUPERWIDEBAND 1104 /**<12 kHz bandpass @hideinitializer*/
|
||||
#define OPUS_BANDWIDTH_FULLBAND 1105 /**<20 kHz bandpass @hideinitializer*/
|
||||
|
||||
#define OPUS_FRAMESIZE_ARG 5000 /**< Select frame size from the argument (default) */
|
||||
#define OPUS_FRAMESIZE_2_5_MS 5001 /**< Use 2.5 ms frames */
|
||||
#define OPUS_FRAMESIZE_5_MS 5002 /**< Use 5 ms frames */
|
||||
#define OPUS_FRAMESIZE_10_MS 5003 /**< Use 10 ms frames */
|
||||
#define OPUS_FRAMESIZE_20_MS 5004 /**< Use 20 ms frames */
|
||||
#define OPUS_FRAMESIZE_40_MS 5005 /**< Use 40 ms frames */
|
||||
#define OPUS_FRAMESIZE_60_MS 5006 /**< Use 60 ms frames */
|
||||
#define OPUS_FRAMESIZE_80_MS 5007 /**< Use 80 ms frames */
|
||||
#define OPUS_FRAMESIZE_100_MS 5008 /**< Use 100 ms frames */
|
||||
#define OPUS_FRAMESIZE_120_MS 5009 /**< Use 120 ms frames */
|
||||
|
||||
/**@}*/
|
||||
|
||||
|
||||
/** @defgroup opus_encoderctls Encoder related CTLs
|
||||
*
|
||||
* These are convenience macros for use with the \c opus_encode_ctl
|
||||
* interface. They are used to generate the appropriate series of
|
||||
* arguments for that call, passing the correct type, size and so
|
||||
* on as expected for each particular request.
|
||||
*
|
||||
* Some usage examples:
|
||||
*
|
||||
* @code
|
||||
* int ret;
|
||||
* ret = opus_encoder_ctl(enc_ctx, OPUS_SET_BANDWIDTH(OPUS_AUTO));
|
||||
* if (ret != OPUS_OK) return ret;
|
||||
*
|
||||
* opus_int32 rate;
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_GET_BANDWIDTH(&rate));
|
||||
*
|
||||
* opus_encoder_ctl(enc_ctx, OPUS_RESET_STATE);
|
||||
* @endcode
|
||||
*
|
||||
* @see opus_genericctls, opus_encoder
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Configures the encoder's computational complexity.
|
||||
* The supported range is 0-10 inclusive with 10 representing the highest complexity.
|
||||
* @see OPUS_GET_COMPLEXITY
|
||||
* @param[in] x <tt>opus_int32</tt>: 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 <tt>opus_int32 *</tt>: 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 <tt>opus_int32</tt>: 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 <tt>opus_int32 *</tt>: 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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Hard CBR. For LPC/hybrid modes at very low bit-rate, this can
|
||||
* cause noticeable quality degradation.</dd>
|
||||
* <dt>1</dt><dd>VBR (default). The exact type of VBR is controlled by
|
||||
* #OPUS_SET_VBR_CONSTRAINT.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Hard CBR.</dd>
|
||||
* <dt>1</dt><dd>VBR (default). The exact type of VBR may be retrieved via
|
||||
* #OPUS_GET_VBR_CONSTRAINT.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Unconstrained VBR.</dd>
|
||||
* <dt>1</dt><dd>Constrained VBR (default). This creates a maximum of one
|
||||
* frame of buffering delay assuming a transport with a
|
||||
* serialization speed of the nominal bitrate.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Unconstrained VBR.</dd>
|
||||
* <dt>1</dt><dd>Constrained VBR (default).</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
|
||||
* <dt>1</dt> <dd>Forced mono</dd>
|
||||
* <dt>2</dt> <dd>Forced stereo</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt><dd>Not forced (default)</dd>
|
||||
* <dt>1</dt> <dd>Forced mono</dd>
|
||||
* <dt>2</dt> <dd>Forced stereo</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband (default)</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
|
||||
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_SIGNAL_VOICE</dt><dd>Bias thresholds towards choosing LPC or Hybrid modes.</dd>
|
||||
* <dt>#OPUS_SIGNAL_MUSIC</dt><dd>Bias thresholds towards choosing MDCT modes.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: 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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Disable inband FEC (default).</dd>
|
||||
* <dt>1</dt><dd>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.</dd>
|
||||
* <dt>2</dt><dd>Inband FEC enabled, but does not necessarily switch to SILK if we have music.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Inband FEC disabled (default).</dd>
|
||||
* <dt>1</dt><dd>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.</dd>
|
||||
* <dt>2</dt><dd>Inband FEC enabled, but does not necessarily switch to SILK if we have music.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: 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 <tt>opus_int32 *</tt>: 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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Disable DTX (default).</dd>
|
||||
* <dt>1</dt><dd>Enabled DTX.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>DTX disabled (default).</dd>
|
||||
* <dt>1</dt><dd>DTX enabled.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: 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 <tt>opus_int32 *</tt>: 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 <b>really</b>
|
||||
* know what you are doing.
|
||||
* @see OPUS_GET_EXPERT_FRAME_DURATION
|
||||
* @param[in] x <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
|
||||
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_80_MS</dt><dd>Use 80 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_100_MS</dt><dd>Use 100 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_120_MS</dt><dd>Use 120 ms frames.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>OPUS_FRAMESIZE_ARG</dt><dd>Select frame size from the argument (default).</dd>
|
||||
* <dt>OPUS_FRAMESIZE_2_5_MS</dt><dd>Use 2.5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_5_MS</dt><dd>Use 5 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_10_MS</dt><dd>Use 10 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_20_MS</dt><dd>Use 20 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_40_MS</dt><dd>Use 40 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_60_MS</dt><dd>Use 60 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_80_MS</dt><dd>Use 80 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_100_MS</dt><dd>Use 100 ms frames.</dd>
|
||||
* <dt>OPUS_FRAMESIZE_120_MS</dt><dd>Use 120 ms frames.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Enable prediction (default).</dd>
|
||||
* <dt>1</dt><dd>Disable prediction.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Prediction enabled (default).</dd>
|
||||
* <dt>1</dt><dd>Prediction disabled.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_uint32 *</tt>: 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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>#OPUS_AUTO</dt> <dd>(default)</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_NARROWBAND</dt> <dd>4 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_MEDIUMBAND</dt> <dd>6 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_WIDEBAND</dt> <dd>8 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_SUPERWIDEBAND</dt><dd>12 kHz passband</dd>
|
||||
* <dt>#OPUS_BANDWIDTH_FULLBAND</dt> <dd>20 kHz passband</dd>
|
||||
* </dl>
|
||||
* @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 <code>Fs</code> value passed to opus_encoder_init()
|
||||
* or opus_decoder_init().
|
||||
* @param[out] x <tt>opus_int32 *</tt>: 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 <tt>opus_int32</tt>: Allowed values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Enable phase inversion (default).</dd>
|
||||
* <dt>1</dt><dd>Disable phase inversion.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>Stereo phase inversion enabled (default).</dd>
|
||||
* <dt>1</dt><dd>Stereo phase inversion disabled.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32 *</tt>: Returns one of the following values:
|
||||
* <dl>
|
||||
* <dt>0</dt><dd>The encoder is not in DTX.</dd>
|
||||
* <dt>1</dt><dd>The encoder is in DTX.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>opus_int32</tt>: 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 <tt>opus_int32 *</tt>: 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 <tt>opus_int32 *</tt>: 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 <tt>opus_int32 *</tt>: 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 <tt>int</tt>: 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 */
|
||||
|
|
@ -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 <tt>opus_int32</tt>: The index of the stream whose encoder you
|
||||
* wish to retrieve.
|
||||
* This must be non-negative and less than
|
||||
* the <code>streams</code> parameter used
|
||||
* to initialize the encoder.
|
||||
* @param[out] y <tt>OpusEncoder**</tt>: 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 <tt>opus_int32</tt>: The index of the stream whose decoder you
|
||||
* wish to retrieve.
|
||||
* This must be non-negative and less than
|
||||
* the <code>streams</code> parameter used
|
||||
* to initialize the decoder.
|
||||
* @param[out] y <tt>OpusDecoder**</tt>: 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
|
||||
* <a href="https://tools.ietf.org/html/rfc7845">RFC 7845</a>
|
||||
* and is based on the self-delimited Opus framing described in Appendix B of
|
||||
* <a href="https://tools.ietf.org/html/rfc6716">RFC 6716</a>.
|
||||
* Normal Opus packets are just a degenerate case of multistream Opus packets,
|
||||
* and can be encoded or decoded with the multistream API by setting
|
||||
* <code>streams</code> to <code>1</code> 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 <code>mapping</code> table defines which decoded channel <code>i</code>
|
||||
* should be used for each input/output (I/O) channel <code>j</code>. This table is
|
||||
* typically provided as an unsigned char array.
|
||||
* Let <code>i = mapping[j]</code> be the index for I/O channel <code>j</code>.
|
||||
* If <code>i < 2*coupled_streams</code>, then I/O channel <code>j</code> is
|
||||
* encoded as the left channel of stream <code>(i/2)</code> if <code>i</code>
|
||||
* is even, or as the right channel of stream <code>(i/2)</code> if
|
||||
* <code>i</code> is odd. Otherwise, I/O channel <code>j</code> is encoded as
|
||||
* mono in stream <code>(i - coupled_streams)</code>, 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 <code>i</code> must either
|
||||
* be the special value 255 or be less than <code>streams + coupled_streams</code>.
|
||||
*
|
||||
* The output channels specified by the encoder
|
||||
* should use the
|
||||
* <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9">Vorbis
|
||||
* channel ordering</a>. 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 <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) 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 <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels in the input signal.
|
||||
* This must be at most 255.
|
||||
* It may be greater than the number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than the number of channels.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than the number of input channels.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: 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 <tt>int</tt>: The target encoder application.
|
||||
* This must be one of the following:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @param[out] error <tt>int *</tt>: 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 <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels in the input signal.
|
||||
* This must be at most 255.
|
||||
* It may be greater than the number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than the number of channels.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than the number of input channels.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: 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 <tt>int</tt>: The target encoder application.
|
||||
* This must be one of the following:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>OpusMSEncoder*</tt>: Multistream encoder state.
|
||||
* @param[in] pcm <tt>const opus_int16*</tt>: The input signal as interleaved
|
||||
* samples.
|
||||
* This must contain
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: 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 <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: 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 <tt>OpusMSEncoder*</tt>: Multistream encoder state.
|
||||
* @param[in] pcm <tt>const float*</tt>: 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
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: 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 <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: 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 <code>OpusMSEncoder</code> allocated by
|
||||
* opus_multistream_encoder_create().
|
||||
* @param st <tt>OpusMSEncoder*</tt>: 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 <tt>OpusMSEncoder*</tt>: 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 <code>OpusMSDecoder</code> structure.
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) 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 <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels to output.
|
||||
* This must be at most 255.
|
||||
* It may be different from the number of coded
|
||||
* channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: Mapping from
|
||||
* coded channels to output channels, as described in
|
||||
* @ref opus_multistream.
|
||||
* @param[out] error <tt>int *</tt>: 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 <tt>OpusMSEncoder*</tt>: Multistream encoder state to initialize.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels to output.
|
||||
* This must be at most 255.
|
||||
* It may be different from the number of coded
|
||||
* channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @param[in] mapping <code>const unsigned char[channels]</code>: 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 <tt>OpusMSDecoder*</tt>: Multistream decoder state.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
|
||||
* Use a <code>NULL</code>
|
||||
* pointer to indicate packet
|
||||
* loss.
|
||||
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
|
||||
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
|
||||
* samples.
|
||||
* This must contain room for
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: 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
|
||||
* <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param decode_fec <tt>int</tt>: 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 <tt>OpusMSDecoder*</tt>: Multistream decoder state.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
|
||||
* Use a <code>NULL</code>
|
||||
* pointer to indicate packet
|
||||
* loss.
|
||||
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
|
||||
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
|
||||
* samples.
|
||||
* This must contain room for
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: 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
|
||||
* <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param decode_fec <tt>int</tt>: 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 <tt>OpusMSDecoder*</tt>: 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 <code>OpusMSDecoder</code> allocated by
|
||||
* opus_multistream_decoder_create().
|
||||
* @param st <tt>OpusMSDecoder</tt>: Multistream decoder state to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_multistream_decoder_destroy(OpusMSDecoder *st);
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_MULTISTREAM_H */
|
||||
|
|
@ -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 <tt>opus_int32 *</tt>: 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 <tt>opus_int32 *</tt>: 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 <tt>unsigned char *</tt>: Returns the demixing matrix to the
|
||||
* supplied pointer location.
|
||||
* @param y <tt>opus_int32</tt>: 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 <tt>int</tt>: The total number of input channels to encode.
|
||||
* This must be no more than 255.
|
||||
* @param mapping_family <tt>int</tt>: 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 <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels in the input signal.
|
||||
* This must be at most 255.
|
||||
* It may be greater than the number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param mapping_family <tt>int</tt>: The mapping family to use for selecting
|
||||
* the appropriate projection.
|
||||
* @param[out] streams <tt>int *</tt>: The total number of streams that will
|
||||
* be encoded from the input.
|
||||
* @param[out] coupled_streams <tt>int *</tt>: Number of coupled (2 channel)
|
||||
* streams that will be encoded from the input.
|
||||
* @param application <tt>int</tt>: The target encoder application.
|
||||
* This must be one of the following:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @param[out] error <tt>int *</tt>: 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 <tt>OpusProjectionEncoder*</tt>: Projection encoder state to initialize.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate of the input signal (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels in the input signal.
|
||||
* This must be at most 255.
|
||||
* It may be greater than the number of
|
||||
* coded channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams to encode from the
|
||||
* input.
|
||||
* This must be no more than the number of channels.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than the number of input channels.
|
||||
* @param application <tt>int</tt>: The target encoder application.
|
||||
* This must be one of the following:
|
||||
* <dl>
|
||||
* <dt>#OPUS_APPLICATION_VOIP</dt>
|
||||
* <dd>Process signal for improved speech intelligibility.</dd>
|
||||
* <dt>#OPUS_APPLICATION_AUDIO</dt>
|
||||
* <dd>Favor faithfulness to the original input.</dd>
|
||||
* <dt>#OPUS_APPLICATION_RESTRICTED_LOWDELAY</dt>
|
||||
* <dd>Configure the minimum possible coding delay by disabling certain modes
|
||||
* of operation.</dd>
|
||||
* </dl>
|
||||
* @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 <tt>OpusProjectionEncoder*</tt>: Projection encoder state.
|
||||
* @param[in] pcm <tt>const opus_int16*</tt>: The input signal as interleaved
|
||||
* samples.
|
||||
* This must contain
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: 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 <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: 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 <tt>OpusProjectionEncoder*</tt>: Projection encoder state.
|
||||
* @param[in] pcm <tt>const float*</tt>: 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
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: 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 <tt>unsigned char*</tt>: Output payload.
|
||||
* This must contain storage for at
|
||||
* least \a max_data_bytes.
|
||||
* @param [in] max_data_bytes <tt>opus_int32</tt>: 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 <code>OpusProjectionEncoder</code> allocated by
|
||||
* opus_projection_ambisonics_encoder_create().
|
||||
* @param st <tt>OpusProjectionEncoder*</tt>: 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 <tt>OpusProjectionEncoder*</tt>: 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 <code>OpusProjectionDecoder</code> structure.
|
||||
* @param channels <tt>int</tt>: The total number of output channels.
|
||||
* This must be no more than 255.
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) 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 <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels to output.
|
||||
* This must be at most 255.
|
||||
* It may be different from the number of coded
|
||||
* channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @param[in] demixing_matrix <tt>const unsigned char[demixing_matrix_size]</tt>: 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 <tt>opus_int32</tt>: The size in bytes of the
|
||||
* demixing matrix, as
|
||||
* described in @ref
|
||||
* opus_projection_ctls.
|
||||
* @param[out] error <tt>int *</tt>: 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 <tt>OpusProjectionDecoder*</tt>: Projection encoder state to initialize.
|
||||
* @param Fs <tt>opus_int32</tt>: Sampling rate to decode at (in Hz).
|
||||
* This must be one of 8000, 12000, 16000,
|
||||
* 24000, or 48000.
|
||||
* @param channels <tt>int</tt>: Number of channels to output.
|
||||
* This must be at most 255.
|
||||
* It may be different from the number of coded
|
||||
* channels (<code>streams +
|
||||
* coupled_streams</code>).
|
||||
* @param streams <tt>int</tt>: The total number of streams coded in the
|
||||
* input.
|
||||
* This must be no more than 255.
|
||||
* @param coupled_streams <tt>int</tt>: 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 (<code>streams +
|
||||
* coupled_streams</code>) must be no
|
||||
* more than 255.
|
||||
* @param[in] demixing_matrix <tt>const unsigned char[demixing_matrix_size]</tt>: 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 <tt>opus_int32</tt>: 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 <tt>OpusProjectionDecoder*</tt>: Projection decoder state.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
|
||||
* Use a <code>NULL</code>
|
||||
* pointer to indicate packet
|
||||
* loss.
|
||||
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
|
||||
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
|
||||
* samples.
|
||||
* This must contain room for
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: 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
|
||||
* <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param decode_fec <tt>int</tt>: 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 <tt>OpusProjectionDecoder*</tt>: Projection decoder state.
|
||||
* @param[in] data <tt>const unsigned char*</tt>: Input payload.
|
||||
* Use a <code>NULL</code>
|
||||
* pointer to indicate packet
|
||||
* loss.
|
||||
* @param len <tt>opus_int32</tt>: Number of bytes in payload.
|
||||
* @param[out] pcm <tt>opus_int16*</tt>: Output signal, with interleaved
|
||||
* samples.
|
||||
* This must contain room for
|
||||
* <code>frame_size*channels</code>
|
||||
* samples.
|
||||
* @param frame_size <tt>int</tt>: 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
|
||||
* <b>must</b> be a multiple of 2.5 ms.
|
||||
* @param decode_fec <tt>int</tt>: 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 <tt>OpusProjectionDecoder*</tt>: 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 <code>OpusProjectionDecoder</code> allocated by
|
||||
* opus_projection_decoder_create().
|
||||
* @param st <tt>OpusProjectionDecoder</tt>: Projection decoder state to be freed.
|
||||
*/
|
||||
OPUS_EXPORT void opus_projection_decoder_destroy(OpusProjectionDecoder *st);
|
||||
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* OPUS_PROJECTION_H */
|
||||
|
|
@ -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 <stdint.h>
|
||||
# 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 <sys/types.h>
|
||||
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 <sys/types.h>
|
||||
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 <inttypes.h>
|
||||
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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue