mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35: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)
|
||||
}
|
||||
|
||||
public final class ChatManagingBot: Equatable {
|
||||
public let bot: EnginePeer
|
||||
public let isPaused: Bool
|
||||
public let canReply: Bool
|
||||
public let settingsUrl: String?
|
||||
|
||||
public init(bot: EnginePeer, isPaused: Bool, canReply: Bool, settingsUrl: String?) {
|
||||
self.bot = bot
|
||||
self.isPaused = isPaused
|
||||
self.canReply = canReply
|
||||
self.settingsUrl = settingsUrl
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatManagingBot, rhs: ChatManagingBot) -> Bool {
|
||||
if lhs === rhs {
|
||||
return true
|
||||
}
|
||||
if lhs.bot != rhs.bot {
|
||||
return false
|
||||
}
|
||||
if lhs.isPaused != rhs.isPaused {
|
||||
return false
|
||||
}
|
||||
if lhs.canReply != rhs.canReply {
|
||||
return false
|
||||
}
|
||||
if lhs.settingsUrl != rhs.settingsUrl {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatContactStatus: Equatable {
|
||||
public var canAddContact: Bool
|
||||
public var canReportIrrelevantLocation: Bool
|
||||
public var peerStatusSettings: PeerStatusSettings?
|
||||
public var invitedBy: Peer?
|
||||
public var managingBot: ChatManagingBot?
|
||||
|
||||
public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?) {
|
||||
public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?, managingBot: ChatManagingBot?) {
|
||||
self.canAddContact = canAddContact
|
||||
self.canReportIrrelevantLocation = canReportIrrelevantLocation
|
||||
self.peerStatusSettings = peerStatusSettings
|
||||
self.invitedBy = invitedBy
|
||||
self.managingBot = managingBot
|
||||
}
|
||||
|
||||
public var isEmpty: Bool {
|
||||
@ -270,6 +305,9 @@ public struct ChatContactStatus: Equatable {
|
||||
if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) {
|
||||
return false
|
||||
}
|
||||
if lhs.managingBot != rhs.managingBot {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,23 @@ public struct LocalAuth {
|
||||
case error(Error)
|
||||
}
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
public final class PrivateKey {
|
||||
public let publicKeyRepresentation: Data
|
||||
|
||||
fileprivate init() {
|
||||
self.publicKeyRepresentation = Data(count: 32)
|
||||
}
|
||||
|
||||
public func encrypt(data: Data) -> Data? {
|
||||
return data
|
||||
}
|
||||
|
||||
public func decrypt(data: Data) -> DecryptionResult {
|
||||
return .result(data)
|
||||
}
|
||||
}
|
||||
#else
|
||||
public final class PrivateKey {
|
||||
private let privateKey: SecKey
|
||||
private let publicKey: SecKey
|
||||
@ -64,6 +81,7 @@ public struct LocalAuth {
|
||||
return .result(result)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static var biometricAuthentication: LocalAuthBiometricAuthentication? {
|
||||
let context = LAContext()
|
||||
@ -157,7 +175,18 @@ public struct LocalAuth {
|
||||
return seedId;
|
||||
}
|
||||
|
||||
public static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
public static func getOrCreatePrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
if let key = self.getPrivateKey(baseAppBundleId: baseAppBundleId, keyId: keyId) {
|
||||
return key
|
||||
} else {
|
||||
return self.addPrivateKey(baseAppBundleId: baseAppBundleId, keyId: keyId)
|
||||
}
|
||||
}
|
||||
|
||||
private static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
#if targetEnvironment(simulator)
|
||||
return PrivateKey()
|
||||
#else
|
||||
guard let bundleSeedId = self.bundleSeedId() else {
|
||||
return nil
|
||||
}
|
||||
@ -196,6 +225,7 @@ public struct LocalAuth {
|
||||
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
||||
|
||||
return result
|
||||
#endif
|
||||
}
|
||||
|
||||
public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool {
|
||||
@ -221,7 +251,10 @@ public struct LocalAuth {
|
||||
return true
|
||||
}
|
||||
|
||||
public static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
private static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? {
|
||||
#if targetEnvironment(simulator)
|
||||
return PrivateKey()
|
||||
#else
|
||||
guard let bundleSeedId = self.bundleSeedId() else {
|
||||
return nil
|
||||
}
|
||||
@ -262,5 +295,6 @@ public struct LocalAuth {
|
||||
|
||||
let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data)
|
||||
return result
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
@property (nonatomic) NSUInteger internalServerErrorCount;
|
||||
@property (nonatomic) NSUInteger floodWaitSeconds;
|
||||
@property (nonatomic, strong) NSString *floodWaitErrorText;
|
||||
|
||||
@property (nonatomic) bool waitingForTokenExport;
|
||||
@property (nonatomic, strong) id waitingForRequestToComplete;
|
||||
|
@ -808,7 +808,7 @@
|
||||
}
|
||||
restartRequest = true;
|
||||
}
|
||||
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound) {
|
||||
else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
|
||||
if (request.errorContext == nil)
|
||||
request.errorContext = [[MTRequestErrorContext alloc] init];
|
||||
|
||||
@ -821,6 +821,32 @@
|
||||
if ([scanner scanInt:&errorWaitTime])
|
||||
{
|
||||
request.errorContext.floodWaitSeconds = errorWaitTime;
|
||||
request.errorContext.floodWaitErrorText = rpcError.errorDescription;
|
||||
|
||||
if (request.shouldContinueExecutionWithErrorContext != nil)
|
||||
{
|
||||
if (request.shouldContinueExecutionWithErrorContext(request.errorContext))
|
||||
{
|
||||
restartRequest = true;
|
||||
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
restartRequest = true;
|
||||
request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime);
|
||||
}
|
||||
}
|
||||
} else if ([rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) {
|
||||
int errorWaitTime = 0;
|
||||
|
||||
NSScanner *scanner = [[NSScanner alloc] initWithString:rpcError.errorDescription];
|
||||
[scanner scanUpToString:@"FLOOD_PREMIUM_WAIT_" intoString:nil];
|
||||
[scanner scanString:@"FLOOD_PREMIUM_WAIT_" intoString:nil];
|
||||
if ([scanner scanInt:&errorWaitTime])
|
||||
{
|
||||
request.errorContext.floodWaitSeconds = errorWaitTime;
|
||||
request.errorContext.floodWaitErrorText = rpcError.errorDescription;
|
||||
|
||||
if (request.shouldContinueExecutionWithErrorContext != nil)
|
||||
{
|
||||
|
@ -19,8 +19,9 @@ public func printOpenFiles() {
|
||||
var flags: Int32 = 0
|
||||
var fd: Int32 = 0
|
||||
var buf = Data(count: Int(MAXPATHLEN) + 1)
|
||||
let maxFd = min(1024, FD_SETSIZE)
|
||||
|
||||
while fd < FD_SETSIZE {
|
||||
while fd < maxFd {
|
||||
errno = 0;
|
||||
flags = fcntl(fd, F_GETFD, 0);
|
||||
if flags == -1 && errno != 0 {
|
||||
|
@ -144,13 +144,13 @@ public class UnauthorizedAccount {
|
||||
return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in
|
||||
return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self))
|
||||
}
|
||||
|> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in
|
||||
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in
|
||||
return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self))
|
||||
|> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration), NoError> in
|
||||
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration) in
|
||||
return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self), transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal<UnauthorizedAccount, NoError> in
|
||||
return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false)
|
||||
|> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal<UnauthorizedAccount, NoError> in
|
||||
return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false, appConfiguration: appConfiguration)
|
||||
|> map { network in
|
||||
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
|
||||
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
|
||||
@ -248,7 +248,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
||||
if let accountState = accountState {
|
||||
switch accountState {
|
||||
case let unauthorizedState as UnauthorizedAccountState:
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
|> map { network -> AccountResult in
|
||||
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
||||
}
|
||||
@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
||||
return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
|
||||
}
|
||||
|> mapToSignal { phoneNumber in
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
|> map { network -> AccountResult in
|
||||
return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary))
|
||||
}
|
||||
@ -267,7 +267,7 @@ public func accountWithId(accountManager: AccountManager<TelegramAccountManagerT
|
||||
}
|
||||
}
|
||||
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers)
|
||||
return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig)
|
||||
|> map { network -> AccountResult in
|
||||
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
|
||||
}
|
||||
@ -889,6 +889,11 @@ public func accountBackupData(postbox: Postbox) -> Signal<AccountBackupData?, No
|
||||
}
|
||||
}
|
||||
|
||||
public enum NetworkSpeedLimitedEvent {
|
||||
case upload
|
||||
case download
|
||||
}
|
||||
|
||||
public class Account {
|
||||
static let sharedQueue = Queue(name: "Account-Shared")
|
||||
|
||||
@ -1519,7 +1524,8 @@ public func standaloneStateManager(
|
||||
proxySettings: proxySettings,
|
||||
networkSettings: networkSettings,
|
||||
phoneNumber: phoneNumber,
|
||||
useRequestTimeoutTimers: false
|
||||
useRequestTimeoutTimers: false,
|
||||
appConfiguration: .defaultValue
|
||||
)
|
||||
|> map { network -> AccountStateManager? in
|
||||
Logger.shared.log("StandaloneStateManager", "received network")
|
||||
|
@ -103,7 +103,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId)
|
||||
}
|
||||
|
||||
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> {
|
||||
static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<Void, UploadPartError> {
|
||||
let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>)
|
||||
if asBigPart {
|
||||
let totalParts: Int32
|
||||
@ -117,7 +117,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data))
|
||||
}
|
||||
|
||||
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, expectedResponseSize: nil)
|
||||
return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, onFloodWaitError: onFloodWaitError, expectedResponseSize: nil)
|
||||
|> mapError { error -> UploadPartError in
|
||||
if error.errorCode == 400 {
|
||||
return .invalidMedia
|
||||
@ -130,7 +130,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal<Void, UploadPartError> {
|
||||
func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<Void, UploadPartError> {
|
||||
return Signal<Void, MTRpcError> { subscriber in
|
||||
let request = MTRequest()
|
||||
|
||||
@ -159,6 +159,13 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
request.dependsOnPasswordEntry = false
|
||||
|
||||
request.shouldContinueExecutionWithErrorContext = { errorContext in
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -295,7 +302,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
|> retryRequest
|
||||
}
|
||||
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> {
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<T, MTRpcError> {
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
request.expectedResponseSize = expectedResponseSize ?? 0
|
||||
@ -314,6 +321,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
@ -344,7 +354,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func requestWithAdditionalData<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> {
|
||||
func requestWithAdditionalData<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> {
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
request.expectedResponseSize = expectedResponseSize ?? 0
|
||||
@ -363,6 +373,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
@ -396,7 +409,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
let requestService = self.requestService
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
@ -416,6 +429,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
|
@ -104,14 +104,14 @@ private struct DownloadWrapper {
|
||||
self.useMainConnection = useMainConnection
|
||||
}
|
||||
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), MTRpcError> {
|
||||
func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?, onFloodWaitError: @escaping (String) -> Void) -> Signal<(T, NetworkResponseInfo), MTRpcError> {
|
||||
let target: MultiplexedRequestTarget
|
||||
if self.isCdn {
|
||||
target = .cdn(Int(self.datacenterId))
|
||||
} else {
|
||||
target = .main(Int(self.datacenterId))
|
||||
}
|
||||
return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, expectedResponseSize: expectedResponseSize)
|
||||
return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize)
|
||||
|> mapError { error, _ -> MTRpcError in
|
||||
return error
|
||||
}
|
||||
@ -192,7 +192,7 @@ private final class MultipartCdnHashSource {
|
||||
clusterContext = ClusterContext(disposable: disposable)
|
||||
self.clusterContexts[offset] = clusterContext
|
||||
|
||||
disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil)
|
||||
disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil, onFloodWaitError: { _ in })
|
||||
|> map { partHashes, _ -> [Int64: Data] in
|
||||
var parsedPartHashes: [Int64: Data] = [:]
|
||||
for part in partHashes {
|
||||
@ -322,7 +322,7 @@ private enum MultipartFetchSource {
|
||||
}
|
||||
}
|
||||
|
||||
func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> {
|
||||
func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool, onFloodWaitError: @escaping (String) -> Void) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> {
|
||||
var resourceReferenceValue: MediaResourceReference?
|
||||
switch resourceReference {
|
||||
case .forceRevalidate:
|
||||
@ -348,7 +348,9 @@ private enum MultipartFetchSource {
|
||||
case .revalidate:
|
||||
return .fail(.revalidateMediaReference)
|
||||
case let .location(parsedLocation):
|
||||
return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit))
|
||||
return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in
|
||||
onFloodWaitError(error)
|
||||
})
|
||||
|> mapError { error -> MultipartFetchDownloadError in
|
||||
if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") {
|
||||
return .revalidateMediaReference
|
||||
@ -380,7 +382,9 @@ private enum MultipartFetchSource {
|
||||
}
|
||||
}
|
||||
case let .web(_, location):
|
||||
return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit))
|
||||
return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in
|
||||
onFloodWaitError(error)
|
||||
})
|
||||
|> mapError { error -> MultipartFetchDownloadError in
|
||||
if error.errorDescription == "WEBFILE_NOT_AVAILABLE" {
|
||||
return .webfileNotAvailable
|
||||
@ -404,7 +408,9 @@ private enum MultipartFetchSource {
|
||||
updatedLength += 1
|
||||
}
|
||||
|
||||
let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength))
|
||||
let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength), onFloodWaitError: { error in
|
||||
onFloodWaitError(error)
|
||||
})
|
||||
|> mapError { _ -> MultipartFetchDownloadError in
|
||||
return .generic
|
||||
}
|
||||
@ -723,6 +729,13 @@ private final class MultipartFetchManager {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func processFloodWaitError(error: String) {
|
||||
if error.hasPrefix("FLOOD_PREMIUM_WAIT") {
|
||||
self.network.addNetworkSpeedLimitedEvent(event: .download)
|
||||
}
|
||||
}
|
||||
|
||||
func checkState() {
|
||||
guard let currentIntervals = self.currentIntervals else {
|
||||
return
|
||||
@ -836,7 +849,15 @@ private final class MultipartFetchManager {
|
||||
}
|
||||
|
||||
let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound)
|
||||
let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground)
|
||||
let queue = self.queue
|
||||
let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground, onFloodWaitError: { [weak self] error in
|
||||
queue.async {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.processFloodWaitError(error: error)
|
||||
}
|
||||
})
|
||||
|> deliverOn(self.queue)
|
||||
let partDisposable = MetaDisposable()
|
||||
self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable)
|
||||
@ -919,7 +940,7 @@ private final class MultipartFetchManager {
|
||||
case let .cdn(_, _, fileToken, _, _, _, masterDownload, _):
|
||||
if !strongSelf.reuploadingToCdn {
|
||||
strongSelf.reuploadingToCdn = true
|
||||
let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil)
|
||||
let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil, onFloodWaitError: { _ in })
|
||||
|> map { result, _ -> [Api.FileHash] in
|
||||
return result
|
||||
}
|
||||
|
@ -470,12 +470,21 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload
|
||||
fetchedResource = .complete()
|
||||
}
|
||||
|
||||
let onFloodWaitError: (String) -> Void = { [weak network] error in
|
||||
guard let network else {
|
||||
return
|
||||
}
|
||||
if error.hasPrefix("FLOOD_PREMIUM_WAIT") {
|
||||
network.addNetworkSpeedLimitedEvent(event: .upload)
|
||||
}
|
||||
}
|
||||
|
||||
let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in
|
||||
switch uploadInterface {
|
||||
case let .download(download):
|
||||
return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression)
|
||||
return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError)
|
||||
case let .multiplexed(multiplexed, datacenterId, consumerId):
|
||||
return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression)
|
||||
return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError)
|
||||
}
|
||||
}, progress: { progress in
|
||||
subscriber.putNext(.progress(progress))
|
||||
|
@ -33,12 +33,13 @@ private final class RequestData {
|
||||
let tag: MediaResourceFetchTag?
|
||||
let continueInBackground: Bool
|
||||
let automaticFloodWait: Bool
|
||||
let onFloodWaitError: ((String) -> Void)?
|
||||
let expectedResponseSize: Int32?
|
||||
let deserializeResponse: (Buffer) -> Any?
|
||||
let completed: (Any, NetworkResponseInfo) -> Void
|
||||
let error: (MTRpcError, Double) -> Void
|
||||
|
||||
init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
|
||||
init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, onFloodWaitError: ((String) -> Void)?, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
|
||||
self.id = id
|
||||
self.consumerId = consumerId
|
||||
self.resourceId = resourceId
|
||||
@ -47,6 +48,7 @@ private final class RequestData {
|
||||
self.tag = tag
|
||||
self.continueInBackground = continueInBackground
|
||||
self.automaticFloodWait = automaticFloodWait
|
||||
self.onFloodWaitError = onFloodWaitError
|
||||
self.expectedResponseSize = expectedResponseSize
|
||||
self.payload = payload
|
||||
self.deserializeResponse = deserializeResponse
|
||||
@ -155,12 +157,12 @@ private final class MultiplexedRequestManagerContext {
|
||||
}
|
||||
}
|
||||
|
||||
func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
|
||||
func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
|
||||
let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground)
|
||||
|
||||
let requestId = self.nextId
|
||||
self.nextId += 1
|
||||
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in
|
||||
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in
|
||||
return data.2(buffer)
|
||||
}, completed: { result, info in
|
||||
completed(result, info)
|
||||
@ -254,7 +256,7 @@ private final class MultiplexedRequestManagerContext {
|
||||
let requestId = request.id
|
||||
selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable))
|
||||
let queue = self.queue
|
||||
disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in
|
||||
disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, onFloodWaitError: request.onFloodWaitError, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -354,13 +356,13 @@ final class MultiplexedRequestManager {
|
||||
return disposable
|
||||
}
|
||||
|
||||
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<T, MTRpcError> {
|
||||
func request<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<T, MTRpcError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.context.with { context in
|
||||
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
||||
return data.2.parse(buffer)
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, _ in
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, _ in
|
||||
if let result = result as? T {
|
||||
subscriber.putNext(result)
|
||||
subscriber.putCompletion()
|
||||
@ -375,13 +377,13 @@ final class MultiplexedRequestManager {
|
||||
}
|
||||
}
|
||||
|
||||
func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
func requestWithAdditionalInfo<T>(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.context.with { context in
|
||||
disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in
|
||||
return data.2.parse(buffer)
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, info in
|
||||
}), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, info in
|
||||
if let result = result as? T {
|
||||
subscriber.putNext((result, info))
|
||||
subscriber.putCompletion()
|
||||
|
@ -459,7 +459,7 @@ public struct NetworkInitializationArguments {
|
||||
private let cloudDataContext = Atomic<CloudDataContext?>(value: nil)
|
||||
#endif
|
||||
|
||||
func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool) -> Signal<Network, NoError> {
|
||||
func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool, appConfiguration: AppConfiguration) -> Signal<Network, NoError> {
|
||||
return Signal { subscriber in
|
||||
let queue = Queue()
|
||||
queue.async {
|
||||
@ -612,6 +612,11 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
|
||||
let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false
|
||||
|
||||
let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures, useExperimentalFeatures: useExperimentalFeatures)
|
||||
|
||||
if let data = appConfiguration.data, let notifyInterval = data["upload_premium_speedup_notify_period"] as? Double {
|
||||
network.updateNetworkSpeedLimitedEventNotifyInterval(value: notifyInterval)
|
||||
}
|
||||
|
||||
appDataUpdatedImpl = { [weak network] data in
|
||||
guard let data = data else {
|
||||
return
|
||||
@ -734,6 +739,22 @@ public enum NetworkRequestResult<T> {
|
||||
case progress(Float, Int32)
|
||||
}
|
||||
|
||||
private final class NetworkSpeedLimitedEventState {
|
||||
var notifyInterval: Double = 60.0 * 60.0
|
||||
var lastNotifyTimestamp: Double = 0.0
|
||||
|
||||
func add(event: NetworkSpeedLimitedEvent) -> Bool {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
if self.lastNotifyTimestamp + self.notifyInterval < timestamp {
|
||||
self.lastNotifyTimestamp = timestamp
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
public let encryptionProvider: EncryptionProvider
|
||||
|
||||
@ -766,6 +787,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
return self._connectionStatus.get() |> distinctUntilChanged
|
||||
}
|
||||
|
||||
public var networkSpeedLimitedEvents: Signal<NetworkSpeedLimitedEvent, NoError> {
|
||||
return self.networkSpeedLimitedEventPipe.signal()
|
||||
}
|
||||
private let networkSpeedLimitedEventPipe = ValuePipe<NetworkSpeedLimitedEvent>()
|
||||
private let networkSpeedLimitedEventState = Atomic<NetworkSpeedLimitedEventState>(value: NetworkSpeedLimitedEventState())
|
||||
|
||||
public func dropConnectionStatus() {
|
||||
_connectionStatus.set(.single(.waitingForNetwork))
|
||||
}
|
||||
@ -826,18 +853,18 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
let array = NSMutableArray()
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .cdnConfig(publicKeys):
|
||||
for key in publicKeys {
|
||||
switch key {
|
||||
case let .cdnPublicKey(dcId, publicKey):
|
||||
if id == Int(dcId) {
|
||||
let dict = NSMutableDictionary()
|
||||
dict["key"] = publicKey
|
||||
dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey)
|
||||
array.add(dict)
|
||||
}
|
||||
case let .cdnConfig(publicKeys):
|
||||
for key in publicKeys {
|
||||
switch key {
|
||||
case let .cdnPublicKey(dcId, publicKey):
|
||||
if id == Int(dcId) {
|
||||
let dict = NSMutableDictionary()
|
||||
dict["key"] = publicKey
|
||||
dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey)
|
||||
array.add(dict)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return array
|
||||
@ -867,12 +894,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
let isCdn: Bool
|
||||
let isMedia: Bool = true
|
||||
switch target {
|
||||
case let .main(id):
|
||||
datacenterId = id
|
||||
isCdn = false
|
||||
case let .cdn(id):
|
||||
datacenterId = id
|
||||
isCdn = true
|
||||
case let .main(id):
|
||||
datacenterId = id
|
||||
isCdn = false
|
||||
case let .cdn(id):
|
||||
datacenterId = id
|
||||
isCdn = true
|
||||
}
|
||||
return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground)
|
||||
}
|
||||
@ -880,7 +907,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
})
|
||||
|
||||
let shouldKeepConnectionSignal = self.shouldKeepConnection.get()
|
||||
|> distinctUntilChanged |> deliverOn(queue)
|
||||
|> distinctUntilChanged |> deliverOn(queue)
|
||||
self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if value {
|
||||
@ -967,11 +994,11 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address)
|
||||
|
||||
/*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false)
|
||||
if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) {
|
||||
} else {
|
||||
let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false)
|
||||
self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false)
|
||||
}*/
|
||||
if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) {
|
||||
} else {
|
||||
let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false)
|
||||
self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false)
|
||||
}*/
|
||||
|
||||
let currentSchemes = self.context.transportSchemesForDatacenter(withId: Int(datacenterId), media: false, enforceMedia: false, isProxy: false)
|
||||
var found = false
|
||||
@ -988,7 +1015,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
public func requestWithAdditionalInfo<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal<NetworkRequestResult<T>, MTRpcError> {
|
||||
public func requestWithAdditionalInfo<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<NetworkRequestResult<T>, MTRpcError> {
|
||||
let requestService = self.requestService
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
@ -1006,6 +1033,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
@ -1056,8 +1086,8 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal<T, MTRpcError> {
|
||||
|
||||
public func request<T>(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse<T>), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal<T, MTRpcError> {
|
||||
let requestService = self.requestService
|
||||
return Signal { subscriber in
|
||||
let request = MTRequest()
|
||||
@ -1075,6 +1105,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
guard let errorContext = errorContext else {
|
||||
return true
|
||||
}
|
||||
if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText {
|
||||
onFloodWaitError(errorText)
|
||||
}
|
||||
if errorContext.floodWaitSeconds > 0 && !automaticFloodWait {
|
||||
return false
|
||||
}
|
||||
@ -1113,6 +1146,21 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNetworkSpeedLimitedEventNotifyInterval(value: Double) {
|
||||
let _ = self.networkSpeedLimitedEventState.with { state in
|
||||
state.notifyInterval = value
|
||||
}
|
||||
}
|
||||
|
||||
func addNetworkSpeedLimitedEvent(event: NetworkSpeedLimitedEvent) {
|
||||
let notify = self.networkSpeedLimitedEventState.with { state in
|
||||
return state.add(event: event)
|
||||
}
|
||||
if notify {
|
||||
self.networkSpeedLimitedEventPipe.putNext(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func retryRequest<T>(signal: Signal<T, MTRpcError>) -> Signal<T, NoError> {
|
||||
|
@ -35,7 +35,9 @@ extension PeerStatusSettings {
|
||||
|
||||
var managingBot: ManagingBot?
|
||||
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)
|
||||
|
@ -360,11 +360,11 @@ final class ChatHistoryPreloadManager {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
#if DEBUG
|
||||
/*#if DEBUG
|
||||
if "".isEmpty {
|
||||
return
|
||||
}
|
||||
#endif
|
||||
#endif*/
|
||||
|
||||
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
|
||||
for item in loadItems {
|
||||
|
@ -22,12 +22,15 @@ public struct PeerStatusSettings: PostboxCoding, Equatable {
|
||||
public struct ManagingBot: Codable, Equatable {
|
||||
public var id: PeerId
|
||||
public var manageUrl: String?
|
||||
public var isPaused: Bool
|
||||
public var canReply: Bool
|
||||
|
||||
public init(id: PeerId, manageUrl: String?) {
|
||||
public init(id: PeerId, manageUrl: String?, isPaused: Bool, canReply: Bool) {
|
||||
self.id = id
|
||||
self.manageUrl = manageUrl
|
||||
self.isPaused = isPaused
|
||||
self.canReply = canReply
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public var flags: PeerStatusSettings.Flags
|
||||
|
@ -1585,6 +1585,34 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatManagingBot: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = PeerStatusSettings.ManagingBot?
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .cachedPeerData(peerId: self.id)
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? CachedPeerDataView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
if let cachedData = view.cachedPeerData as? CachedUserData {
|
||||
return cachedData.peerStatusSettings?.managingBot
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BotBiometricsState: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = TelegramBotBiometricsState
|
||||
|
||||
|
@ -358,3 +358,43 @@ func _internal_updateBotBiometricsState(account: Account, peerId: EnginePeer.Id,
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
func _internal_toggleChatManagingBotIsPaused(account: Account, chatId: EnginePeer.Id) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.updatePeerCachedData(peerIds: Set([chatId]), update: { _, current in
|
||||
guard let current = current as? CachedUserData else {
|
||||
return current
|
||||
}
|
||||
|
||||
if var peerStatusSettings = current.peerStatusSettings {
|
||||
if let managingBot = peerStatusSettings.managingBot {
|
||||
peerStatusSettings.managingBot?.isPaused = !managingBot.isPaused
|
||||
}
|
||||
|
||||
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
func _internal_removeChatManagingBot(account: Account, chatId: EnginePeer.Id) -> Signal<Never, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
transaction.updatePeerCachedData(peerIds: Set([chatId]), update: { _, current in
|
||||
guard let current = current as? CachedUserData else {
|
||||
return current
|
||||
}
|
||||
|
||||
if var peerStatusSettings = current.peerStatusSettings {
|
||||
peerStatusSettings.managingBot = nil
|
||||
|
||||
return current.withUpdatedPeerStatusSettings(peerStatusSettings)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
@ -1494,6 +1494,14 @@ public extension TelegramEngine {
|
||||
public func updateBotBiometricsState(peerId: EnginePeer.Id, update: @escaping (TelegramBotBiometricsState) -> TelegramBotBiometricsState) {
|
||||
let _ = _internal_updateBotBiometricsState(account: self.account, peerId: peerId, update: update).startStandalone()
|
||||
}
|
||||
|
||||
public func toggleChatManagingBotIsPaused(chatId: EnginePeer.Id) {
|
||||
let _ = _internal_toggleChatManagingBotIsPaused(account: self.account, chatId: chatId).startStandalone()
|
||||
}
|
||||
|
||||
public func removeChatManagingBot(chatId: EnginePeer.Id) {
|
||||
let _ = _internal_removeChatManagingBot(account: self.account, chatId: chatId).startStandalone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,18 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
|
||||
}
|
||||
}
|
||||
|
||||
if authorTitle == nil {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? InlineBusinessBotMessageAttribute {
|
||||
if let title = attribute.title {
|
||||
authorTitle = title
|
||||
} else if let peerId = attribute.peerId, let peer = message.peers[peerId] {
|
||||
authorTitle = peer.debugDisplayTitle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
|
||||
authorTitle = nil
|
||||
}
|
||||
|
@ -209,8 +209,31 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.shadowGradientView = UIImageView()
|
||||
if let image = StoryContentCaptionComponent.View.shadowImage {
|
||||
self.shadowGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0))
|
||||
if let _ = StoryContentCaptionComponent.View.shadowImage {
|
||||
let height: CGFloat = 128.0
|
||||
let baseGradientAlpha: CGFloat = 0.8
|
||||
let numSteps = 8
|
||||
let firstStep = 0
|
||||
let firstLocation = 0.0
|
||||
let colors = (0 ..< numSteps).map { i -> UIColor in
|
||||
if i < firstStep {
|
||||
return UIColor(white: 1.0, alpha: 1.0)
|
||||
} else {
|
||||
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
|
||||
return UIColor(white: 0.0, alpha: baseGradientAlpha * value)
|
||||
}
|
||||
}
|
||||
let locations = (0 ..< numSteps).map { i -> CGFloat in
|
||||
if i < firstStep {
|
||||
return 0.0
|
||||
} else {
|
||||
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||
return (firstLocation + (1.0 - firstLocation) * step)
|
||||
}
|
||||
}
|
||||
|
||||
self.shadowGradientView.image = generateGradientImage(size: CGSize(width: 8.0, height: height), colors: colors.reversed(), locations: locations.reversed().map { 1.0 - $0 })!.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(height - 1.0))
|
||||
}
|
||||
|
||||
self.scrollViewContainer = UIView()
|
||||
@ -386,7 +409,8 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
transition.setBounds(view: self.textSelectionKnobContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.scrollView.bounds.minY), size: CGSize()))
|
||||
|
||||
let shadowOverflow: CGFloat = 58.0
|
||||
let shadowHeight: CGFloat = self.shadowGradientView.image?.size.height ?? 100.0
|
||||
let shadowOverflow: CGFloat = floor(shadowHeight * 0.6)
|
||||
let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow))
|
||||
|
||||
let shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0))
|
||||
|
@ -2690,7 +2690,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
self.bottomContentGradientLayer.colors = colors
|
||||
self.bottomContentGradientLayer.type = .axial
|
||||
|
||||
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3)
|
||||
self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
}
|
||||
|
||||
let wasPanning = self.component?.isPanning ?? false
|
||||
|
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 performOpenURL: ((Message?, String, Promise<Bool>?) -> Void)?
|
||||
|
||||
var networkSpeedEventsDisposable: Disposable?
|
||||
|
||||
public var alwaysShowSearchResultsAsList: Bool = false {
|
||||
didSet {
|
||||
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 {
|
||||
let peerId = chatLocationPeerId
|
||||
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
|
||||
@ -5297,10 +5324,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
threadInfo,
|
||||
hasSearchTags,
|
||||
hasSavedChats,
|
||||
isPremiumRequiredForMessaging
|
||||
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in
|
||||
isPremiumRequiredForMessaging,
|
||||
managingBot
|
||||
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, peerReportNotice, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging {
|
||||
if strongSelf.peerView === peerView && strongSelf.reportIrrelvantGeoNotice == peerReportNotice && strongSelf.hasScheduledMessages == hasScheduledMessages && strongSelf.threadInfo == threadInfo && strongSelf.presentationInterfaceState.hasSearchTags == hasSearchTags && strongSelf.presentationInterfaceState.hasSavedChats == hasSavedChats && strongSelf.presentationInterfaceState.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging && managingBot == strongSelf.presentationInterfaceState.contactStatus?.managingBot {
|
||||
return
|
||||
}
|
||||
|
||||
@ -5395,7 +5423,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var contactStatus: ChatContactStatus?
|
||||
if let peer = peerView.peers[peerView.peerId] {
|
||||
if let cachedData = peerView.cachedData as? CachedUserData {
|
||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil)
|
||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
|
||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||
var invitedBy: Peer?
|
||||
if let invitedByPeerId = cachedData.invitedBy {
|
||||
@ -5403,7 +5431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
invitedBy = peer
|
||||
}
|
||||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy)
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
} else if let cachedData = peerView.cachedData as? CachedChannelData {
|
||||
var canReportIrrelevantLocation = true
|
||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
|
||||
@ -5418,7 +5446,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
invitedBy = peer
|
||||
}
|
||||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy)
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
}
|
||||
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -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 {
|
||||
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 {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
@ -5874,9 +5908,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
hasScheduledMessages,
|
||||
hasSearchTags,
|
||||
hasSavedChats,
|
||||
isPremiumRequiredForMessaging
|
||||
isPremiumRequiredForMessaging,
|
||||
managingBot
|
||||
)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging in
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot in
|
||||
if let strongSelf = self {
|
||||
strongSelf.hasScheduledMessages = hasScheduledMessages
|
||||
|
||||
@ -5886,7 +5921,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let peer = peerView.peers[peerView.peerId] {
|
||||
copyProtectionEnabled = peer.isCopyProtectionEnabled
|
||||
if let cachedData = peerView.cachedData as? CachedUserData {
|
||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil)
|
||||
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
|
||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||
var invitedBy: Peer?
|
||||
if let invitedByPeerId = cachedData.invitedBy {
|
||||
@ -5894,7 +5929,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
invitedBy = peer
|
||||
}
|
||||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy)
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
} else if let cachedData = peerView.cachedData as? CachedChannelData {
|
||||
var canReportIrrelevantLocation = true
|
||||
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.participationStatus == .member {
|
||||
@ -5907,7 +5942,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
invitedBy = peer
|
||||
}
|
||||
}
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy)
|
||||
contactStatus = ChatContactStatus(canAddContact: false, canReportIrrelevantLocation: canReportIrrelevantLocation, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
|
||||
}
|
||||
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -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
|
||||
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 {
|
||||
animated = true
|
||||
@ -6799,6 +6840,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.preloadSavedMessagesChatsDisposable?.dispose()
|
||||
self.recorderDataDisposable.dispose()
|
||||
self.displaySendWhenOnlineTipDisposable.dispose()
|
||||
self.networkSpeedEventsDisposable?.dispose()
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -117,32 +117,44 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
||||
}
|
||||
|
||||
var displayActionsPanel = false
|
||||
if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings {
|
||||
if !peerStatusSettings.flags.isEmpty {
|
||||
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.canShareContact) {
|
||||
displayActionsPanel = true
|
||||
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.suggestAddMembers) {
|
||||
if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus {
|
||||
if let peerStatusSettings = contactStatus.peerStatusSettings {
|
||||
if !peerStatusSettings.flags.isEmpty {
|
||||
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.canShareContact) {
|
||||
displayActionsPanel = true
|
||||
} else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) {
|
||||
displayActionsPanel = true
|
||||
} else if peerStatusSettings.contains(.suggestAddMembers) {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
}
|
||||
if peerStatusSettings.requestChatTitle != nil {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
}
|
||||
if peerStatusSettings.requestChatTitle != nil {
|
||||
displayActionsPanel = true
|
||||
}
|
||||
}
|
||||
|
||||
if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) {
|
||||
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
|
||||
return currentPanel
|
||||
} else if let controllerInteraction = controllerInteraction {
|
||||
let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
if (selectedContext == nil || selectedContext! <= .pinnedMessage) {
|
||||
if displayActionsPanel {
|
||||
if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode {
|
||||
return currentPanel
|
||||
} else if let controllerInteraction = controllerInteraction {
|
||||
let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
} else if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, contactStatus.managingBot != nil {
|
||||
if let currentPanel = currentPanel as? ChatManagingBotTitlePanelNode {
|
||||
return currentPanel
|
||||
} else {
|
||||
let panel = ChatManagingBotTitlePanelNode(context: context)
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,448 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramPresentationData
|
||||
import ChatPresentationInterfaceState
|
||||
import ComponentFlow
|
||||
import AvatarNode
|
||||
import MultilineTextComponent
|
||||
import PlainButtonComponent
|
||||
import ComponentDisplayAdapters
|
||||
import AccountContext
|
||||
import TelegramCore
|
||||
import BundleIconComponent
|
||||
import ContextUI
|
||||
import SwiftSignalKit
|
||||
|
||||
private final class ChatManagingBotTitlePanelComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let insets: UIEdgeInsets
|
||||
let peer: EnginePeer
|
||||
let managesChat: Bool
|
||||
let isPaused: Bool
|
||||
let toggleIsPaused: () -> Void
|
||||
let openSettings: (UIView) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
insets: UIEdgeInsets,
|
||||
peer: EnginePeer,
|
||||
managesChat: Bool,
|
||||
isPaused: Bool,
|
||||
toggleIsPaused: @escaping () -> Void,
|
||||
openSettings: @escaping (UIView) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.insets = insets
|
||||
self.peer = peer
|
||||
self.managesChat = managesChat
|
||||
self.isPaused = isPaused
|
||||
self.toggleIsPaused = toggleIsPaused
|
||||
self.openSettings = openSettings
|
||||
}
|
||||
|
||||
static func ==(lhs: ChatManagingBotTitlePanelComponent, rhs: ChatManagingBotTitlePanelComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings != rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.insets != rhs.insets {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.managesChat != rhs.managesChat {
|
||||
return false
|
||||
}
|
||||
if lhs.isPaused != rhs.isPaused {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let text = ComponentView<Empty>()
|
||||
private var avatarNode: AvatarNode?
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
private let settingsButton = ComponentView<Empty>()
|
||||
|
||||
private var component: ChatManagingBotTitlePanelComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ChatManagingBotTitlePanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let topInset: CGFloat = 6.0
|
||||
let bottomInset: CGFloat = 6.0
|
||||
let avatarDiameter: CGFloat = 36.0
|
||||
let avatarTextSpacing: CGFloat = 10.0
|
||||
let titleTextSpacing: CGFloat = 1.0
|
||||
let leftInset: CGFloat = component.insets.left + 12.0
|
||||
let rightInset: CGFloat = component.insets.right + 10.0
|
||||
let actionAndSettingsButtonsSpacing: CGFloat = 8.0
|
||||
|
||||
//TODO:localize
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.isPaused ? "START" : "STOP", font: Font.semibold(15.0), textColor: component.theme.list.itemCheckColors.foregroundColor))
|
||||
)),
|
||||
background: AnyComponent(RoundedRectangle(
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
cornerRadius: nil
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
contentInsets: UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.toggleIsPaused()
|
||||
},
|
||||
animateAlpha: true,
|
||||
animateScale: false,
|
||||
animateContents: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 150.0, height: 100.0)
|
||||
)
|
||||
|
||||
let settingsButtonSize = self.settingsButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Chat/Context Menu/Customize",
|
||||
tintColor: component.theme.rootController.navigationBar.controlColor
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
minSize: CGSize(width: 1.0, height: 40.0),
|
||||
contentInsets: UIEdgeInsets(top: 0.0, left: 2.0, bottom: 0.0, right: 2.0),
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard let settingsButtonView = self.settingsButton.view else {
|
||||
return
|
||||
}
|
||||
component.openSettings(settingsButtonView)
|
||||
},
|
||||
animateAlpha: true,
|
||||
animateScale: false,
|
||||
animateContents: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 150.0, height: 100.0)
|
||||
)
|
||||
|
||||
var maxTextWidth: CGFloat = availableSize.width - leftInset - avatarDiameter - avatarTextSpacing - rightInset - settingsButtonSize.width - 8.0
|
||||
if component.managesChat {
|
||||
maxTextWidth -= actionButtonSize.width - actionAndSettingsButtonsSpacing
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.semibold(16.0), textColor: component.theme.rootController.navigationBar.primaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
||||
)
|
||||
//TODO:localize
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.managesChat ? "bot manages this chat" : "bot has access to this chat", font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxTextWidth, height: 100.0)
|
||||
)
|
||||
|
||||
let size = CGSize(width: availableSize.width, height: topInset + titleSize.height + titleTextSpacing + textSize.height + bottomInset)
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset + avatarDiameter + avatarTextSpacing, y: topInset), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.layer.anchorPoint = CGPoint()
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size)
|
||||
transition.setPosition(view: titleView, position: titleFrame.origin)
|
||||
}
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleTextSpacing), size: textSize)
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
textView.layer.anchorPoint = CGPoint()
|
||||
self.addSubview(textView)
|
||||
}
|
||||
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
|
||||
transition.setPosition(view: textView, position: textFrame.origin)
|
||||
}
|
||||
|
||||
let avatarFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - avatarDiameter) * 0.5)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
|
||||
let avatarNode: AvatarNode
|
||||
if let current = self.avatarNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
|
||||
self.avatarNode = avatarNode
|
||||
self.addSubview(avatarNode.view)
|
||||
}
|
||||
avatarNode.frame = avatarFrame
|
||||
avatarNode.updateSize(size: avatarFrame.size)
|
||||
avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer)
|
||||
|
||||
let settingsButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - settingsButtonSize.width, y: floor((size.height - settingsButtonSize.height) * 0.5)), size: settingsButtonSize)
|
||||
if let settingsButtonView = self.settingsButton.view {
|
||||
if settingsButtonView.superview == nil {
|
||||
self.addSubview(settingsButtonView)
|
||||
}
|
||||
transition.setFrame(view: settingsButtonView, frame: settingsButtonFrame)
|
||||
}
|
||||
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: settingsButtonFrame.minX - actionAndSettingsButtonsSpacing - actionButtonSize.width, y: floor((size.height - actionButtonSize.height) * 0.5)), size: actionButtonSize)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
if actionButtonView.superview == nil {
|
||||
self.addSubview(actionButtonView)
|
||||
}
|
||||
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
||||
transition.setAlpha(view: actionButtonView, alpha: component.managesChat ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let context: AccountContext
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let content = ComponentView<Empty>()
|
||||
|
||||
private var chatLocation: ChatLocation?
|
||||
private var theme: PresentationTheme?
|
||||
private var managingBot: ChatManagingBot?
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
}
|
||||
|
||||
private func toggleIsPaused() {
|
||||
guard let chatPeerId = self.chatLocation?.peerId else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = self.context.engine.peers.toggleChatManagingBotIsPaused(chatId: chatPeerId)
|
||||
}
|
||||
|
||||
private func openSettingsMenu(sourceView: UIView) {
|
||||
guard let interfaceInteraction = self.interfaceInteraction else {
|
||||
return
|
||||
}
|
||||
guard let chatController = interfaceInteraction.chatController() else {
|
||||
return
|
||||
}
|
||||
guard let chatPeerId = self.chatLocation?.peerId else {
|
||||
return
|
||||
}
|
||||
guard let managingBot = self.managingBot else {
|
||||
return
|
||||
}
|
||||
|
||||
let strings = self.context.sharedContext.currentPresentationData.with { $0 }.strings
|
||||
let _ = strings
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Remove bot from this chat", textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.context.engine.peers.removeChatManagingBot(chatId: chatPeerId)
|
||||
})))
|
||||
if let url = managingBot.settingsUrl {
|
||||
items.append(.action(ContextMenuActionItem(text: "Manage Bot", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url, skipUrlAuth: false)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let chatController = interfaceInteraction.chatController() else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.openResolvedUrl(
|
||||
result,
|
||||
context: self.context,
|
||||
urlContext: .generic,
|
||||
navigationController: chatController.navigationController as? NavigationController,
|
||||
forceExternal: false,
|
||||
openPeer: { [weak self] peer, navigation in
|
||||
guard let self, let chatController = interfaceInteraction.chatController() else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = chatController.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
switch navigation {
|
||||
case let .chat(_, subject, peekData):
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: subject, peekData: peekData))
|
||||
case let .withBotStartPayload(botStart):
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botStart: botStart, keepStack: .always))
|
||||
case let .withAttachBot(attachBotStart):
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), attachBotStart: attachBotStart))
|
||||
case let .withBotApp(botAppStart):
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botAppStart: botAppStart))
|
||||
case .info:
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer, let chatController = interfaceInteraction.chatController() else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = chatController.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
if let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
sendFile: nil,
|
||||
sendSticker: nil,
|
||||
requestMessageActionUrlAuth: nil,
|
||||
joinVoiceChat: nil,
|
||||
present: { [weak chatController] c, a in
|
||||
chatController?.present(c, in: .window(.root), with: a)
|
||||
},
|
||||
dismissInput: {
|
||||
},
|
||||
contentContext: nil,
|
||||
progress: nil,
|
||||
completion: nil
|
||||
)
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: chatController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil)
|
||||
interfaceInteraction.presentController(contextController, nil)
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
|
||||
self.chatLocation = interfaceState.chatLocation
|
||||
self.managingBot = interfaceState.contactStatus?.managingBot
|
||||
|
||||
if interfaceState.theme !== self.theme {
|
||||
self.theme = interfaceState.theme
|
||||
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
|
||||
if let managingBot = interfaceState.contactStatus?.managingBot {
|
||||
let contentSize = self.content.update(
|
||||
transition: Transition(transition),
|
||||
component: AnyComponent(ChatManagingBotTitlePanelComponent(
|
||||
context: self.context,
|
||||
theme: interfaceState.theme,
|
||||
strings: interfaceState.strings,
|
||||
insets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: 0.0, right: rightInset),
|
||||
peer: managingBot.bot,
|
||||
managesChat: managingBot.canReply,
|
||||
isPaused: managingBot.isPaused,
|
||||
toggleIsPaused: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.toggleIsPaused()
|
||||
},
|
||||
openSettings: { [weak self] sourceView in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openSettingsMenu(sourceView: sourceView)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width, height: 1000.0)
|
||||
)
|
||||
if let contentView = self.content.view {
|
||||
if contentView.superview == nil {
|
||||
self.view.addSubview(contentView)
|
||||
}
|
||||
transition.updateFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: contentSize))
|
||||
}
|
||||
|
||||
return LayoutResult(backgroundHeight: contentSize.height, insetHeight: contentSize.height, hitTestSlop: 0.0)
|
||||
} else {
|
||||
return LayoutResult(backgroundHeight: 0.0, insetHeight: 0.0, hitTestSlop: 0.0)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceView: UIView
|
||||
|
||||
init(controller: ViewController, sourceView: UIView) {
|
||||
self.controller = controller
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
@ -1074,8 +1074,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.requestBiometryAuth()
|
||||
case "web_app_biometry_update_token":
|
||||
var tokenData: Data?
|
||||
if let json, let tokenDataValue = json["token"] as? Data {
|
||||
tokenData = tokenDataValue
|
||||
if let json, let tokenDataValue = json["token"] as? String, !tokenDataValue.isEmpty {
|
||||
tokenData = tokenDataValue.data(using: .utf8)
|
||||
}
|
||||
self.requestBiometryUpdateToken(tokenData: tokenData)
|
||||
default:
|
||||
@ -1514,10 +1514,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
||||
|
||||
Thread { [weak self] in
|
||||
var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
if key == nil {
|
||||
key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
}
|
||||
let key = LocalAuth.getOrCreatePrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
|
||||
let decryptedData: LocalAuth.DecryptionResult
|
||||
if let key {
|
||||
@ -1567,9 +1564,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
data["status"] = isAuthorized ? "authorized" : "failed"
|
||||
if isAuthorized {
|
||||
if let tokenData {
|
||||
data["token"] = tokenData
|
||||
data["token"] = String(data: tokenData, encoding: .utf8) ?? ""
|
||||
} else {
|
||||
data["token"] = Data()
|
||||
data["token"] = ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -1593,10 +1590,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
if let tokenData {
|
||||
let appBundleId = self.context.sharedContext.applicationBindings.appBundleId
|
||||
Thread { [weak self] in
|
||||
var key = LocalAuth.getPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
if key == nil {
|
||||
key = LocalAuth.addPrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
}
|
||||
let key = LocalAuth.getOrCreatePrivateKey(baseAppBundleId: appBundleId, keyId: keyId)
|
||||
|
||||
var encryptedData: TelegramBotBiometricsState.OpaqueToken?
|
||||
if let key {
|
||||
@ -1619,6 +1613,28 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
state.opaqueToken = encryptedData
|
||||
return state
|
||||
})
|
||||
|
||||
var data: [String: Any] = [:]
|
||||
data["status"] = "updated"
|
||||
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||
return
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||
} else {
|
||||
var data: [String: Any] = [:]
|
||||
data["status"] = "failed"
|
||||
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||
return
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
@ -1628,6 +1644,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
state.opaqueToken = nil
|
||||
return state
|
||||
})
|
||||
|
||||
var data: [String: Any] = [:]
|
||||
data["status"] = "removed"
|
||||
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: data) else {
|
||||
return
|
||||
}
|
||||
guard let jsonDataString = String(data: jsonData, encoding: .utf8) else {
|
||||
return
|
||||
}
|
||||
self.webView?.sendEvent(name: "biometry_token_updated", data: jsonDataString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user