From 2f8fbd5fcddd7239a75e4745c7894175b453dc9c Mon Sep 17 00:00:00 2001 From: Mikhail Filimonov Date: Wed, 13 Mar 2024 16:04:07 +0300 Subject: [PATCH 1/7] added log for slow db --- submodules/Postbox/Sources/Postbox.swift | 11 +++++++++++ .../Sources/TelegramEngine/Peers/ChannelMembers.swift | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 40a62f57d8..493a22343f 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -2999,6 +2999,14 @@ final class PostboxImpl { let startTime = CFAbsoluteTimeGetCurrent() + var crashDisposable: Disposable? + #if DEBUG || BETA + crashDisposable = (Signal.single(Void()) + |> delay(0.1, queue: .concurrentDefaultQueue())).startStandalone(next: { _ in + preconditionFailure() + }) + #endif + self.valueBox.begin() let transaction = Transaction(queue: self.queue, postbox: self) self.afterBegin(transaction: transaction) @@ -3013,6 +3021,9 @@ final class PostboxImpl { postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file), on:\(line)") } + crashDisposable?.dispose() + + let _ = self.isInTransaction.swap(false) if let currentUpdatedState = self.currentUpdatedState { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift index 85899d79ae..05b6c9825c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelMembers.swift @@ -90,15 +90,19 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId: var items: [RenderedChannelParticipant] = [] switch result { case let .channelParticipants(_, participants, chats, users): + postboxLog("channel users insertion started, count: \(participants.count)") let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users) + postboxLog("channel users parsed") updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) + postboxLog("channel users postbox updated, started mapping ids") var peers: [PeerId: Peer] = [:] for id in parsedPeers.allIds { if let peer = transaction.getPeer(id) { peers[peer.id] = peer } } - + postboxLog("channel users finish mapping, started updating participants") + for participant in CachedChannelParticipants(apiParticipants: participants).participants { if let peer = parsedPeers.get(participant.peerId) { var renderedPresences: [PeerId: PeerPresence] = [:] @@ -108,6 +112,7 @@ func _internal_channelMembers(postbox: Postbox, network: Network, accountPeerId: items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presences: renderedPresences)) } } + postboxLog("channel participants finish updating") case .channelParticipantsNotModified: return nil } From 3acc53280f35745ca032da370f815eb790ee4c6b Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 13 Mar 2024 17:56:08 +0400 Subject: [PATCH 2/7] Premium features --- .../ChatPresentationInterfaceState.swift | 40 +- .../MtProtoKit/MTRequestErrorContext.h | 1 + .../Sources/MTRequestMessageService.m | 28 +- .../Sources/ShareControllerNode.swift | 2 +- submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api16.swift | 22 +- submodules/TelegramApi/Sources/Api32.swift | 31 ++ .../Sources/Account/Account.swift | 24 +- .../Sources/Network/Download.swift | 28 +- .../Sources/Network/MultipartFetch.swift | 39 +- .../Sources/Network/MultipartUpload.swift | 13 +- .../Network/MultiplexedRequestManager.swift | 18 +- .../Sources/Network/Network.swift | 100 +++- .../Settings/PeerContactSettings.swift | 65 ++- .../State/ChatHistoryPreloadManager.swift | 4 +- .../SyncCore/SyncCore_CachedChannelData.swift | 2 +- .../SyncCore/SyncCore_CachedGroupData.swift | 2 +- .../SyncCore/SyncCore_CachedUserData.swift | 2 +- .../SyncCore_PeerStatusSettings.swift | 26 +- .../SyncCore_TelegramSecretChat.swift | 2 +- .../TelegramEngine/Peers/ReportPeer.swift | 2 +- .../Peers/UpdateCachedPeerData.swift | 6 +- .../StringForMessageTimestampStatus.swift | 12 + .../StoryContentCaptionComponent.swift | 30 +- .../StoryItemSetContainerComponent.swift | 2 +- .../Contents.json | 12 + .../InlineTextRightArrow.imageset/more.pdf | 79 +++ .../Resources/Animations/anim_speed_low.tgs | Bin 0 -> 3990 bytes .../TelegramUI/Sources/ChatController.swift | 127 ++++- .../ChatInterfaceTitlePanelNodes.swift | 54 +- .../ChatManagingBotTitlePanelNode.swift | 472 ++++++++++++++++++ .../WebUI/Sources/WebAppController.swift | 8 +- 32 files changed, 1105 insertions(+), 150 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf create mode 100644 submodules/TelegramUI/Resources/Animations/anim_speed_low.tgs create mode 100644 submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift index 75406cdcf6..3cc929b696 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPresentationInterfaceState.swift @@ -231,17 +231,52 @@ public enum ChatRecordedMediaPreview: Equatable { case video(Video) } +public final class ChatManagingBot: Equatable { + public let bot: EnginePeer + public let isPaused: Bool + public let canReply: Bool + public let settingsUrl: String? + + public init(bot: EnginePeer, isPaused: Bool, canReply: Bool, settingsUrl: String?) { + self.bot = bot + self.isPaused = isPaused + self.canReply = canReply + self.settingsUrl = settingsUrl + } + + public static func ==(lhs: ChatManagingBot, rhs: ChatManagingBot) -> Bool { + if lhs === rhs { + return true + } + if lhs.bot != rhs.bot { + return false + } + if lhs.isPaused != rhs.isPaused { + return false + } + if lhs.canReply != rhs.canReply { + return false + } + if lhs.settingsUrl != rhs.settingsUrl { + return false + } + return true + } +} + public struct ChatContactStatus: Equatable { public var canAddContact: Bool public var canReportIrrelevantLocation: Bool public var peerStatusSettings: PeerStatusSettings? public var invitedBy: Peer? + public var managingBot: ChatManagingBot? - public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?) { + public init(canAddContact: Bool, canReportIrrelevantLocation: Bool, peerStatusSettings: PeerStatusSettings?, invitedBy: Peer?, managingBot: ChatManagingBot?) { self.canAddContact = canAddContact self.canReportIrrelevantLocation = canReportIrrelevantLocation self.peerStatusSettings = peerStatusSettings self.invitedBy = invitedBy + self.managingBot = managingBot } public var isEmpty: Bool { @@ -270,6 +305,9 @@ public struct ChatContactStatus: Equatable { if !arePeersEqual(lhs.invitedBy, rhs.invitedBy) { return false } + if lhs.managingBot != rhs.managingBot { + return false + } return true } } diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h index b1e9361e08..c7105cb69c 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestErrorContext.h @@ -8,6 +8,7 @@ @property (nonatomic) NSUInteger internalServerErrorCount; @property (nonatomic) NSUInteger floodWaitSeconds; +@property (nonatomic, strong) NSString *floodWaitErrorText; @property (nonatomic) bool waitingForTokenExport; @property (nonatomic, strong) id waitingForRequestToComplete; diff --git a/submodules/MtProtoKit/Sources/MTRequestMessageService.m b/submodules/MtProtoKit/Sources/MTRequestMessageService.m index 83457c3c61..a81adedb34 100644 --- a/submodules/MtProtoKit/Sources/MTRequestMessageService.m +++ b/submodules/MtProtoKit/Sources/MTRequestMessageService.m @@ -808,7 +808,7 @@ } restartRequest = true; } - else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound) { + else if (rpcError.errorCode == 420 || [rpcError.errorDescription rangeOfString:@"FLOOD_WAIT_"].location != NSNotFound || [rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) { if (request.errorContext == nil) request.errorContext = [[MTRequestErrorContext alloc] init]; @@ -821,6 +821,32 @@ if ([scanner scanInt:&errorWaitTime]) { request.errorContext.floodWaitSeconds = errorWaitTime; + request.errorContext.floodWaitErrorText = rpcError.errorDescription; + + if (request.shouldContinueExecutionWithErrorContext != nil) + { + if (request.shouldContinueExecutionWithErrorContext(request.errorContext)) + { + restartRequest = true; + request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime); + } + } + else + { + restartRequest = true; + request.errorContext.minimalExecuteTime = MAX(request.errorContext.minimalExecuteTime, MTAbsoluteSystemTime() + (CFAbsoluteTime)errorWaitTime); + } + } + } else if ([rpcError.errorDescription rangeOfString:@"FLOOD_PREMIUM_WAIT_"].location != NSNotFound) { + int errorWaitTime = 0; + + NSScanner *scanner = [[NSScanner alloc] initWithString:rpcError.errorDescription]; + [scanner scanUpToString:@"FLOOD_PREMIUM_WAIT_" intoString:nil]; + [scanner scanString:@"FLOOD_PREMIUM_WAIT_" intoString:nil]; + if ([scanner scanInt:&errorWaitTime]) + { + request.errorContext.floodWaitSeconds = errorWaitTime; + request.errorContext.floodWaitErrorText = rpcError.errorDescription; if (request.shouldContinueExecutionWithErrorContext != nil) { diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 6c3ab733c3..b3fd88b49e 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -211,7 +211,7 @@ private final class ShareContentInfoView: UIView { let accentColor = params.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0) if self.arrowIcon == nil { - if let templateImage = UIImage(bundleImageName: "Settings/TextArrowRight") { + if let templateImage = UIImage(bundleImageName: "Item List/InlineTextRightArrow") { let scaleFactor: CGFloat = 0.8 let imageSize = CGSize(width: floor(templateImage.size.width * scaleFactor), height: floor(templateImage.size.height * scaleFactor)) self.arrowIcon = generateImage(imageSize, contextGenerator: { size, context in diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 43ee98d43f..787db8d696 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -671,7 +671,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-901375139] = { return Api.PeerLocated.parse_peerLocated($0) } dict[-118740917] = { return Api.PeerLocated.parse_peerSelfLocated($0) } dict[-1721619444] = { return Api.PeerNotifySettings.parse_peerNotifySettings($0) } - dict[-1525149427] = { return Api.PeerSettings.parse_peerSettings($0) } + dict[-1395233698] = { return Api.PeerSettings.parse_peerSettings($0) } dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) } dict[-1770029977] = { return Api.PhoneCall.parse_phoneCall($0) } dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) } diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index 258955a62f..88dfb1d60c 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -898,26 +898,28 @@ public extension Api { } public extension Api { enum PeerSettings: TypeConstructorDescription { - case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?) + case peerSettings(flags: Int32, geoDistance: Int32?, requestChatTitle: String?, requestChatDate: Int32?, businessBotId: Int64?, businessBotManageUrl: String?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate): + case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl): if boxed { - buffer.appendInt32(-1525149427) + buffer.appendInt32(-1395233698) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 6) != 0 {serializeInt32(geoDistance!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {serializeString(requestChatTitle!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 9) != 0 {serializeInt32(requestChatDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {serializeInt64(businessBotId!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 13) != 0 {serializeString(businessBotManageUrl!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate): - return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any)]) + case .peerSettings(let flags, let geoDistance, let requestChatTitle, let requestChatDate, let businessBotId, let businessBotManageUrl): + return ("peerSettings", [("flags", flags as Any), ("geoDistance", geoDistance as Any), ("requestChatTitle", requestChatTitle as Any), ("requestChatDate", requestChatDate as Any), ("businessBotId", businessBotId as Any), ("businessBotManageUrl", businessBotManageUrl as Any)]) } } @@ -930,12 +932,18 @@ public extension Api { if Int(_1!) & Int(1 << 9) != 0 {_3 = parseString(reader) } var _4: Int32? if Int(_1!) & Int(1 << 9) != 0 {_4 = reader.readInt32() } + var _5: Int64? + if Int(_1!) & Int(1 << 13) != 0 {_5 = reader.readInt64() } + var _6: String? + if Int(_1!) & Int(1 << 13) != 0 {_6 = parseString(reader) } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 6) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 9) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 9) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4) + let _c5 = (Int(_1!) & Int(1 << 13) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 13) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PeerSettings.peerSettings(flags: _1!, geoDistance: _2, requestChatTitle: _3, requestChatDate: _4, businessBotId: _5, businessBotManageUrl: _6) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index a505f25c98..9338176e30 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -221,6 +221,21 @@ public extension Api.functions.account { }) } } +public extension Api.functions.account { + static func disablePeerConnectedBot(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1581481689) + peer.serialize(buffer, true) + return (FunctionDescription(name: "account.disablePeerConnectedBot", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.account { static func finishTakeoutSession(flags: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -1254,6 +1269,22 @@ public extension Api.functions.account { }) } } +public extension Api.functions.account { + static func toggleConnectedBotPaused(peer: Api.InputPeer, paused: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1684934807) + peer.serialize(buffer, true) + paused.serialize(buffer, true) + return (FunctionDescription(name: "account.toggleConnectedBotPaused", parameters: [("peer", String(describing: peer)), ("paused", String(describing: paused))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.account { static func toggleUsername(username: String, active: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index 6ed005ceef..1114aaa8bc 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -144,13 +144,13 @@ public class UnauthorizedAccount { return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in return (transaction.getSharedData(SharedDataKeys.localizationSettings)?.get(LocalizationSettings.self), transaction.getSharedData(SharedDataKeys.proxySettings)?.get(ProxySettings.self)) } - |> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?), NoError> in - return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in - return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self)) + |> mapToSignal { localizationSettings, proxySettings -> Signal<(LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration), NoError> in + return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?, AppConfiguration) in + return (localizationSettings, proxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings)?.get(NetworkSettings.self), transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? .defaultValue) } } - |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal in - return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false) + |> mapToSignal { localizationSettings, proxySettings, networkSettings, appConfiguration -> Signal in + return initializedNetwork(accountId: self.id, arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil, useRequestTimeoutTimers: false, appConfiguration: appConfiguration) |> map { network in let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) @@ -248,7 +248,7 @@ public func accountWithId(accountManager: AccountManager map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } @@ -257,7 +257,7 @@ public func accountWithId(accountManager: AccountManager mapToSignal { phoneNumber in - return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers) + return initializedNetwork(accountId: id, arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber, useRequestTimeoutTimers: useRequestTimeoutTimers, appConfiguration: appConfig) |> map { network -> AccountResult in return .authorized(Account(accountManager: accountManager, id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods, supplementary: supplementary)) } @@ -267,7 +267,7 @@ public func accountWithId(accountManager: AccountManager map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } @@ -889,6 +889,11 @@ public func accountBackupData(postbox: Postbox) -> Signal map { network -> AccountStateManager? in Logger.shared.log("StandaloneStateManager", "received network") diff --git a/submodules/TelegramCore/Sources/Network/Download.swift b/submodules/TelegramCore/Sources/Network/Download.swift index 2ebe6cff22..7966a99445 100644 --- a/submodules/TelegramCore/Sources/Network/Download.swift +++ b/submodules/TelegramCore/Sources/Network/Download.swift @@ -103,7 +103,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { self.context.authTokenForDatacenter(withIdRequired: self.datacenterId, authToken:self.mtProto.requiredAuthToken, masterDatacenterId: self.mtProto.authTokenMasterDatacenterId) } - static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal { + static func uploadPart(multiplexedManager: MultiplexedRequestManager, datacenterId: Int, consumerId: Int64, tag: MediaResourceFetchTag?, fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal { let saveFilePart: (FunctionDescription, Buffer, DeserializeFunctionResponse) if asBigPart { let totalParts: Int32 @@ -117,7 +117,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { saveFilePart = Api.functions.upload.saveFilePart(fileId: fileId, filePart: Int32(index), bytes: Buffer(data: data)) } - return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, expectedResponseSize: nil) + return multiplexedManager.request(to: .main(datacenterId), consumerId: consumerId, resourceId: nil, data: wrapMethodBody(saveFilePart, useCompression: useCompression), tag: tag, continueInBackground: true, onFloodWaitError: onFloodWaitError, expectedResponseSize: nil) |> mapError { error -> UploadPartError in if error.errorCode == 400 { return .invalidMedia @@ -130,7 +130,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } } - func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false) -> Signal { + func uploadPart(fileId: Int64, index: Int, data: Data, asBigPart: Bool, bigTotalParts: Int? = nil, useCompression: Bool = false, onFloodWaitError: ((String) -> Void)? = nil) -> Signal { return Signal { subscriber in let request = MTRequest() @@ -159,6 +159,13 @@ class Download: NSObject, MTRequestMessageServiceDelegate { request.dependsOnPasswordEntry = false request.shouldContinueExecutionWithErrorContext = { errorContext in + guard let errorContext = errorContext else { + return true + } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } + return true } @@ -295,7 +302,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { |> retryRequest } - func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true) -> Signal { + func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), expectedResponseSize: Int32? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal { return Signal { subscriber in let request = MTRequest() request.expectedResponseSize = expectedResponseSize ?? 0 @@ -314,6 +321,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } @@ -344,7 +354,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } } - func requestWithAdditionalData(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> { + func requestWithAdditionalData(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, expectedResponseSize: Int32? = nil) -> Signal<(T, Double), (MTRpcError, Double)> { return Signal { subscriber in let request = MTRequest() request.expectedResponseSize = expectedResponseSize ?? 0 @@ -363,6 +373,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } @@ -396,7 +409,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } } - func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> { + func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, failOnServerErrors: Bool = false, logPrefix: String = "", expectedResponseSize: Int32? = nil) -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() @@ -416,6 +429,9 @@ class Download: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } diff --git a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift index 5c9a473a3a..83d0c4b40c 100644 --- a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift +++ b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift @@ -104,14 +104,14 @@ private struct DownloadWrapper { self.useMainConnection = useMainConnection } - func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), MTRpcError> { + func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, expectedResponseSize: Int32?, onFloodWaitError: @escaping (String) -> Void) -> Signal<(T, NetworkResponseInfo), MTRpcError> { let target: MultiplexedRequestTarget if self.isCdn { target = .cdn(Int(self.datacenterId)) } else { target = .main(Int(self.datacenterId)) } - return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, expectedResponseSize: expectedResponseSize) + return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, resourceId: self.resourceId, data: data, tag: tag, continueInBackground: continueInBackground, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize) |> mapError { error, _ -> MTRpcError in return error } @@ -192,7 +192,7 @@ private final class MultipartCdnHashSource { clusterContext = ClusterContext(disposable: disposable) self.clusterContexts[offset] = clusterContext - disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil) + disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground, expectedResponseSize: nil, onFloodWaitError: { _ in }) |> map { partHashes, _ -> [Int64: Data] in var parsedPartHashes: [Int64: Data] = [:] for part in partHashes { @@ -322,7 +322,7 @@ private enum MultipartFetchSource { } } - func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> { + func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool, onFloodWaitError: @escaping (String) -> Void) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> { var resourceReferenceValue: MediaResourceReference? switch resourceReference { case .forceRevalidate: @@ -348,7 +348,9 @@ private enum MultipartFetchSource { case .revalidate: return .fail(.revalidateMediaReference) case let .location(parsedLocation): - return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit)) + return download.request(Api.functions.upload.getFile(flags: 0, location: parsedLocation, offset: offset, limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in + onFloodWaitError(error) + }) |> mapError { error -> MultipartFetchDownloadError in if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { return .revalidateMediaReference @@ -380,7 +382,9 @@ private enum MultipartFetchSource { } } case let .web(_, location): - return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit)) + return download.request(Api.functions.upload.getWebFile(location: location, offset: Int32(offset), limit: Int32(limit)), tag: tag, continueInBackground: continueInBackground, expectedResponseSize: Int32(limit), onFloodWaitError: { error in + onFloodWaitError(error) + }) |> mapError { error -> MultipartFetchDownloadError in if error.errorDescription == "WEBFILE_NOT_AVAILABLE" { return .webfileNotAvailable @@ -404,7 +408,9 @@ private enum MultipartFetchSource { updatedLength += 1 } - let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength)) + let part = download.request(Api.functions.upload.getCdnFile(fileToken: Buffer(data: fileToken), offset: offset, limit: Int32(updatedLength)), tag: nil, continueInBackground: continueInBackground, expectedResponseSize: Int32(updatedLength), onFloodWaitError: { error in + onFloodWaitError(error) + }) |> mapError { _ -> MultipartFetchDownloadError in return .generic } @@ -723,6 +729,13 @@ private final class MultipartFetchManager { } } + + private func processFloodWaitError(error: String) { + if error.hasPrefix("FLOOD_PREMIUM_WAIT") { + self.network.addNetworkSpeedLimitedEvent(event: .download) + } + } + func checkState() { guard let currentIntervals = self.currentIntervals else { return @@ -836,7 +849,15 @@ private final class MultipartFetchManager { } let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound) - let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground) + let queue = self.queue + let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground, onFloodWaitError: { [weak self] error in + queue.async { + guard let self else { + return + } + self.processFloodWaitError(error: error) + } + }) |> deliverOn(self.queue) let partDisposable = MetaDisposable() self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable) @@ -919,7 +940,7 @@ private final class MultipartFetchManager { case let .cdn(_, _, fileToken, _, _, _, masterDownload, _): if !strongSelf.reuploadingToCdn { strongSelf.reuploadingToCdn = true - let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil) + let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground, expectedResponseSize: nil, onFloodWaitError: { _ in }) |> map { result, _ -> [Api.FileHash] in return result } diff --git a/submodules/TelegramCore/Sources/Network/MultipartUpload.swift b/submodules/TelegramCore/Sources/Network/MultipartUpload.swift index 95331fcf9f..3f07e3bb5e 100644 --- a/submodules/TelegramCore/Sources/Network/MultipartUpload.swift +++ b/submodules/TelegramCore/Sources/Network/MultipartUpload.swift @@ -470,12 +470,21 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload fetchedResource = .complete() } + let onFloodWaitError: (String) -> Void = { [weak network] error in + guard let network else { + return + } + if error.hasPrefix("FLOOD_PREMIUM_WAIT") { + network.addNetworkSpeedLimitedEvent(event: .upload) + } + } + let manager = MultipartUploadManager(headerSize: headerSize, data: dataSignal, encryptionKey: encryptionKey, hintFileSize: hintFileSize, hintFileIsLarge: hintFileIsLarge, forceNoBigParts: forceNoBigParts, useLargerParts: useLargerParts, increaseParallelParts: increaseParallelParts, uploadPart: { part in switch uploadInterface { case let .download(download): - return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression) + return download.uploadPart(fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError) case let .multiplexed(multiplexed, datacenterId, consumerId): - return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression) + return Download.uploadPart(multiplexedManager: multiplexed, datacenterId: datacenterId, consumerId: consumerId, tag: nil, fileId: part.fileId, index: part.index, data: part.data, asBigPart: part.bigPart, bigTotalParts: part.bigTotalParts, useCompression: useCompression, onFloodWaitError: onFloodWaitError) } }, progress: { progress in subscriber.putNext(.progress(progress)) diff --git a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift index 7e1a846480..1172b3c739 100644 --- a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift +++ b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift @@ -33,12 +33,13 @@ private final class RequestData { let tag: MediaResourceFetchTag? let continueInBackground: Bool let automaticFloodWait: Bool + let onFloodWaitError: ((String) -> Void)? let expectedResponseSize: Int32? let deserializeResponse: (Buffer) -> Any? let completed: (Any, NetworkResponseInfo) -> Void let error: (MTRpcError, Double) -> Void - init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) { + init(id: Int32, consumerId: Int64, resourceId: String?, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, onFloodWaitError: ((String) -> Void)?, expectedResponseSize: Int32?, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) { self.id = id self.consumerId = consumerId self.resourceId = resourceId @@ -47,6 +48,7 @@ private final class RequestData { self.tag = tag self.continueInBackground = continueInBackground self.automaticFloodWait = automaticFloodWait + self.onFloodWaitError = onFloodWaitError self.expectedResponseSize = expectedResponseSize self.payload = payload self.deserializeResponse = deserializeResponse @@ -155,12 +157,12 @@ private final class MultiplexedRequestManagerContext { } } - func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable { + func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable { let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground) let requestId = self.nextId self.nextId += 1 - self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in + self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, resourceId: resourceId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, deserializeResponse: { buffer in return data.2(buffer) }, completed: { result, info in completed(result, info) @@ -254,7 +256,7 @@ private final class MultiplexedRequestManagerContext { let requestId = request.id selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable)) let queue = self.queue - disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in + disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait, onFloodWaitError: request.onFloodWaitError, expectedResponseSize: request.expectedResponseSize).start(next: { [weak self, weak selectedContext] result, info in queue.async { guard let strongSelf = self else { return @@ -354,13 +356,13 @@ final class MultiplexedRequestManager { return disposable } - func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal { + func request(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() self.context.with { context in disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in return data.2.parse(buffer) - }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, _ in + }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, _ in if let result = result as? T { subscriber.putNext(result) subscriber.putCompletion() @@ -375,13 +377,13 @@ final class MultiplexedRequestManager { } } - func requestWithAdditionalInfo(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> { + func requestWithAdditionalInfo(to target: MultiplexedRequestTarget, consumerId: Int64, resourceId: String?, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil, expectedResponseSize: Int32?) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> { return Signal { subscriber in let disposable = MetaDisposable() self.context.with { context in disposable.set(context.request(to: target, consumerId: consumerId, resourceId: resourceId, data: (data.0, data.1, { buffer in return data.2.parse(buffer) - }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, expectedResponseSize: expectedResponseSize, completed: { result, info in + }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, onFloodWaitError: onFloodWaitError, expectedResponseSize: expectedResponseSize, completed: { result, info in if let result = result as? T { subscriber.putNext((result, info)) subscriber.putCompletion() diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift index 2914559d88..4d82d66fba 100644 --- a/submodules/TelegramCore/Sources/Network/Network.swift +++ b/submodules/TelegramCore/Sources/Network/Network.swift @@ -459,7 +459,7 @@ public struct NetworkInitializationArguments { private let cloudDataContext = Atomic(value: nil) #endif -func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool) -> Signal { +func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializationArguments, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool, languageCode: String?, proxySettings: ProxySettings?, networkSettings: NetworkSettings?, phoneNumber: String?, useRequestTimeoutTimers: Bool, appConfiguration: AppConfiguration) -> Signal { return Signal { subscriber in let queue = Queue() queue.async { @@ -612,6 +612,11 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa let useExperimentalFeatures = networkSettings?.useExperimentalDownload ?? false let network = Network(queue: queue, datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath, appDataDisposable: appDataDisposable, encryptionProvider: arguments.encryptionProvider, useRequestTimeoutTimers: useRequestTimeoutTimers, useBetaFeatures: arguments.useBetaFeatures, useExperimentalFeatures: useExperimentalFeatures) + + if let data = appConfiguration.data, let notifyInterval = data["upload_premium_speedup_notify_period"] as? Double { + network.updateNetworkSpeedLimitedEventNotifyInterval(value: notifyInterval) + } + appDataUpdatedImpl = { [weak network] data in guard let data = data else { return @@ -734,6 +739,22 @@ public enum NetworkRequestResult { case progress(Float, Int32) } +private final class NetworkSpeedLimitedEventState { + var notifyInterval: Double = 60.0 * 60.0 + var lastNotifyTimestamp: Double = 0.0 + + func add(event: NetworkSpeedLimitedEvent) -> Bool { + let timestamp = CFAbsoluteTimeGetCurrent() + + if self.lastNotifyTimestamp + self.notifyInterval < timestamp { + self.lastNotifyTimestamp = timestamp + return true + } else { + return false + } + } +} + public final class Network: NSObject, MTRequestMessageServiceDelegate { public let encryptionProvider: EncryptionProvider @@ -766,6 +787,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { return self._connectionStatus.get() |> distinctUntilChanged } + public var networkSpeedLimitedEvents: Signal { + return self.networkSpeedLimitedEventPipe.signal() + } + private let networkSpeedLimitedEventPipe = ValuePipe() + private let networkSpeedLimitedEventState = Atomic(value: NetworkSpeedLimitedEventState()) + public func dropConnectionStatus() { _connectionStatus.set(.single(.waitingForNetwork)) } @@ -826,18 +853,18 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { let array = NSMutableArray() if let result = result { switch result { - case let .cdnConfig(publicKeys): - for key in publicKeys { - switch key { - case let .cdnPublicKey(dcId, publicKey): - if id == Int(dcId) { - let dict = NSMutableDictionary() - dict["key"] = publicKey - dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey) - array.add(dict) - } + case let .cdnConfig(publicKeys): + for key in publicKeys { + switch key { + case let .cdnPublicKey(dcId, publicKey): + if id == Int(dcId) { + let dict = NSMutableDictionary() + dict["key"] = publicKey + dict["fingerprint"] = MTRsaFingerprint(encryptionProvider, publicKey) + array.add(dict) } } + } } } return array @@ -867,12 +894,12 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { let isCdn: Bool let isMedia: Bool = true switch target { - case let .main(id): - datacenterId = id - isCdn = false - case let .cdn(id): - datacenterId = id - isCdn = true + case let .main(id): + datacenterId = id + isCdn = false + case let .cdn(id): + datacenterId = id + isCdn = true } return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground) } @@ -880,7 +907,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { }) let shouldKeepConnectionSignal = self.shouldKeepConnection.get() - |> distinctUntilChanged |> deliverOn(queue) + |> distinctUntilChanged |> deliverOn(queue) self.shouldKeepConnectionDisposable.set(shouldKeepConnectionSignal.start(next: { [weak self] value in if let strongSelf = self { if value { @@ -967,11 +994,11 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { self.context.addAddressForDatacenter(withId: Int(datacenterId), address: address) /*let currentScheme = self.context.transportSchemeForDatacenter(withId: Int(datacenterId), media: false, isProxy: false) - if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) { - } else { - let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false) - self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false) - }*/ + if let currentScheme = currentScheme, currentScheme.address.isEqual(to: address) { + } else { + let scheme = MTTransportScheme(transport: MTTcpTransport.self, address: address, media: false) + self.context.updateTransportSchemeForDatacenter(withId: Int(datacenterId), transportScheme: scheme, media: false, isProxy: false) + }*/ let currentSchemes = self.context.transportSchemesForDatacenter(withId: Int(datacenterId), media: false, enforceMedia: false, isProxy: false) var found = false @@ -988,7 +1015,7 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { } } - public func requestWithAdditionalInfo(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal, MTRpcError> { + public func requestWithAdditionalInfo(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), info: NetworkRequestAdditionalInfo, tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal, MTRpcError> { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() @@ -1006,6 +1033,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } @@ -1056,8 +1086,8 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { } } } - - public func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true) -> Signal { + + public func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: NetworkRequestDependencyTag? = nil, automaticFloodWait: Bool = true, onFloodWaitError: ((String) -> Void)? = nil) -> Signal { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() @@ -1075,6 +1105,9 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { guard let errorContext = errorContext else { return true } + if let onFloodWaitError, errorContext.floodWaitSeconds > 0, let errorText = errorContext.floodWaitErrorText { + onFloodWaitError(errorText) + } if errorContext.floodWaitSeconds > 0 && !automaticFloodWait { return false } @@ -1113,6 +1146,21 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate { } } } + + func updateNetworkSpeedLimitedEventNotifyInterval(value: Double) { + let _ = self.networkSpeedLimitedEventState.with { state in + state.notifyInterval = value + } + } + + func addNetworkSpeedLimitedEvent(event: NetworkSpeedLimitedEvent) { + let notify = self.networkSpeedLimitedEventState.with { state in + return state.add(event: event) + } + if notify { + self.networkSpeedLimitedEventPipe.putNext(event) + } + } } public func retryRequest(signal: Signal) -> Signal { diff --git a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift index c421160341..2333ad61f0 100644 --- a/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift +++ b/submodules/TelegramCore/Sources/Settings/PeerContactSettings.swift @@ -6,33 +6,44 @@ import SwiftSignalKit extension PeerStatusSettings { init(apiSettings: Api.PeerSettings) { switch apiSettings { - case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate): - var result = PeerStatusSettings.Flags() - if (flags & (1 << 1)) != 0 { - result.insert(.canAddContact) - } - if (flags & (1 << 0)) != 0 { - result.insert(.canReport) - } - if (flags & (1 << 2)) != 0 { - result.insert(.canBlock) - } - if (flags & (1 << 3)) != 0 { - result.insert(.canShareContact) - } - if (flags & (1 << 4)) != 0 { - result.insert(.addExceptionWhenAddingContact) - } - if (flags & (1 << 5)) != 0 { - result.insert(.canReportIrrelevantGeoLocation) - } - if (flags & (1 << 7)) != 0 { - result.insert(.autoArchived) - } - if (flags & (1 << 8)) != 0 { - result.insert(.suggestAddMembers) - } - self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0) + case let .peerSettings(flags, geoDistance, requestChatTitle, requestChatDate, businessBotId, businessBotManageUrl): + var result = PeerStatusSettings.Flags() + if (flags & (1 << 1)) != 0 { + result.insert(.canAddContact) + } + if (flags & (1 << 0)) != 0 { + result.insert(.canReport) + } + if (flags & (1 << 2)) != 0 { + result.insert(.canBlock) + } + if (flags & (1 << 3)) != 0 { + result.insert(.canShareContact) + } + if (flags & (1 << 4)) != 0 { + result.insert(.addExceptionWhenAddingContact) + } + if (flags & (1 << 5)) != 0 { + result.insert(.canReportIrrelevantGeoLocation) + } + if (flags & (1 << 7)) != 0 { + result.insert(.autoArchived) + } + if (flags & (1 << 8)) != 0 { + result.insert(.suggestAddMembers) + } + + var managingBot: ManagingBot? + if let businessBotId { + managingBot = ManagingBot( + id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(businessBotId)), + manageUrl: businessBotManageUrl, + isPaused: (flags & (1 << 11)) != 0, + canReply: (flags & (1 << 12)) != 0 + ) + } + + self = PeerStatusSettings(flags: result, geoDistance: geoDistance, requestChatTitle: requestChatTitle, requestChatDate: requestChatDate, requestChatIsChannel: (flags & (1 << 10)) != 0, managingBot: managingBot) } } } diff --git a/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift b/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift index acd550c523..37e1406e1f 100644 --- a/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift +++ b/submodules/TelegramCore/Sources/State/ChatHistoryPreloadManager.swift @@ -360,11 +360,11 @@ final class ChatHistoryPreloadManager { guard let strongSelf = self else { return } - #if DEBUG + /*#if DEBUG if "".isEmpty { return } - #endif + #endif*/ var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = [] for item in loadItems { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index cfef517df3..7c8fac5dd5 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -551,7 +551,7 @@ public final class CachedChannelData: CachedPeerData { var peerIds = Set() if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift index 5959310a0b..eb8391bec0 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedGroupData.swift @@ -203,7 +203,7 @@ public final class CachedGroupData: CachedPeerData { self.exportedInvitation = decoder.decode(ExportedInvitation.self, forKey: "i") self.botInfos = decoder.decodeObjectArrayWithDecoderForKey("b") as [CachedPeerBotInfo] if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 5e9a6f657b..e6cb200972 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -558,7 +558,7 @@ public final class CachedUserData: CachedPeerData { self.botInfo = decoder.decodeObjectForKey("bi") as? BotInfo self.editableBotInfo = decoder.decodeObjectForKey("ebi") as? EditableBotInfo if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift index ed6ca78314..d12b6e71cb 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PeerStatusSettings.swift @@ -16,7 +16,20 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { public static let canReportIrrelevantGeoLocation = Flags(rawValue: 1 << 6) public static let autoArchived = Flags(rawValue: 1 << 7) public static let suggestAddMembers = Flags(rawValue: 1 << 8) - + } + + public struct ManagingBot: Codable, Equatable { + public var id: PeerId + public var manageUrl: String? + public var isPaused: Bool + public var canReply: Bool + + public init(id: PeerId, manageUrl: String?, isPaused: Bool, canReply: Bool) { + self.id = id + self.manageUrl = manageUrl + self.isPaused = isPaused + self.canReply = canReply + } } public var flags: PeerStatusSettings.Flags @@ -24,20 +37,23 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { public var requestChatTitle: String? public var requestChatDate: Int32? public var requestChatIsChannel: Bool? + public var managingBot: ManagingBot? public init() { self.flags = PeerStatusSettings.Flags() self.geoDistance = nil self.requestChatTitle = nil self.requestChatDate = nil + self.managingBot = nil } - public init(flags: PeerStatusSettings.Flags, geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil) { + public init(flags: PeerStatusSettings.Flags, geoDistance: Int32? = nil, requestChatTitle: String? = nil, requestChatDate: Int32? = nil, requestChatIsChannel: Bool? = nil, managingBot: ManagingBot?) { self.flags = flags self.geoDistance = geoDistance self.requestChatTitle = requestChatTitle self.requestChatDate = requestChatDate self.requestChatIsChannel = requestChatIsChannel + self.managingBot = managingBot } public init(decoder: PostboxDecoder) { @@ -46,6 +62,7 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { self.requestChatTitle = decoder.decodeOptionalStringForKey("requestChatTitle") self.requestChatDate = decoder.decodeOptionalInt32ForKey("requestChatDate") self.requestChatIsChannel = decoder.decodeOptionalBoolForKey("requestChatIsChannel") + self.managingBot = decoder.decodeCodable(ManagingBot.self, forKey: "managingBot") } public func encode(_ encoder: PostboxEncoder) { @@ -70,6 +87,11 @@ public struct PeerStatusSettings: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "requestChatIsChannel") } + if let managingBot = self.managingBot { + encoder.encodeCodable(managingBot, forKey: "managingBot") + } else { + encoder.encodeNil(forKey: "managingBot") + } } public func contains(_ member: PeerStatusSettings.Flags) -> Bool { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift index 78150dc1d7..be745fbed8 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramSecretChat.swift @@ -93,7 +93,7 @@ public final class CachedSecretChatData: CachedPeerData { public init(decoder: PostboxDecoder) { if let legacyValue = decoder.decodeOptionalInt32ForKey("pcs") { - self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil) + self.peerStatusSettings = PeerStatusSettings(flags: PeerStatusSettings.Flags(rawValue: legacyValue), geoDistance: nil, managingBot: nil) } else if let peerStatusSettings = decoder.decodeObjectForKey("pss", decoder: { PeerStatusSettings(decoder: $0) }) as? PeerStatusSettings { self.peerStatusSettings = peerStatusSettings } else { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift index 1deae7bbed..04ffbec511 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift @@ -214,7 +214,7 @@ func _internal_dismissPeerStatusOptions(account: Account, peerId: PeerId) -> Sig if let current = current as? CachedUserData { var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings() peerStatusSettings.flags = [] - return current.withUpdatedPeerStatusSettings(PeerStatusSettings(flags: [])) + return current.withUpdatedPeerStatusSettings(peerStatusSettings) } else if let current = current as? CachedGroupData { var peerStatusSettings = current.peerStatusSettings ?? PeerStatusSettings() peerStatusSettings.flags = [] diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 775b5d7c2e..df655e5377 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -56,12 +56,12 @@ func fetchAndUpdateSupplementalCachedPeerData(peerId rawPeerId: PeerId, accountP var peerStatusSettings: PeerStatusSettings if let peer = transaction.getPeer(peer.id), let associatedPeerId = peer.associatedPeerId, !transaction.isPeerContact(peerId: associatedPeerId) { if let peer = peer as? TelegramSecretChat, case .creator = peer.role { - peerStatusSettings = PeerStatusSettings(flags: []) + peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil) } else { - peerStatusSettings = PeerStatusSettings(flags: [.canReport]) + peerStatusSettings = PeerStatusSettings(flags: [.canReport], managingBot: nil) } } else { - peerStatusSettings = PeerStatusSettings(flags: []) + peerStatusSettings = PeerStatusSettings(flags: [], managingBot: nil) } transaction.updatePeerCachedData(peerIds: [peer.id], update: { peerId, current in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift index 69bc656b17..ef3ac7d6ff 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift @@ -156,6 +156,18 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess } } + if authorTitle == nil { + for attribute in message.attributes { + if let attribute = attribute as? InlineBusinessBotMessageAttribute { + if let title = attribute.title { + authorTitle = title + } else if let peerId = attribute.peerId, let peer = message.peers[peerId] { + authorTitle = peer.debugDisplayTitle + } + } + } + } + if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info { authorTitle = nil } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index 42c75a2a00..7bc8d4a842 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -209,8 +209,31 @@ final class StoryContentCaptionComponent: Component { override init(frame: CGRect) { self.shadowGradientView = UIImageView() - if let image = StoryContentCaptionComponent.View.shadowImage { - self.shadowGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0)) + if let _ = StoryContentCaptionComponent.View.shadowImage { + let height: CGFloat = 128.0 + let baseGradientAlpha: CGFloat = 0.8 + let numSteps = 8 + let firstStep = 0 + let firstLocation = 0.0 + let colors = (0 ..< numSteps).map { i -> UIColor in + if i < firstStep { + return UIColor(white: 1.0, alpha: 1.0) + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step) + return UIColor(white: 0.0, alpha: baseGradientAlpha * value) + } + } + let locations = (0 ..< numSteps).map { i -> CGFloat in + if i < firstStep { + return 0.0 + } else { + let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1) + return (firstLocation + (1.0 - firstLocation) * step) + } + } + + self.shadowGradientView.image = generateGradientImage(size: CGSize(width: 8.0, height: height), colors: colors.reversed(), locations: locations.reversed().map { 1.0 - $0 })!.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(height - 1.0)) } self.scrollViewContainer = UIView() @@ -386,7 +409,8 @@ final class StoryContentCaptionComponent: Component { transition.setBounds(view: self.textSelectionKnobContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: self.scrollView.bounds.minY), size: CGSize())) - let shadowOverflow: CGFloat = 58.0 + let shadowHeight: CGFloat = self.shadowGradientView.image?.size.height ?? 100.0 + let shadowOverflow: CGFloat = floor(shadowHeight * 0.6) let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow)) let shadowGradientFrame = CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.minY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0)) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 26853ef250..ab245eef4b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -2690,7 +2690,7 @@ public final class StoryItemSetContainerComponent: Component { self.bottomContentGradientLayer.colors = colors self.bottomContentGradientLayer.type = .axial - self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.3) + self.contentDimView.backgroundColor = UIColor(white: 0.0, alpha: 0.8) } let wasPanning = self.component?.isPanning ?? false diff --git a/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json new file mode 100644 index 0000000000..9d3f9124cb --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "more.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf b/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf new file mode 100644 index 0000000000..82da70ea58 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Item List/InlineTextRightArrow.imageset/more.pdf @@ -0,0 +1,79 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.500000 1.335754 cm +0.000000 0.000000 0.000000 scn +5.252930 4.662109 m +5.252930 4.527832 5.199219 4.409668 5.097168 4.307617 c +0.843262 0.145020 l +0.746582 0.048340 0.628418 0.000000 0.488770 0.000000 c +0.214844 0.000000 0.000000 0.209473 0.000000 0.488770 c +0.000000 0.628418 0.053711 0.746582 0.139648 0.837891 c +4.049805 4.662109 l +0.139648 8.486328 l +0.053711 8.577637 0.000000 8.701172 0.000000 8.835449 c +0.000000 9.114746 0.214844 9.324219 0.488770 9.324219 c +0.628418 9.324219 0.746582 9.275879 0.843262 9.184570 c +5.097168 5.016602 l +5.199219 4.919922 5.252930 4.796387 5.252930 4.662109 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 675 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 8.000000 12.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000765 00000 n +0000000787 00000 n +0000000959 00000 n +0000001033 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1092 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Resources/Animations/anim_speed_low.tgs b/submodules/TelegramUI/Resources/Animations/anim_speed_low.tgs new file mode 100644 index 0000000000000000000000000000000000000000..bb9b6f86c602f47c75972fa7ea2e6c417b269d44 GIT binary patch literal 3990 zcmV;H4{7ipiwFP!000021MOVdZX8Dv{1suIyBn2tm)?wB?Y;=Mu?O}+-~)F>S%P(e zr0q2X{rg5_RySvc!y!35WMKt?$MjK^RT&i-nN`%M)%j0ntEV!oeqB9Xr72B*HLPBp zuAXK$tloTFJ;jd?_>t1E`VBwC53ASx=lgef=kGs0d-mri&wu#W6TzD=U%u2=j?d0c z&e8CTqha;-`1g}j{{CsX{pS5k?ezTj)zi=$`u?BKpFjEWyYHVpM>}VydjDs<_G$Hj z-;VXgpYi?ku===h<%N%R2q_Ql2=|UR@3}#ovpZ+~j@X?~t2eyxUp_R&p?|?(81+I1 z{Nqa>E}oHGhPChg$@S}TiZSx7bM2pl_Gu~-2T8O8w>EmML;u>-E8Etuxxb+g2YpEI zL?lfiq6_bLb+g^Xu5SF$zjk!vWADa=p_M~>2t)tc(L?A1X?1D|ue$gkz2tcuerRo1 z4{7LM2lfy{9|v2%@CELqWeyFIdh6&j0sFz$#79LY7n@X0hhga7?-cS|YscH#iGA&S zcY6BaH!Pcv$EPRn&$XS66?D1>`O9Q(4KvTf>qARse-4+tazz7StAQCsV{wNzF~ej` z2U{^?V7xq#bi*cQCyyy@dw0z0)k_SOhU|9I(^#fb%aGgTfthP*>aO^lotOsi*3l{H z8g!n$KK^)OBu?P1emVu*-kh6#?+uxHc4r{*07U2 zU|oi$3WUu`|9}Z*@-%nLHO0%mAzTvWB$+yBd(w{Nd# z|D4hP<94F8JN??wG_x(U#26N7dXe1ivhM8XOOKr$U%I4b|Ks%d{n@Jzr|(vukJe^t zwG8fJI7gnhuGrd5G%Z zb|yny8U5RyE+Us$|F)}_9_d9H$sd|^UDL}Pap1$cb1>Wa?JW}gof%{u?Ca(U-hmR@ zurU)eh#^7lIps5zhlwn?ak*ce33b=K;fj_P4m2bp4#h|g3vG&^ctd8VZr*p0BC1$o z(vZ_1SR<@&Y;{|<`r+5(w|m$s-juE4CR_dO=0p{yfSk1XNqpN)R8Xo|*B4JbM50(j zPYe0mhS=l^wLAE2gUjY+0$nAVHvylj1rSJU8^+)#A1@ThHQy1y8&y#KfWvxLQ1fK_FN>xm3L`XkmWKwU~Db;s@r7cgQVj&dh46pa3Kt+{-#XiWW zd96x>exdF`=dLnIlHK91#s zB@mQIlf?TmtE3c+k>$n)v1B5!KnF=>r^PtJK!OAVxoV8oNd{4m8FsZj3C68FAFlB{ z8e#H8&#Sh=kSb%E*MVZ3snJ1RbLXi-CL>|i0bnj5FXNO{Pbb%)1g)Veio>gi>EEYv5?lfTn*X)H@RfWSb z%{U);fC7PDwG^M}KOMw^z#Xs_oD7B2pcewN1+P%bj#f0xAk3`NaZxe9n2QWB=)@-e z!UF!}EZbnB)CR3z*svX%5@CzQ*_DFSgKf@LkVnE=ycTQlT*sQOJA_HiUV=qw_!BDz z3%l7TkSr4|d(y{7!Jn0%LPdr`-vEQgUoEiZh(*`)DPRlUN&4|J*y0VqCJ=WpAlQO7 zTgTa0hWKXXfz;EO#_9kxfpoVpX(b$^!6~C(Bt~9y7E8w>$l!nzIdv~6;c3^% zfi?BJg>KUdBTR_QL?!1=YJqvF*u4-yaZ@U}2L%V&8+7Vtn4#@R5Y%KVf_Bbknw8y1 z(wb;T!Qep~XA{S6<2V~Rnw8^hrryYkPjmcerjE0%c#vEOW7W~!)^Rb`5SH>(;BAn& zSoJhjt#tsK%^BiYBgg|HVv4mxkXoY5q$vqt!jKUxokC7R#%gVpN3L0omchJf_|>7$ z;TJG=&OFjIcmkb94oEHlbVMIu%$S7B2X=At>V09X?Zp^4hwPT&&|sfaQBXMxY*iRj8AhBMb!xcCp!|cFmUPxMEnL{BLe2D5Qf-k2rGvo ztfG@VM7(LQFzO>51yY~YB8xUEjO5|IASPSN977BA0EnbZkVLf$E=axdtrIR?O*jim zjEh>z47yCN#u#m*_e^fn@4A{WE7VnnHry|Si_InCS4sq*m%88!t1b|pu^F(FibS)~ zOk6A+Sv@o!D;+{o_wpER950Nd%(DW#nK@qAnUX%)loHI&aWT}u1))&62tBA?10o|` z*0`QR2Ui4XM;2-2Ds*MJ4qZ;f&=s%ZFTt!tOp;;R1qN1%X2TXq!VlCC2O%r$+dRV9 z6Mzg(n5)&SPE(zURa2*2z3ODlh6Oc~%^{h4bB%kV<8Dl$(J0IgolZ$$b$<}T+lu^9 zW>CdwS{78nRhfqyFpDZh)Xv3BvvM(#h(SJ)dSDvO%7buarjCoP-u9E=8znU}bzE#U zU{QZ3T&A3FmPF<$>e}-iH*hxE#RE;QSDeE|!|1i=7If${tgXkhF6y>)i%rX6(3F z>oncqMvggBjU6wnRhS_w3|x$b95hFvEJc&3+8K?7b4>_yx{|>nIh;6?Lvco>MIq=^ z{Xq0L%E`d(QKjFE@n9-a(CNP~oRtln(P`YBO6Cl&++dlUDHfiO%DTCqYL9H1CSr<| zHGD9aRO^zM+WmROa~ihO>HI>B7nuweM&`V{k*p zN~OAH6&F}$l>}7OO$_SXD8>9~l=rM%Cc06IaiF*TU1@pQuEnrZ=(;}Z4sloxB;25E zv1ZNK?Hm_F83dB;*&-Du-v-9MW@)@CWUFLa2}PJPyIyT`%J8n2nD~$a(xtgD#xY9^4wey^K=uH-xr4&GfJMO z0KBXYi*Y(8YZ5j}Efv?6k-HAi$&@!eDEMBOh0`i)~QWz9$hUVFA5;4WMD(Xrx-&S3pW& zASSNdHTNAgO$Wfe&VEAl7NGfd#{Jf(M*n*H;g^p(l&cPo-uRD8)8@g^=l|CSNB0ra zzH^-1U$FwBf_=Wl6I}pyZ)yaF&*B_mpAX8kV?+JC3X*-*d2ZTA68jUM&L^T04{30rvLx| literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c366748421..81a8febaac 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -585,6 +585,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var performTextSelectionAction: ((Message?, Bool, NSAttributedString, TextSelectionAction) -> Void)? var performOpenURL: ((Message?, String, Promise?) -> Void)? + var networkSpeedEventsDisposable: Disposable? + public var alwaysShowSearchResultsAsList: Bool = false { didSet { self.presentationInterfaceState = self.presentationInterfaceState.updatedDisplayHistoryFilterAsList(self.alwaysShowSearchResultsAsList) @@ -4906,6 +4908,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } + let managingBot: Signal + if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser { + managingBot = self.context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: self.context.account.peerId) + ) + |> mapToSignal { result -> Signal in + guard let result else { + return .single(nil) + } + return context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: result.id) + ) + |> map { botPeer -> ChatManagingBot? in + guard let botPeer else { + return nil + } + + var isPaused = false + if result.recipients.exclude { + isPaused = result.recipients.additionalPeers.contains(peerId) + } else { + isPaused = !result.recipients.additionalPeers.contains(peerId) + } + + var settingsUrl: String? + if let username = botPeer.addressName { + settingsUrl = "https://t.me/\(username)" + } + + return ChatManagingBot(bot: botPeer, isPaused: isPaused, canReply: result.canReply, settingsUrl: settingsUrl) + } + } + |> distinctUntilChanged + } else { + managingBot = .single(nil) + } + do { let peerId = chatLocationPeerId if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId { @@ -5294,10 +5333,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 } @@ -5392,7 +5432,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 { @@ -5400,7 +5440,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 { @@ -5415,7 +5455,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() @@ -5518,6 +5558,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 } @@ -5538,6 +5581,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } + if let contactStatus, contactStatus.managingBot != nil { + displayActionsPanel = true + } if strongSelf.presentationInterfaceState.search != nil && hasSearchTags { displayActionsPanel = true } @@ -5871,9 +5917,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 @@ -5883,7 +5930,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 { @@ -5891,7 +5938,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 { @@ -5904,7 +5951,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() @@ -6108,6 +6155,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 { @@ -6125,6 +6175,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } } + if let contactStatus, contactStatus.managingBot != nil { + displayActionsPanel = true + } if displayActionsPanel != didDisplayActionsPanel { animated = true @@ -6796,6 +6849,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.preloadSavedMessagesChatsDisposable?.dispose() self.recorderDataDisposable.dispose() self.displaySendWhenOnlineTipDisposable.dispose() + self.networkSpeedEventsDisposable?.dispose() } deallocate() } @@ -11685,6 +11739,57 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + var lastEventTimestamp: Double = 0.0 + self.networkSpeedEventsDisposable = (self.context.account.network.networkSpeedLimitedEvents + |> deliverOnMainQueue).start(next: { [weak self] event in + guard let self else { + return + } + + let timestamp = CFAbsoluteTimeGetCurrent() + if lastEventTimestamp + 10.0 < timestamp { + lastEventTimestamp = timestamp + } else { + return + } + + //TODO:localize + let title: String + let text: String + switch event { + case .download: + var speedIncreaseFactor = 10 + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_download"] as? Double { + speedIncreaseFactor = Int(value) + } + title = "Download speed limited" + text = "Subscribe to [Telegram Premium]() and increase download speeds \(speedIncreaseFactor) times." + case .upload: + var speedIncreaseFactor = 10 + if let data = self.context.currentAppConfiguration.with({ $0 }).data, let value = data["upload_premium_speedup_upload"] as? Double { + speedIncreaseFactor = Int(value) + } + title = "Upload speed limited" + text = "Subscribe to [Telegram Premium]() and increase upload speeds \(speedIncreaseFactor) times." + } + let content: UndoOverlayContent = .universal(animation: "anim_speed_low", scale: 0.066, colors: [:], title: title, text: text, customUndoText: nil, timeout: 5.0) + + self.present(UndoOverlayController(presentationData: self.presentationData, content: content, elevatedLayout: false, position: .top, action: { [weak self] action in + guard let self else { + return false + } + switch action { + case .info: + let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .reactions, forceDark: false, dismissed: nil) + self.push(controller) + return true + default: + break + } + return false + }), in: .current) + }) + self.displayNodeDidLoad() } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index d90cc96063..694a5266a2 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -117,32 +117,44 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat } var displayActionsPanel = false - if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings { - if !peerStatusSettings.flags.isEmpty { - if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.canShareContact) { - displayActionsPanel = true - } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { - displayActionsPanel = true - } else if peerStatusSettings.contains(.suggestAddMembers) { + if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus { + if let peerStatusSettings = contactStatus.peerStatusSettings { + if !peerStatusSettings.flags.isEmpty { + if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canReport) || peerStatusSettings.contains(.canBlock) || peerStatusSettings.contains(.autoArchived) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.canShareContact) { + displayActionsPanel = true + } else if contactStatus.canReportIrrelevantLocation && peerStatusSettings.contains(.canReportIrrelevantGeoLocation) { + displayActionsPanel = true + } else if peerStatusSettings.contains(.suggestAddMembers) { + displayActionsPanel = true + } + } + if peerStatusSettings.requestChatTitle != nil { displayActionsPanel = true } } - if peerStatusSettings.requestChatTitle != nil { - displayActionsPanel = true - } } - if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) { - if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode { - return currentPanel - } else if let controllerInteraction = controllerInteraction { - let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer) - panel.interfaceInteraction = interfaceInteraction - return panel + if (selectedContext == nil || selectedContext! <= .pinnedMessage) { + if displayActionsPanel { + if let currentPanel = currentPanel as? ChatReportPeerTitlePanelNode { + return currentPanel + } else if let controllerInteraction = controllerInteraction { + let panel = ChatReportPeerTitlePanelNode(context: context, animationCache: controllerInteraction.presentationContext.animationCache, animationRenderer: controllerInteraction.presentationContext.animationRenderer) + panel.interfaceInteraction = interfaceInteraction + return panel + } + } else if !chatPresentationInterfaceState.peerIsBlocked && !inhibitTitlePanelDisplay, let contactStatus = chatPresentationInterfaceState.contactStatus, contactStatus.managingBot != nil { + if let currentPanel = currentPanel as? ChatManagingBotTitlePanelNode { + return currentPanel + } else { + let panel = ChatManagingBotTitlePanelNode(context: context) + panel.interfaceInteraction = interfaceInteraction + return panel + } } } diff --git a/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift new file mode 100644 index 0000000000..73e852e12d --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatManagingBotTitlePanelNode.swift @@ -0,0 +1,472 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import TelegramPresentationData +import ChatPresentationInterfaceState +import ComponentFlow +import AvatarNode +import MultilineTextComponent +import PlainButtonComponent +import ComponentDisplayAdapters +import AccountContext +import TelegramCore +import BundleIconComponent +import ContextUI +import SwiftSignalKit + +private final class ChatManagingBotTitlePanelComponent: Component { + let context: AccountContext + let theme: PresentationTheme + let strings: PresentationStrings + let insets: UIEdgeInsets + let peer: EnginePeer + let managesChat: Bool + let isPaused: Bool + let toggleIsPaused: () -> Void + let openSettings: (UIView) -> Void + + init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + insets: UIEdgeInsets, + peer: EnginePeer, + managesChat: Bool, + isPaused: Bool, + toggleIsPaused: @escaping () -> Void, + openSettings: @escaping (UIView) -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.insets = insets + self.peer = peer + self.managesChat = managesChat + self.isPaused = isPaused + self.toggleIsPaused = toggleIsPaused + self.openSettings = openSettings + } + + static func ==(lhs: ChatManagingBotTitlePanelComponent, rhs: ChatManagingBotTitlePanelComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings != rhs.strings { + return false + } + if lhs.insets != rhs.insets { + return false + } + if lhs.peer != rhs.peer { + return false + } + if lhs.managesChat != rhs.managesChat { + return false + } + if lhs.isPaused != rhs.isPaused { + return false + } + return true + } + + final class View: UIView { + private let title = ComponentView() + private let text = ComponentView() + private var avatarNode: AvatarNode? + private let actionButton = ComponentView() + private let settingsButton = ComponentView() + + private var component: ChatManagingBotTitlePanelComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ChatManagingBotTitlePanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let topInset: CGFloat = 6.0 + let bottomInset: CGFloat = 6.0 + let avatarDiameter: CGFloat = 36.0 + let avatarTextSpacing: CGFloat = 10.0 + let titleTextSpacing: CGFloat = 1.0 + let leftInset: CGFloat = component.insets.left + 12.0 + let rightInset: CGFloat = component.insets.right + 10.0 + let actionAndSettingsButtonsSpacing: CGFloat = 8.0 + + //TODO:localize + let actionButtonSize = self.actionButton.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.isPaused ? "START" : "STOP", font: Font.semibold(15.0), textColor: component.theme.list.itemCheckColors.foregroundColor)) + )), + background: AnyComponent(RoundedRectangle( + color: component.theme.list.itemCheckColors.fillColor, + cornerRadius: nil + )), + effectAlignment: .center, + contentInsets: UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + component.toggleIsPaused() + }, + animateAlpha: true, + animateScale: false, + animateContents: false + )), + environment: {}, + containerSize: CGSize(width: 150.0, height: 100.0) + ) + + let settingsButtonSize = self.settingsButton.update( + transition: transition, + component: AnyComponent(PlainButtonComponent( + content: AnyComponent(BundleIconComponent( + name: "Chat/Context Menu/Customize", + tintColor: component.theme.rootController.navigationBar.controlColor + )), + effectAlignment: .center, + minSize: CGSize(width: 1.0, height: 40.0), + contentInsets: UIEdgeInsets(top: 0.0, left: 2.0, bottom: 0.0, right: 2.0), + action: { [weak self] in + guard let self, let component = self.component else { + return + } + guard let settingsButtonView = self.settingsButton.view else { + return + } + component.openSettings(settingsButtonView) + }, + animateAlpha: true, + animateScale: false, + animateContents: false + )), + environment: {}, + containerSize: CGSize(width: 150.0, height: 100.0) + ) + + let maxTextWidth: CGFloat = availableSize.width - leftInset - avatarDiameter - avatarTextSpacing - rightInset - actionButtonSize.width - actionAndSettingsButtonsSpacing - settingsButtonSize.width - 8.0 + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.peer.displayTitle(strings: component.strings, displayOrder: .firstLast), font: Font.semibold(16.0), textColor: component.theme.rootController.navigationBar.primaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: maxTextWidth, height: 100.0) + ) + //TODO:localize + let textSize = self.text.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: "bot has access to this chat", font: Font.regular(15.0), textColor: component.theme.rootController.navigationBar.secondaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: maxTextWidth, height: 100.0) + ) + + let size = CGSize(width: availableSize.width, height: topInset + titleSize.height + titleTextSpacing + textSize.height + bottomInset) + + let titleFrame = CGRect(origin: CGPoint(x: leftInset + avatarDiameter + avatarTextSpacing, y: topInset), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + titleView.layer.anchorPoint = CGPoint() + self.addSubview(titleView) + } + titleView.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) + transition.setPosition(view: titleView, position: titleFrame.origin) + } + + let textFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleTextSpacing), size: textSize) + if let textView = self.text.view { + if textView.superview == nil { + textView.layer.anchorPoint = CGPoint() + self.addSubview(textView) + } + textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size) + transition.setPosition(view: textView, position: textFrame.origin) + } + + let avatarFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - avatarDiameter) * 0.5)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) + let avatarNode: AvatarNode + if let current = self.avatarNode { + avatarNode = current + } else { + avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0)) + self.avatarNode = avatarNode + self.addSubview(avatarNode.view) + } + avatarNode.frame = avatarFrame + avatarNode.updateSize(size: avatarFrame.size) + avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer) + + let settingsButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - settingsButtonSize.width, y: floor((size.height - settingsButtonSize.height) * 0.5)), size: settingsButtonSize) + if let settingsButtonView = self.settingsButton.view { + if settingsButtonView.superview == nil { + self.addSubview(settingsButtonView) + } + transition.setFrame(view: settingsButtonView, frame: settingsButtonFrame) + } + + let actionButtonFrame = CGRect(origin: CGPoint(x: settingsButtonFrame.minX - actionAndSettingsButtonsSpacing - actionButtonSize.width, y: floor((size.height - actionButtonSize.height) * 0.5)), size: actionButtonSize) + if let actionButtonView = self.actionButton.view { + if actionButtonView.superview == nil { + self.addSubview(actionButtonView) + } + transition.setFrame(view: actionButtonView, frame: actionButtonFrame) + } + + return size + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +final class ChatManagingBotTitlePanelNode: ChatTitleAccessoryPanelNode { + private let context: AccountContext + private let separatorNode: ASDisplayNode + private let content = ComponentView() + + private var chatLocation: ChatLocation? + private var theme: PresentationTheme? + private var managingBot: ChatManagingBot? + + init(context: AccountContext) { + self.context = context + self.separatorNode = ASDisplayNode() + self.separatorNode.isLayerBacked = true + + super.init() + + self.addSubnode(self.separatorNode) + } + + private func toggleIsPaused() { + guard let chatPeerId = self.chatLocation?.peerId else { + return + } + + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: self.context.account.peerId) + ) + |> deliverOnMainQueue).startStandalone(next: { [weak self] bot in + guard let self else { + return + } + guard let bot else { + return + } + + var recipients = bot.recipients + var additionalPeers = recipients.additionalPeers + if additionalPeers.contains(chatPeerId) { + additionalPeers.remove(chatPeerId) + } else { + additionalPeers.insert(chatPeerId) + } + recipients = TelegramBusinessRecipients( + categories: recipients.categories, + additionalPeers: additionalPeers, + exclude: recipients.exclude + ) + + let _ = self.context.engine.accountData.setAccountConnectedBot(bot: TelegramAccountConnectedBot( + id: bot.id, + recipients: recipients, + canReply: bot.canReply + )).startStandalone() + }) + } + + private func openSettingsMenu(sourceView: UIView) { + guard let interfaceInteraction = self.interfaceInteraction else { + return + } + guard let chatController = interfaceInteraction.chatController() else { + return + } + guard let managingBot = self.managingBot else { + return + } + let _ = managingBot + + let strings = self.context.sharedContext.currentPresentationData.with { $0 }.strings + let _ = strings + + var items: [ContextMenuItem] = [] + + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "Remove bot from this chat", textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + let _ = self + }))) + if let url = managingBot.settingsUrl { + items.append(.action(ContextMenuActionItem(text: "Manage Bot", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self else { + return + } + let _ = (self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url, skipUrlAuth: false) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self else { + return + } + guard let chatController = interfaceInteraction.chatController() else { + return + } + self.context.sharedContext.openResolvedUrl( + result, + context: self.context, + urlContext: .generic, + navigationController: chatController.navigationController as? NavigationController, + forceExternal: false, + openPeer: { [weak self] peer, navigation in + guard let self, let chatController = interfaceInteraction.chatController() else { + return + } + guard let navigationController = chatController.navigationController as? NavigationController else { + return + } + switch navigation { + case let .chat(_, subject, peekData): + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: subject, peekData: peekData)) + case let .withBotStartPayload(botStart): + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botStart: botStart, keepStack: .always)) + case let .withAttachBot(attachBotStart): + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), attachBotStart: attachBotStart)) + case let .withBotApp(botAppStart): + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), botAppStart: botAppStart)) + case .info: + let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id)) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer, let chatController = interfaceInteraction.chatController() else { + return + } + guard let navigationController = chatController.navigationController as? NavigationController else { + return + } + if let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) { + navigationController.pushViewController(controller) + } + }) + default: + break + } + }, + sendFile: nil, + sendSticker: nil, + requestMessageActionUrlAuth: nil, + joinVoiceChat: nil, + present: { [weak chatController] c, a in + chatController?.present(c, in: .window(.root), with: a) + }, + dismissInput: { + }, + contentContext: nil, + progress: nil, + completion: nil + ) + }) + }))) + } + + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let contextController = ContextController(presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: chatController, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: nil) + interfaceInteraction.presentController(contextController, nil) + } + + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult { + self.chatLocation = interfaceState.chatLocation + self.managingBot = interfaceState.contactStatus?.managingBot + + if interfaceState.theme !== self.theme { + self.theme = interfaceState.theme + + self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor + } + + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel))) + + if let managingBot = interfaceState.contactStatus?.managingBot { + let contentSize = self.content.update( + transition: Transition(transition), + component: AnyComponent(ChatManagingBotTitlePanelComponent( + context: self.context, + theme: interfaceState.theme, + strings: interfaceState.strings, + insets: UIEdgeInsets(top: 0.0, left: leftInset, bottom: 0.0, right: rightInset), + peer: managingBot.bot, + managesChat: managingBot.canReply, + isPaused: managingBot.isPaused, + toggleIsPaused: { [weak self] in + guard let self else { + return + } + self.toggleIsPaused() + }, + openSettings: { [weak self] sourceView in + guard let self else { + return + } + self.openSettingsMenu(sourceView: sourceView) + } + )), + environment: {}, + containerSize: CGSize(width: width, height: 1000.0) + ) + if let contentView = self.content.view { + if contentView.superview == nil { + self.view.addSubview(contentView) + } + transition.updateFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: contentSize)) + } + + return LayoutResult(backgroundHeight: contentSize.height, insetHeight: contentSize.height, hitTestSlop: 0.0) + } else { + return LayoutResult(backgroundHeight: 0.0, insetHeight: 0.0, hitTestSlop: 0.0) + } + + } +} + +private final class HeaderContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceView: UIView + + init(controller: ViewController, sourceView: UIView) { + self.controller = controller + self.sourceView = sourceView + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} + diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 8ee017bd74..9825157dd9 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1073,8 +1073,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 { + tokenData = tokenDataValue.data(using: .utf8) } self.requestBiometryUpdateToken(tokenData: tokenData) default: @@ -1566,9 +1566,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"] = "" } } From be1323511de0280808b2ec3aab67eee5ea4a423e Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 13 Mar 2024 18:02:52 +0400 Subject: [PATCH 3/7] Hide bots --- submodules/Postbox/Sources/Postbox.swift | 9 +++++++-- submodules/TelegramUI/Sources/ChatController.swift | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 493a22343f..5c17964f83 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -2999,13 +2999,15 @@ final class PostboxImpl { let startTime = CFAbsoluteTimeGetCurrent() - var crashDisposable: Disposable? + #if os(macOS) #if DEBUG || BETA + var crashDisposable: Disposable? crashDisposable = (Signal.single(Void()) |> delay(0.1, queue: .concurrentDefaultQueue())).startStandalone(next: { _ in preconditionFailure() }) #endif + #endif self.valueBox.begin() let transaction = Transaction(queue: self.queue, postbox: self) @@ -3021,8 +3023,11 @@ final class PostboxImpl { postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file), on:\(line)") } + #if os(macOS) + #if DEBUG || BETA crashDisposable?.dispose() - + #endif + #endif let _ = self.isInTransaction.swap(false) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 9741421f8f..aaf9c723e9 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4909,7 +4909,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } let managingBot: Signal - if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser { + if let peerId = self.chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser, !"".isEmpty { managingBot = self.context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: self.context.account.peerId) ) From dc5d9f9ca1829c78338e4b33b1a40b96a208053c Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 13 Mar 2024 19:05:17 +0400 Subject: [PATCH 4/7] Fix bot token event --- .../WebUI/Sources/WebAppController.swift | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 9825157dd9..728b03b92e 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1073,7 +1073,7 @@ 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? String { + if let json, let tokenDataValue = json["token"] as? String, !tokenDataValue.isEmpty { tokenData = tokenDataValue.data(using: .utf8) } self.requestBiometryUpdateToken(tokenData: tokenData) @@ -1618,6 +1618,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() @@ -1627,6 +1649,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) } } } From dad4e0bfd60a3358e4b4054391f3c4bdcfdc11ff Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 13 Mar 2024 23:02:21 +0400 Subject: [PATCH 5/7] Add mock local keys for simulator --- submodules/LocalAuth/Sources/LocalAuth.swift | 38 ++++++++++++++++++- .../WebUI/Sources/WebAppController.swift | 10 +---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/submodules/LocalAuth/Sources/LocalAuth.swift b/submodules/LocalAuth/Sources/LocalAuth.swift index 7796fc355e..ad67c038e6 100644 --- a/submodules/LocalAuth/Sources/LocalAuth.swift +++ b/submodules/LocalAuth/Sources/LocalAuth.swift @@ -21,6 +21,23 @@ public struct LocalAuth { case error(Error) } + #if targetEnvironment(simulator) + public final class PrivateKey { + public let publicKeyRepresentation: Data + + fileprivate init() { + self.publicKeyRepresentation = Data(count: 32) + } + + public func encrypt(data: Data) -> Data? { + return data + } + + public func decrypt(data: Data) -> DecryptionResult { + return .result(data) + } + } + #else public final class PrivateKey { private let privateKey: SecKey private let publicKey: SecKey @@ -64,6 +81,7 @@ public struct LocalAuth { return .result(result) } } + #endif public static var biometricAuthentication: LocalAuthBiometricAuthentication? { let context = LAContext() @@ -157,7 +175,18 @@ public struct LocalAuth { return seedId; } - public static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + public static func getOrCreatePrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + if let key = self.getPrivateKey(baseAppBundleId: baseAppBundleId, keyId: keyId) { + return key + } else { + return self.addPrivateKey(baseAppBundleId: baseAppBundleId, keyId: keyId) + } + } + + private static func getPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + #if targetEnvironment(simulator) + return PrivateKey() + #else guard let bundleSeedId = self.bundleSeedId() else { return nil } @@ -196,6 +225,7 @@ public struct LocalAuth { let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data) return result + #endif } public static func removePrivateKey(baseAppBundleId: String, keyId: Data) -> Bool { @@ -221,7 +251,10 @@ public struct LocalAuth { return true } - public static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + private static func addPrivateKey(baseAppBundleId: String, keyId: Data) -> PrivateKey? { + #if targetEnvironment(simulator) + return PrivateKey() + #else guard let bundleSeedId = self.bundleSeedId() else { return nil } @@ -262,5 +295,6 @@ public struct LocalAuth { let result = PrivateKey(privateKey: privateKey, publicKey: publicKey, publicKeyRepresentation: publicKeyRepresentation as Data) return result + #endif } } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 728b03b92e..5894b1e08b 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -1513,10 +1513,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 { @@ -1592,10 +1589,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 { From 5bbd363fca938feffb347e1c520dd382a4f5aabd Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 13 Mar 2024 23:02:41 +0400 Subject: [PATCH 6/7] Don't scan more than 1024 open file descriptors --- submodules/Postbox/Sources/TimeBasedCleanup.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/submodules/Postbox/Sources/TimeBasedCleanup.swift b/submodules/Postbox/Sources/TimeBasedCleanup.swift index 1c1a0be7eb..d0b9fddc61 100644 --- a/submodules/Postbox/Sources/TimeBasedCleanup.swift +++ b/submodules/Postbox/Sources/TimeBasedCleanup.swift @@ -19,8 +19,9 @@ public func printOpenFiles() { var flags: Int32 = 0 var fd: Int32 = 0 var buf = Data(count: Int(MAXPATHLEN) + 1) + let maxFd = min(1024, FD_SETSIZE) - while fd < FD_SETSIZE { + while fd < maxFd { errno = 0; flags = fcntl(fd, F_GETFD, 0); if flags == -1 && errno != 0 { From 4b7cbbea321fbc920cbd4ab3d9a6f3fe4444d722 Mon Sep 17 00:00:00 2001 From: Mikhail Filimonov Date: Thu, 14 Mar 2024 09:06:54 +0300 Subject: [PATCH 7/7] added log for slow db --- submodules/Postbox/Sources/Postbox.swift | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 5c17964f83..7c6b01b67f 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -2999,15 +2999,6 @@ final class PostboxImpl { let startTime = CFAbsoluteTimeGetCurrent() - #if os(macOS) - #if DEBUG || BETA - var crashDisposable: Disposable? - crashDisposable = (Signal.single(Void()) - |> delay(0.1, queue: .concurrentDefaultQueue())).startStandalone(next: { _ in - preconditionFailure() - }) - #endif - #endif self.valueBox.begin() let transaction = Transaction(queue: self.queue, postbox: self) @@ -3023,11 +3014,6 @@ final class PostboxImpl { postboxLog("Postbox transaction took \(transactionDuration * 1000.0) ms, from: \(file), on:\(line)") } - #if os(macOS) - #if DEBUG || BETA - crashDisposable?.dispose() - #endif - #endif let _ = self.isInTransaction.swap(false)