mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Add back changes
This commit is contained in:
parent
4e9b22ffce
commit
2a2d468fd9
@ -231,17 +231,52 @@ public enum ChatRecordedMediaPreview: Equatable {
|
|||||||
case video(Video)
|
case video(Video)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class ChatManagingBot: Equatable {
|
||||||
|
public let bot: EnginePeer
|
||||||
|
public let isPaused: Bool
|
||||||
|
public let canReply: Bool
|
||||||
|
public let settingsUrl: String?
|
||||||
|
|
||||||
|
public init(bot: EnginePeer, isPaused: Bool, canReply: Bool, settingsUrl: String?) {
|
||||||
|
self.bot = bot
|
||||||
|
self.isPaused = isPaused
|
||||||
|
self.canReply = canReply
|
||||||
|
self.settingsUrl = settingsUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: ChatManagingBot, rhs: ChatManagingBot) -> Bool {
|
||||||
|
if lhs === rhs {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if lhs.bot != rhs.bot {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isPaused != rhs.isPaused {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.canReply != rhs.canReply {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.settingsUrl != rhs.settingsUrl {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct ChatContactStatus: Equatable {
|
public struct ChatContactStatus: Equatable {
|
||||||
public var canAddContact: Bool
|
public var canAddContact: Bool
|
||||||
public var canReportIrrelevantLocation: Bool
|
public var canReportIrrelevantLocation: Bool
|
||||||
public var peerStatusSettings: PeerStatusSettings?
|
public var peerStatusSettings: PeerStatusSettings?
|
||||||
public var invitedBy: Peer?
|
public var invitedBy: Peer?
|
||||||
|
public var managingBot: ChatManagingBot?
|
||||||
|
|
||||||
public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?) {
|
public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?, managingBot: ChatManagingBot?) {
|
||||||
self.canAddContact = canAddContact
|
self.canAddContact = canAddContact
|
||||||
self.canReportIrrelevantLocation = canReportIrrelevantLocation
|
self.canReportIrrelevantLocation = canReportIrrelevantLocation
|
||||||
self.peerStatusSettings = peerStatusSettings
|
self.peerStatusSettings = peerStatusSettings
|
||||||
self.invitedBy = invitedBy
|
self.invitedBy = invitedBy
|
||||||
|
self.managingBot = managingBot
|
||||||
}
|
}
|
||||||
|
|
||||||
public var isEmpty: Bool {
|
public var isEmpty: Bool {
|
||||||
@ -270,6 +305,9 @@ public struct ChatContactStatus: Equatable {
|
|||||||
if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) {
|
if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.managingBot != rhs.managingBot {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,23 @@ public struct LocalAuth {
|
|||||||
case error(Error)
|
case error(Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
public final class PrivateKey {
|
||||||
|
public let publicKeyRepresentation: Data
|
||||||
|
|
||||||
|
fileprivate init() {
|
||||||
|
self.publicKeyRepresentation = Data(count: 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func encrypt(data: Data) -> Data? {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
public func decrypt(data: Data) -> DecryptionResult {
|
||||||
|
return .result(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
public final class PrivateKey {
|
public final class PrivateKey {
|
||||||
private let privateKey: SecKey
|
private let privateKey: SecKey
|
||||||
private let publicKey: SecKey
|
private let publicKey: SecKey
|
||||||
@ -64,6 +81,7 @@ public struct LocalAuth {
|
|||||||
return .result(result)
|
return .result(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public static var biometricAuthentication: LocalAuthBiometricAuthentication? {
|
public static var biometricAuthentication: LocalAuthBiometricAuthentication? {
|
||||||
let context = LAContext()
|
let context = LAContext()
|
||||||
@ -157,7 +175,18 @@ public struct LocalAuth {
|
|||||||
return seedId;
|
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 {
|
guard let bundleSeedId = self.bundleSeedId() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -196,6 +225,7 @@ public struct LocalAuth {
|
|||||||
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool {
|
public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool {
|
||||||
@ -221,7 +251,10 @@ public struct LocalAuth {
|
|||||||
return true
|
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 {
|
guard let bundleSeedId = self.bundleSeedId() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -262,5 +295,6 @@ public struct LocalAuth {
|
|||||||
|
|
||||||
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
||||||
return result
|
return result
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
@property (nonatomic) NSUInteger internalServerErrorCount;
|
@property (nonatomic) NSUInteger internalServerErrorCount;
|
||||||
@property (nonatomic) NSUInteger floodWaitSeconds;
|
@property (nonatomic) NSUInteger floodWaitSeconds;
|
||||||
|
@property (nonatomic, strong) NSString *floodWaitErrorText;
|
||||||
|
|
||||||
@property (nonatomic) bool waitingForTokenExport;
|
@property (nonatomic) bool waitingForTokenExport;
|
||||||
@property (nonatomic, strong) id waitingForRequestToComplete;
|
@property (nonatomic, strong) id waitingForRequestToComplete;
|
||||||
|
@ -808,7 +808,7 @@
|
|||||||
}
|
}
|
||||||
restartRequest = true;
|
restartRequest = true;
|
||||||
}
|
}
|
||||||
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound) {
|
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
|
||||||
if (request.errorContext == nil)
|
if (request.errorContext == nil)
|
||||||
request.errorContext = [[MTRequestErrorContext alloc] init];
|
request.errorContext = [[MTRequestErrorContext alloc] init];
|
||||||
|
|
||||||
@ -821,6 +821,32 @@
|
|||||||
if ([scanner scanInt:&errorWaitTime])
|
if ([scanner scanInt:&errorWaitTime])
|
||||||
{
|
{
|
||||||
request.errorContext.floodWaitSeconds = errorWaitTime;
|
request.errorContext.floodWaitSeconds = errorWaitTime;
|
||||||
|
request.errorContext.floodWaitErrorText = rpcError.errorDescription;
|
||||||
|
|
||||||
|
if (request.shouldContinueExecutionWithErrorContext != nil)
|
||||||
|
{
|
||||||
|
if (request.shouldContinueExecutionWithErrorContext(request.errorContext))
|
||||||
|
{
|
||||||
|
restartRequest = true;
|
||||||
|
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
restartRequest = true;
|
||||||
|
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ([rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
|
||||||
|
int errorWaitTime = 0;
|
||||||
|
|
||||||
|
NSScanner *scanner = [[NSScanner alloc] initWithString:rpcError.errorDescription];
|
||||||
|
[scanner scanUpToString:@"FLOOD_PREMIUM_WAIT_" intoString:nil];
|
||||||
|
[scanner scanString:@"FLOOD_PREMIUM_WAIT_" intoString:nil];
|
||||||
|
if ([scanner scanInt:&errorWaitTime])
|
||||||
|
{
|
||||||
|
request.errorContext.floodWaitSeconds = errorWaitTime;
|
||||||
|
request.errorContext.floodWaitErrorText = rpcError.errorDescription;
|
||||||
|
|
||||||
if (request.shouldContinueExecutionWithErrorContext != nil)
|
if (request.shouldContinueExecutionWithErrorContext != nil)
|
||||||
{
|
{
|
||||||
|
@ -19,8 +19,9 @@ public func printOpenFiles() {
|
|||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
var fd: Int32 = 0
|
var fd: Int32 = 0
|
||||||
var buf = Data(count: Int(MAXPATHLEN) + 1)
|
var buf = Data(count: Int(MAXPATHLEN) + 1)
|
||||||
|
let maxFd = min(1024, FD_SETSIZE)
|
||||||
|
|
||||||
while fd < FD_SETSIZE {
|
while fd < maxFd {
|
||||||
errno = 0;
|
errno = 0;
|
||||||
flags = fcntl(fd, F_GETFD, 0);
|
flags = fcntl(fd, F_GETFD, 0);
|
||||||
if flags == -1 && errno != 0 {
|
if flags == -1 && errno != 0 {
|
||||||
|
@ -144,13 +144,13 @@ public class UnauthorizedAccount {
|
|||||||
return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in
|
return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in
|
||||||
return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self))
|
return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self))
|
||||||
}
|
}
|
||||||
|> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in
|
|> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration), NoError> in
|
||||||
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in
|
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration) in
|
||||||
return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self))
|
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
|
|> 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)
|
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
|
|> map { network in
|
||||||
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
|
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
|
||||||
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
|
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
|
||||||
@ -248,7 +248,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
|||||||
if let accountState = accountState {
|
if let accountState = accountState {
|
||||||
switch accountState {
|
switch accountState {
|
||||||
case let unauthorizedState as UnauthorizedAccountState:
|
case let unauthorizedState as UnauthorizedAccountState:
|
||||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
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
|
|> map { network -> AccountResult in
|
||||||
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
||||||
}
|
}
|
||||||
@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
|||||||
return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
|
return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
|
||||||
}
|
}
|
||||||
|> mapToSignal { phoneNumber in
|
|> mapToSignal { phoneNumber in
|
||||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
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
|
|> map { network -> AccountResult in
|
||||||
return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary))
|
return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary))
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
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
|
|> map { network -> AccountResult in
|
||||||
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
||||||
}
|
}
|
||||||
@ -889,6 +889,11 @@ public func accountBackupData(postbox: Postbox) -> Signal<AccountBackupData?, No
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum NetworkSpeedLimitedEvent {
|
||||||
|
case upload
|
||||||
|
case download
|
||||||
|
}
|
||||||
|
|
||||||
public class Account {
|
public class Account {
|
||||||
static let sharedQueue = Queue(name: "Account-Shared")
|
static let sharedQueue = Queue(name: "Account-Shared")
|
||||||
|
|
||||||
@ -1519,7 +1524,8 @@ public func standaloneStateManager(
|
|||||||
proxySettings: proxySettings,
|
proxySettings: proxySettings,
|
||||||
networkSettings: networkSettings,
|
networkSettings: networkSettings,
|
||||||
phoneNumber: phoneNumber,
|
phoneNumber: phoneNumber,
|
||||||
useRequestTimeoutTimers: false
|
useRequestTimeoutTimers: false,
|
||||||
|
appConfiguration: .defaultValue
|
||||||
)
|
)
|
||||||
|> map { network -> AccountStateManager? in
|
|> map { network -> AccountStateManager? in
|
||||||
Logger.shared.log("StandaloneStateManager", "received network")
|
Logger.shared.log("StandaloneStateManager", "received network")
|
||||||
|
@ -103,7 +103,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId)
|
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> 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>)
|
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
|
||||||
if asBigPart {
|
if asBigPart {
|
||||||
let totalParts: Int32
|
let totalParts: Int32
|
||||||
@ -117,7 +117,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
|
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
|
||||||
}
|
}
|
||||||
|
|
||||||
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, 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
|
|> mapError { error -> UploadPartError in
|
||||||
if error.errorCode == 400 {
|
if error.errorCode == 400 {
|
||||||
return .invalidMedia
|
return .invalidMedia
|
||||||
@ -130,7 +130,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> 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
|
return Signal<Void, MTRpcError> { subscriber in
|
||||||
let request = MTRequest()
|
let request = MTRequest()
|
||||||
|
|
||||||
@ -159,6 +159,13 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
request.dependsOnPasswordEntry = false
|
request.dependsOnPasswordEntry = false
|
||||||
|
|
||||||
request.shouldContinueExecutionWithErrorContext = { errorContext in
|
request.shouldContinueExecutionWithErrorContext = { errorContext in
|
||||||
|
guard let errorContext = errorContext else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||||
|
onFloodWaitError(errorText)
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,7 +302,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
|> retryRequest
|
|> 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
|
return Signal { subscriber in
|
||||||
let request = MTRequest()
|
let request = MTRequest()
|
||||||
request.expectedResponseSize = expectedResponseSize ?? 0
|
request.expectedResponseSize = expectedResponseSize ?? 0
|
||||||
@ -314,6 +321,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
guard let errorContext = errorContext else {
|
guard let errorContext = errorContext else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||||
|
onFloodWaitError(errorText)
|
||||||
|
}
|
||||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -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
|
return Signal { subscriber in
|
||||||
let request = MTRequest()
|
let request = MTRequest()
|
||||||
request.expectedResponseSize = expectedResponseSize ?? 0
|
request.expectedResponseSize = expectedResponseSize ?? 0
|
||||||
@ -363,6 +373,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
guard let errorContext = errorContext else {
|
guard let errorContext = errorContext else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||||
|
onFloodWaitError(errorText)
|
||||||
|
}
|
||||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -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
|
let requestService = self.requestService
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let request = MTRequest()
|
let request = MTRequest()
|
||||||
@ -416,6 +429,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
guard let errorContext = errorContext else {
|
guard let errorContext = errorContext else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||||
|
onFloodWaitError(errorText)
|
||||||
|
}
|
||||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -104,14 +104,14 @@ private struct DownloadWrapper {
|
|||||||
self.useMainConnection = useMainConnection
|
self.useMainConnection = useMainConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?) -> 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
|
let target: MultiplexedRequestTarget
|
||||||
if self.isCdn {
|
if self.isCdn {
|
||||||
target = .cdn(Int(self.datacenterId))
|
target = .cdn(Int(self.datacenterId))
|
||||||
} else {
|
} else {
|
||||||
target = .main(Int(self.datacenterId))
|
target = .main(Int(self.datacenterId))
|
||||||
}
|
}
|
||||||
return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, 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
|
|> mapError { error, _ -> MTRpcError in
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
@ -192,7 +192,7 @@ private final class MultipartCdnHashSource {
|
|||||||
clusterContext = ClusterContext(disposable: disposable)
|
clusterContext = ClusterContext(disposable: disposable)
|
||||||
self.clusterContexts[offset] = clusterContext
|
self.clusterContexts[offset] = clusterContext
|
||||||
|
|
||||||
disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil)
|
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
|
|> map { partHashes, _ -> [Int64: Data] in
|
||||||
var parsedPartHashes: [Int64: Data] = [:]
|
var parsedPartHashes: [Int64: Data] = [:]
|
||||||
for part in partHashes {
|
for part in partHashes {
|
||||||
@ -322,7 +322,7 @@ private enum MultipartFetchSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> 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?
|
var resourceReferenceValue: MediaResourceReference?
|
||||||
switch resourceReference {
|
switch resourceReference {
|
||||||
case .forceRevalidate:
|
case .forceRevalidate:
|
||||||
@ -348,7 +348,9 @@ private enum MultipartFetchSource {
|
|||||||
case .revalidate:
|
case .revalidate:
|
||||||
return .fail(.revalidateMediaReference)
|
return .fail(.revalidateMediaReference)
|
||||||
case let .location(parsedLocation):
|
case let .location(parsedLocation):
|
||||||
return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit))
|
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
|
|> mapError { error -> MultipartFetchDownloadError in
|
||||||
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
|
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
|
||||||
return .revalidateMediaReference
|
return .revalidateMediaReference
|
||||||
@ -380,7 +382,9 @@ private enum MultipartFetchSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .web(_, location):
|
case let .web(_, location):
|
||||||
return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit))
|
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
|
|> mapError { error -> MultipartFetchDownloadError in
|
||||||
if error.errorDescription == "WEBFILE_NOT_AVAILABLE" {
|
if error.errorDescription == "WEBFILE_NOT_AVAILABLE" {
|
||||||
return .webfileNotAvailable
|
return .webfileNotAvailable
|
||||||
@ -404,7 +408,9 @@ private enum MultipartFetchSource {
|
|||||||
updatedLength += 1
|
updatedLength += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength))
|
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
|
|> mapError { _ -> MultipartFetchDownloadError in
|
||||||
return .generic
|
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() {
|
func checkState() {
|
||||||
guard let currentIntervals = self.currentIntervals else {
|
guard let currentIntervals = self.currentIntervals else {
|
||||||
return
|
return
|
||||||
@ -836,7 +849,15 @@ private final class MultipartFetchManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound)
|
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)
|
|> deliverOn(self.queue)
|
||||||
let partDisposable = MetaDisposable()
|
let partDisposable = MetaDisposable()
|
||||||
self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable)
|
self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable)
|
||||||
@ -919,7 +940,7 @@ private final class MultipartFetchManager {
|
|||||||
case let .cdn(_, _, fileToken, _, _, _, masterDownload, _):
|
case let .cdn(_, _, fileToken, _, _, _, masterDownload, _):
|
||||||
if !strongSelf.reuploadingToCdn {
|
if !strongSelf.reuploadingToCdn {
|
||||||
strongSelf.reuploadingToCdn = true
|
strongSelf.reuploadingToCdn = true
|
||||||
let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil)
|
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
|
|> map { result, _ -> [Api.FileHash] in
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -470,12 +470,21 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload
|
|||||||
fetchedResource = .complete()
|
fetchedResource = .complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let onFloodWaitError: (String) -> Void = { [weak network] error in
|
||||||
|
guard let network else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if error.hasPrefix("FLOOD_PREMIUM_WAIT") {
|
||||||
|
network.addNetworkSpeedLimitedEvent(event: .upload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in
|
let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in
|
||||||
switch uploadInterface {
|
switch uploadInterface {
|
||||||
case let .download(download):
|
case let .download(download):
|
||||||
return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression)
|
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):
|
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
|
}, progress: { progress in
|
||||||
subscriber.putNext(.progress(progress))
|
subscriber.putNext(.progress(progress))
|
||||||
|
@ -33,12 +33,13 @@ private final class RequestData {
|
|||||||
let tag: MediaResourceFetchTag?
|
let tag: MediaResourceFetchTag?
|
||||||
let continueInBackground: Bool
|
let continueInBackground: Bool
|
||||||
let automaticFloodWait: Bool
|
let automaticFloodWait: Bool
|
||||||
|
let onFloodWaitError: ((String) -> Void)?
|
||||||
let expectedResponseSize: Int32?
|
let expectedResponseSize: Int32?
|
||||||
let deserializeResponse: (Buffer) -> Any?
|
let deserializeResponse: (Buffer) -> Any?
|
||||||
let completed: (Any, NetworkResponseInfo) -> Void
|
let completed: (Any, NetworkResponseInfo) -> Void
|
||||||
let error: (MTRpcError, Double) -> Void
|
let error: (MTRpcError, Double) -> Void
|
||||||
|
|
||||||
init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, 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.id = id
|
||||||
self.consumerId = consumerId
|
self.consumerId = consumerId
|
||||||
self.resourceId = resourceId
|
self.resourceId = resourceId
|
||||||
@ -47,6 +48,7 @@ private final class RequestData {
|
|||||||
self.tag = tag
|
self.tag = tag
|
||||||
self.continueInBackground = continueInBackground
|
self.continueInBackground = continueInBackground
|
||||||
self.automaticFloodWait = automaticFloodWait
|
self.automaticFloodWait = automaticFloodWait
|
||||||
|
self.onFloodWaitError = onFloodWaitError
|
||||||
self.expectedResponseSize = expectedResponseSize
|
self.expectedResponseSize = expectedResponseSize
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
self.deserializeResponse = deserializeResponse
|
self.deserializeResponse = deserializeResponse
|
||||||
@ -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 targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground)
|
||||||
|
|
||||||
let requestId = self.nextId
|
let requestId = self.nextId
|
||||||
self.nextId += 1
|
self.nextId += 1
|
||||||
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, 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)
|
return data.2(buffer)
|
||||||
}, completed: { result, info in
|
}, completed: { result, info in
|
||||||
completed(result, info)
|
completed(result, info)
|
||||||
@ -254,7 +256,7 @@ private final class MultiplexedRequestManagerContext {
|
|||||||
let requestId = request.id
|
let requestId = request.id
|
||||||
selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable))
|
selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable))
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, 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 {
|
queue.async {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -354,13 +356,13 @@ final class MultiplexedRequestManager {
|
|||||||
return disposable
|
return disposable
|
||||||
}
|
}
|
||||||
|
|
||||||
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, 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
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
self.context.with { context in
|
self.context.with { context in
|
||||||
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
||||||
return data.2.parse(buffer)
|
return data.2.parse(buffer)
|
||||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, _ in
|
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, _ in
|
||||||
if let result = result as? T {
|
if let result = result as? T {
|
||||||
subscriber.putNext(result)
|
subscriber.putNext(result)
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
@ -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
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
self.context.with { context in
|
self.context.with { context in
|
||||||
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
||||||
return data.2.parse(buffer)
|
return data.2.parse(buffer)
|
||||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, 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 {
|
if let result = result as? T {
|
||||||
subscriber.putNext((result, info))
|
subscriber.putNext((result, info))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
|
@ -459,7 +459,7 @@ public struct NetworkInitializationArguments {
|
|||||||
private let cloudDataContext = Atomic<CloudDataContext?>(value: nil)
|
private let cloudDataContext = Atomic<CloudDataContext?>(value: nil)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool) -> 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
|
return Signal { subscriber in
|
||||||
let queue = Queue()
|
let queue = Queue()
|
||||||
queue.async {
|
queue.async {
|
||||||
@ -612,6 +612,11 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
|
|||||||
let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false
|
let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false
|
||||||
|
|
||||||
let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures, useExperimentalFeatures: useExperimentalFeatures)
|
let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures, useExperimentalFeatures: useExperimentalFeatures)
|
||||||
|
|
||||||
|
if let data = appConfiguration.data, let notifyInterval = data["upload_premium_speedup_notify_period"] as? Double {
|
||||||
|
network.updateNetworkSpeedLimitedEventNotifyInterval(value: notifyInterval)
|
||||||
|
}
|
||||||
|
|
||||||
appDataUpdatedImpl = { [weak network] data in
|
appDataUpdatedImpl = { [weak network] data in
|
||||||
guard let data = data else {
|
guard let data = data else {
|
||||||
return
|
return
|
||||||
@ -734,6 +739,22 @@ public enum NetworkRequestResult<T> {
|
|||||||
case progress(Float, Int32)
|
case progress(Float, Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class NetworkSpeedLimitedEventState {
|
||||||
|
var notifyInterval: Double = 60.0 * 60.0
|
||||||
|
var lastNotifyTimestamp: Double = 0.0
|
||||||
|
|
||||||
|
func add(event: NetworkSpeedLimitedEvent) -> Bool {
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
|
if self.lastNotifyTimestamp + self.notifyInterval < timestamp {
|
||||||
|
self.lastNotifyTimestamp = timestamp
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||||
public let encryptionProvider: EncryptionProvider
|
public let encryptionProvider: EncryptionProvider
|
||||||
|
|
||||||
@ -766,6 +787,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
return self._connectionStatus.get() |> distinctUntilChanged
|
return self._connectionStatus.get() |> distinctUntilChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var networkSpeedLimitedEvents: Signal<NetworkSpeedLimitedEvent, NoError> {
|
||||||
|
return self.networkSpeedLimitedEventPipe.signal()
|
||||||
|
}
|
||||||
|
private let networkSpeedLimitedEventPipe = ValuePipe<NetworkSpeedLimitedEvent>()
|
||||||
|
private let networkSpeedLimitedEventState = Atomic<NetworkSpeedLimitedEventState>(value: NetworkSpeedLimitedEventState())
|
||||||
|
|
||||||
public func dropConnectionStatus() {
|
public func dropConnectionStatus() {
|
||||||
_connectionStatus.set(.single(.waitingForNetwork))
|
_connectionStatus.set(.single(.waitingForNetwork))
|
||||||
}
|
}
|
||||||
@ -826,18 +853,18 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
let array = NSMutableArray()
|
let array = NSMutableArray()
|
||||||
if let result = result {
|
if let result = result {
|
||||||
switch result {
|
switch result {
|
||||||
case let .cdnConfig(publicKeys):
|
case let .cdnConfig(publicKeys):
|
||||||
for key in publicKeys {
|
for key in publicKeys {
|
||||||
switch key {
|
switch key {
|
||||||
case let .cdnPublicKey(dcId, publicKey):
|
case let .cdnPublicKey(dcId, publicKey):
|
||||||
if id == Int(dcId) {
|
if id == Int(dcId) {
|
||||||
let dict = NSMutableDictionary()
|
let dict = NSMutableDictionary()
|
||||||
dict["key"] = publicKey
|
dict["key"] = publicKey
|
||||||
dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey)
|
dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey)
|
||||||
array.add(dict)
|
array.add(dict)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return array
|
return array
|
||||||
@ -867,12 +894,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
let isCdn: Bool
|
let isCdn: Bool
|
||||||
let isMedia: Bool = true
|
let isMedia: Bool = true
|
||||||
switch target {
|
switch target {
|
||||||
case let .main(id):
|
case let .main(id):
|
||||||
datacenterId = id
|
datacenterId = id
|
||||||
isCdn = false
|
isCdn = false
|
||||||
case let .cdn(id):
|
case let .cdn(id):
|
||||||
datacenterId = id
|
datacenterId = id
|
||||||
isCdn = true
|
isCdn = true
|
||||||
}
|
}
|
||||||
return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground)
|
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()
|
let shouldKeepConnectionSignal = self.shouldKeepConnection.get()
|
||||||
|> distinctUntilChanged |> deliverOn(queue)
|
|> distinctUntilChanged |> deliverOn(queue)
|
||||||
self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in
|
self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if value {
|
if value {
|
||||||
@ -967,11 +994,11 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address)
|
self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address)
|
||||||
|
|
||||||
/*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false)
|
/*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false)
|
||||||
if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) {
|
if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) {
|
||||||
} else {
|
} else {
|
||||||
let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false)
|
let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false)
|
||||||
self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: 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)
|
let currentSchemes = self.context.transportSchemesForDatacenter(withId: Int(datacenterId), media: false, enforceMedia: false, isProxy: false)
|
||||||
var found = 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
|
let requestService = self.requestService
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let request = MTRequest()
|
let request = MTRequest()
|
||||||
@ -1006,6 +1033,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
guard let errorContext = errorContext else {
|
guard let errorContext = errorContext else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||||
|
onFloodWaitError(errorText)
|
||||||
|
}
|
||||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -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
|
let requestService = self.requestService
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let request = MTRequest()
|
let request = MTRequest()
|
||||||
@ -1075,6 +1105,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
|||||||
guard let errorContext = errorContext else {
|
guard let errorContext = errorContext else {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||||
|
onFloodWaitError(errorText)
|
||||||
|
}
|
||||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -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> {
|
public func retryRequest<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError> {
|
||||||
|
@ -35,7 +35,9 @@ extension PeerStatusSettings {
|
|||||||
|
|
||||||
var managingBot: ManagingBot?
|
var managingBot: ManagingBot?
|
||||||
if let businessBotId {
|
if let businessBotId {
|
||||||
managingBot = ManagingBot(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(businessBotId)), manageUrl: businessBotManageUrl)
|
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)
|
self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0, managingBot: managingBot)
|
||||||
|
@ -360,11 +360,11 @@ final class ChatHistoryPreloadManager {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
#if DEBUG
|
/*#if DEBUG
|
||||||
if "".isEmpty {
|
if "".isEmpty {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
#endif
|
#endif*/
|
||||||
|
|
||||||
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
|
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
|
||||||
for item in loadItems {
|
for item in loadItems {
|
||||||
|
@ -22,12 +22,15 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
|||||||
public struct ManagingBot: Codable, Equatable {
|
public struct ManagingBot: Codable, Equatable {
|
||||||
public var id: PeerId
|
public var id: PeerId
|
||||||
public var manageUrl: String?
|
public var manageUrl: String?
|
||||||
|
public var isPaused: Bool
|
||||||
|
public var canReply: Bool
|
||||||
|
|
||||||
public init(id: PeerId, manageUrl: String?) {
|
public init(id: PeerId, manageUrl: String?, isPaused: Bool, canReply: Bool) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.manageUrl = manageUrl
|
self.manageUrl = manageUrl
|
||||||
|
self.isPaused = isPaused
|
||||||
|
self.canReply = canReply
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public var flags: PeerStatusSettings.Flags
|
public var flags: PeerStatusSettings.Flags
|
||||||
|
@ -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 struct BotBiometricsState: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||||
public typealias Result = TelegramBotBiometricsState
|
public typealias Result = TelegramBotBiometricsState
|
||||||
|
|
||||||
|
@ -358,3 +358,43 @@ func _internal_updateBotBiometricsState(account: Account, peerId: EnginePeer.Id,
|
|||||||
}
|
}
|
||||||
|> ignoreValues
|
|> 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
|
||||||
|
}
|
||||||
|
@ -1494,6 +1494,14 @@ public extension TelegramEngine {
|
|||||||
public func updateBotBiometricsState(peerId: EnginePeer.Id, update: @escaping (TelegramBotBiometricsState) -> TelegramBotBiometricsState) {
|
public func updateBotBiometricsState(peerId: EnginePeer.Id, update: @escaping (TelegramBotBiometricsState) -> TelegramBotBiometricsState) {
|
||||||
let _ = _internal_updateBotBiometricsState(account: self.account, peerId: peerId, update: update).startStandalone()
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
|
||||||
authorTitle = nil
|
authorTitle = nil
|
||||||
}
|
}
|
||||||
|
@ -209,8 +209,31 @@ final class StoryContentCaptionComponent: Component {
|
|||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.shadowGradientView = UIImageView()
|
self.shadowGradientView = UIImageView()
|
||||||
if let image = StoryContentCaptionComponent.View.shadowImage {
|
if let _ = StoryContentCaptionComponent.View.shadowImage {
|
||||||
self.shadowGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0))
|
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()
|
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()))
|
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 shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow))
|
||||||
|
|
||||||
let shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0))
|
let shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0))
|
||||||
|
@ -2690,7 +2690,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.bottomContentGradientLayer.colors = colors
|
self.bottomContentGradientLayer.colors = colors
|
||||||
self.bottomContentGradientLayer.type = .axial
|
self.bottomContentGradientLayer.type = .axial
|
||||||
|
|
||||||
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3)
|
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||||
}
|
}
|
||||||
|
|
||||||
let wasPanning = self.component?.isPanning ?? false
|
let wasPanning = self.component?.isPanning ?? false
|
||||||
|
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
|
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.
@ -588,6 +588,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
|
var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)?
|
||||||
var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
|
var performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
|
||||||
|
|
||||||
|
var networkSpeedEventsDisposable: Disposable?
|
||||||
|
|
||||||
public var alwaysShowSearchResultsAsList: Bool = false {
|
public var alwaysShowSearchResultsAsList: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList)
|
self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList)
|
||||||
@ -4909,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 {
|
do {
|
||||||
let peerId = chatLocationPeerId
|
let peerId = chatLocationPeerId
|
||||||
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
|
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
|
||||||
@ -5297,10 +5324,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
threadInfo,
|
threadInfo,
|
||||||
hasSearchTags,
|
hasSearchTags,
|
||||||
hasSavedChats,
|
hasSavedChats,
|
||||||
isPremiumRequiredForMessaging
|
isPremiumRequiredForMessaging,
|
||||||
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in
|
managingBot
|
||||||
|
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5395,7 +5423,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var contactStatus: ChatContactStatus?
|
var contactStatus: ChatContactStatus?
|
||||||
if let peer = peerView.peers[peerView.peerId] {
|
if let peer = peerView.peers[peerView.peerId] {
|
||||||
if let cachedData = peerView.cachedData as? CachedUserData {
|
if let cachedData = peerView.cachedData as? CachedUserData {
|
||||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil)
|
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
|
||||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||||
var invitedBy: Peer?
|
var invitedBy: Peer?
|
||||||
if let invitedByPeerId = cachedData.invitedBy {
|
if let invitedByPeerId = cachedData.invitedBy {
|
||||||
@ -5403,7 +5431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
invitedBy = peer
|
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 {
|
} else if let cachedData = peerView.cachedData as? CachedChannelData {
|
||||||
var canReportIrrelevantLocation = true
|
var canReportIrrelevantLocation = true
|
||||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
|
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
|
||||||
@ -5418,7 +5446,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
invitedBy = peer
|
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>()
|
var peers = SimpleDictionary<PeerId, Peer>()
|
||||||
@ -5521,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 {
|
if strongSelf.presentationInterfaceState.search != nil && strongSelf.presentationInterfaceState.hasSearchTags {
|
||||||
didDisplayActionsPanel = true
|
didDisplayActionsPanel = true
|
||||||
}
|
}
|
||||||
@ -5541,6 +5572,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let contactStatus, contactStatus.managingBot != nil {
|
||||||
|
displayActionsPanel = true
|
||||||
|
}
|
||||||
if strongSelf.presentationInterfaceState.search != nil && hasSearchTags {
|
if strongSelf.presentationInterfaceState.search != nil && hasSearchTags {
|
||||||
displayActionsPanel = true
|
displayActionsPanel = true
|
||||||
}
|
}
|
||||||
@ -5874,9 +5908,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
hasScheduledMessages,
|
hasScheduledMessages,
|
||||||
hasSearchTags,
|
hasSearchTags,
|
||||||
hasSavedChats,
|
hasSavedChats,
|
||||||
isPremiumRequiredForMessaging
|
isPremiumRequiredForMessaging,
|
||||||
|
managingBot
|
||||||
)
|
)
|
||||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in
|
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.hasScheduledMessages = hasScheduledMessages
|
strongSelf.hasScheduledMessages = hasScheduledMessages
|
||||||
|
|
||||||
@ -5886,7 +5921,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if let peer = peerView.peers[peerView.peerId] {
|
if let peer = peerView.peers[peerView.peerId] {
|
||||||
copyProtectionEnabled = peer.isCopyProtectionEnabled
|
copyProtectionEnabled = peer.isCopyProtectionEnabled
|
||||||
if let cachedData = peerView.cachedData as? CachedUserData {
|
if let cachedData = peerView.cachedData as? CachedUserData {
|
||||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil)
|
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
|
||||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||||
var invitedBy: Peer?
|
var invitedBy: Peer?
|
||||||
if let invitedByPeerId = cachedData.invitedBy {
|
if let invitedByPeerId = cachedData.invitedBy {
|
||||||
@ -5894,7 +5929,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
invitedBy = peer
|
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 {
|
} else if let cachedData = peerView.cachedData as? CachedChannelData {
|
||||||
var canReportIrrelevantLocation = true
|
var canReportIrrelevantLocation = true
|
||||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
|
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
|
||||||
@ -5907,7 +5942,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
invitedBy = peer
|
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>()
|
var peers = SimpleDictionary<PeerId, Peer>()
|
||||||
@ -6111,6 +6146,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
|
||||||
|
didDisplayActionsPanel = true
|
||||||
|
}
|
||||||
|
|
||||||
var displayActionsPanel = false
|
var displayActionsPanel = false
|
||||||
if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
|
if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
|
||||||
@ -6128,6 +6166,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let contactStatus, contactStatus.managingBot != nil {
|
||||||
|
displayActionsPanel = true
|
||||||
|
}
|
||||||
|
|
||||||
if displayActionsPanel != didDisplayActionsPanel {
|
if displayActionsPanel != didDisplayActionsPanel {
|
||||||
animated = true
|
animated = true
|
||||||
@ -6799,6 +6840,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.preloadSavedMessagesChatsDisposable?.dispose()
|
self.preloadSavedMessagesChatsDisposable?.dispose()
|
||||||
self.recorderDataDisposable.dispose()
|
self.recorderDataDisposable.dispose()
|
||||||
self.displaySendWhenOnlineTipDisposable.dispose()
|
self.displaySendWhenOnlineTipDisposable.dispose()
|
||||||
|
self.networkSpeedEventsDisposable?.dispose()
|
||||||
}
|
}
|
||||||
deallocate()
|
deallocate()
|
||||||
}
|
}
|
||||||
@ -11688,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()
|
self.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,32 +117,44 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
var displayActionsPanel = false
|
var displayActionsPanel = false
|
||||||
if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings {
|
if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus {
|
||||||
if !peerStatusSettings.flags.isEmpty {
|
if let peerStatusSettings = contactStatus.peerStatusSettings {
|
||||||
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
|
if !peerStatusSettings.flags.isEmpty {
|
||||||
displayActionsPanel = true
|
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
|
||||||
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
|
displayActionsPanel = true
|
||||||
displayActionsPanel = true
|
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
|
||||||
} else if peerStatusSettings.contains(.canShareContact) {
|
displayActionsPanel = true
|
||||||
displayActionsPanel = true
|
} else if peerStatusSettings.contains(.canShareContact) {
|
||||||
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
|
displayActionsPanel = true
|
||||||
displayActionsPanel = true
|
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
|
||||||
} else if peerStatusSettings.contains(.suggestAddMembers) {
|
displayActionsPanel = true
|
||||||
|
} else if peerStatusSettings.contains(.suggestAddMembers) {
|
||||||
|
displayActionsPanel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if peerStatusSettings.requestChatTitle != nil {
|
||||||
displayActionsPanel = true
|
displayActionsPanel = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if peerStatusSettings.requestChatTitle != nil {
|
|
||||||
displayActionsPanel = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) {
|
if (selectedContext == nil || selectedContext! <= .pinnedMessage) {
|
||||||
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
|
if displayActionsPanel {
|
||||||
return currentPanel
|
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
|
||||||
} else if let controllerInteraction = controllerInteraction {
|
return currentPanel
|
||||||
let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
|
} else if let controllerInteraction = controllerInteraction {
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
|
||||||
return panel
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -1074,8 +1074,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
self.requestBiometryAuth()
|
self.requestBiometryAuth()
|
||||||
case "web_app_biometry_update_token":
|
case "web_app_biometry_update_token":
|
||||||
var tokenData: Data?
|
var tokenData: Data?
|
||||||
if let json, let tokenDataValue = json["token"] as? Data {
|
if let json, let tokenDataValue = json["token"] as? String, !tokenDataValue.isEmpty {
|
||||||
tokenData = tokenDataValue
|
tokenData = tokenDataValue.data(using: .utf8)
|
||||||
}
|
}
|
||||||
self.requestBiometryUpdateToken(tokenData: tokenData)
|
self.requestBiometryUpdateToken(tokenData: tokenData)
|
||||||
default:
|
default:
|
||||||
@ -1514,10 +1514,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
||||||
|
|
||||||
Thread { [weak self] in
|
Thread { [weak self] in
|
||||||
var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
let key = LocalAuth.getOrCreatePrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||||
if key == nil {
|
|
||||||
key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
|
||||||
}
|
|
||||||
|
|
||||||
let decryptedData: LocalAuth.DecryptionResult
|
let decryptedData: LocalAuth.DecryptionResult
|
||||||
if let key {
|
if let key {
|
||||||
@ -1567,9 +1564,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
data["status"] = isAuthorized ? "authorized" : "failed"
|
data["status"] = isAuthorized ? "authorized" : "failed"
|
||||||
if isAuthorized {
|
if isAuthorized {
|
||||||
if let tokenData {
|
if let tokenData {
|
||||||
data["token"] = tokenData
|
data["token"] = String(data: tokenData, encoding: .utf8) ?? ""
|
||||||
} else {
|
} else {
|
||||||
data["token"] = Data()
|
data["token"] = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1593,10 +1590,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
if let tokenData {
|
if let tokenData {
|
||||||
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
||||||
Thread { [weak self] in
|
Thread { [weak self] in
|
||||||
var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
let key = LocalAuth.getOrCreatePrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||||
if key == nil {
|
|
||||||
key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
|
||||||
}
|
|
||||||
|
|
||||||
var encryptedData: TelegramBotBiometricsState.OpaqueToken?
|
var encryptedData: TelegramBotBiometricsState.OpaqueToken?
|
||||||
if let key {
|
if let key {
|
||||||
@ -1619,6 +1613,28 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
state.opaqueToken = encryptedData
|
state.opaqueToken = encryptedData
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var data: [String: Any] = [:]
|
||||||
|
data["status"] = "updated"
|
||||||
|
|
||||||
|
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||||
|
} else {
|
||||||
|
var data: [String: Any] = [:]
|
||||||
|
data["status"] = "failed"
|
||||||
|
|
||||||
|
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
@ -1628,6 +1644,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
state.opaqueToken = nil
|
state.opaqueToken = nil
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var data: [String: Any] = [:]
|
||||||
|
data["status"] = "removed"
|
||||||
|
|
||||||
|
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user