diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 589bbbca27..a04da00b0f 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -225,6 +225,7 @@ public struct ResolvedBotAdminRights: OptionSet { public static let manageVideoChats = ResolvedBotAdminRights(rawValue: 512) public static let canBeAnonymous = ResolvedBotAdminRights(rawValue: 1024) public static let manageChat = ResolvedBotAdminRights(rawValue: 2048) + public static let manageTopics = ResolvedBotAdminRights(rawValue: 4096) public var chatAdminRights: TelegramChatAdminRightsFlags? { var flags = TelegramChatAdminRightsFlags() @@ -259,6 +260,9 @@ public struct ResolvedBotAdminRights: OptionSet { if self.contains(ResolvedBotAdminRights.canBeAnonymous) { flags.insert(.canBeAnonymous) } + if self.contains(ResolvedBotAdminRights.manageTopics) { + flags.insert(.canManageTopics) + } if flags.isEmpty && !self.contains(ResolvedBotAdminRights.manageChat) { return nil diff --git a/submodules/Components/PagerComponent/Sources/PagerComponent.swift b/submodules/Components/PagerComponent/Sources/PagerComponent.swift index 31f38da7cd..5d480fd29f 100644 --- a/submodules/Components/PagerComponent/Sources/PagerComponent.swift +++ b/submodules/Components/PagerComponent/Sources/PagerComponent.swift @@ -1103,6 +1103,27 @@ public final class PagerComponent View { diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index f8fbf6c94e..207c6e8e76 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -437,7 +437,7 @@ public class ContactsController: ViewController { strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) default: let presentationData = strongSelf.presentationData - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { self?.context.sharedContext.applicationBindings.openSettings() })]), in: .window(.root)) strongSelf.contactsNode.contactListNode.listNode.clearHighlightAnimated(true) @@ -787,7 +787,7 @@ public class ContactsController: ViewController { default: let presentationData = strongSelf.presentationData if let navigationController = strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController, let topController = navigationController.topViewController as? ViewController { - topController.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + topController.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { self?.context.sharedContext.applicationBindings.openSettings() })]), in: .window(.root)) } diff --git a/submodules/DeviceAccess/Sources/DeviceAccess.swift b/submodules/DeviceAccess/Sources/DeviceAccess.swift index 625bd10f41..5ac46798f5 100644 --- a/submodules/DeviceAccess/Sources/DeviceAccess.swift +++ b/submodules/DeviceAccess/Sources/DeviceAccess.swift @@ -336,7 +336,7 @@ public final class DeviceAccess { case .ageVerification: text = presentationData.strings.AccessDenied_AgeVerificationCamera } - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -363,7 +363,7 @@ public final class DeviceAccess { text = presentationData.strings.AccessDenied_AgeVerificationCamera } } - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -392,7 +392,7 @@ public final class DeviceAccess { case .voiceCall: text = presentationData.strings.AccessDenied_CallMicrophone } - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) if case .voiceCall = microphoneSubject { @@ -421,7 +421,7 @@ public final class DeviceAccess { case .qrCode: text = presentationData.strings.AccessDenied_QrCode } - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -462,7 +462,7 @@ public final class DeviceAccess { completion(false) if let presentationData = presentationData { let text = presentationData.strings.AccessDenied_LocationPreciseDenied - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -477,7 +477,7 @@ public final class DeviceAccess { completion(false) if let presentationData = presentationData { let text = presentationData.strings.AccessDenied_LocationAlwaysDenied - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -498,7 +498,7 @@ public final class DeviceAccess { } else { text = presentationData.strings.AccessDenied_LocationDisabled } - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } @@ -558,7 +558,7 @@ public final class DeviceAccess { } case .cellularData: if let presentationData = presentationData { - present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Permissions_CellularDataTitle_v0, text: presentationData.strings.Permissions_CellularDataText_v0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Permissions_CellularDataTitle_v0, text: presentationData.strings.Permissions_CellularDataText_v0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { openSettings() })]), nil) } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index c7b6ffae17..12a60b459f 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -3923,21 +3923,35 @@ public func avatarMediaPickerController( final class PickerDelegate: NSObject, PHPickerViewControllerDelegate { var completion: ((Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void)? - func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - picker.dismiss(animated: true) - - for item in results { - if item.itemProvider.canLoadObject(ofClass: UIImage.self) { - item.itemProvider.loadObject(ofClass: UIImage.self) { image, error in - if let uiImage = image as? UIImage { - Queue.mainQueue().async { - self.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {}) - } + private func resolveResult(_ result: PHPickerResult) { + if let assetIdentifier = result.assetIdentifier { + let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [assetIdentifier], options: nil) + if let asset = fetchResult.firstObject { + Queue.mainQueue().async { + self.completion?(asset, nil, CGRect(), nil, false, { _ in return nil }, {}) + } + return + } + } + + if result.itemProvider.canLoadObject(ofClass: UIImage.self) { + result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, _ in + if let uiImage = image as? UIImage { + Queue.mainQueue().async { + self?.completion?(uiImage, nil, CGRect(), nil, false, { _ in return nil }, {}) } } } } } + + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + picker.dismiss(animated: true) + + if let result = results.first { + self.resolveResult(result) + } + } } let holder = PickerDelegate() @@ -3945,7 +3959,7 @@ public func avatarMediaPickerController( let openMediaPicker = { var configuration = PHPickerConfiguration(photoLibrary: .shared()) - configuration.filter = .images + configuration.filter = .any(of: [.images, .videos]) configuration.selectionLimit = 1 let picker = PHPickerViewController(configuration: configuration) diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift index e592965d63..e433428491 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminController.swift @@ -673,6 +673,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s .direct(.canManageRanks), .direct(.canPinMessages), .direct(.canManageTopics), + .sub(.stories, storiesRelatedFlags), .direct(.canManageCalls), .direct(.canBeAnonymous), .direct(.canAddAdmins) @@ -1553,6 +1554,7 @@ public func channelAdminController(context: AccountContext, updatedPresentationD dismissImpl?() }, completed: { + updated(TelegramChatAdminRights(rights: updateFlags)) dismissImpl?() })) } else if updateFlags != defaultFlags || updateRank != nil { diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 0b8208a0d2..689da92243 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -478,6 +478,7 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net var mediaMessageCount = 0 var consumedText = false + var captionAssigned = false for item in items { switch item { case let .text(text): @@ -493,11 +494,22 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net case let .media(media): switch media { case let .media(reference): + let captionText: String + if !captionAssigned { + if let file = reference.media as? TelegramMediaFile, file.isInstantVideo { + captionText = "" + } else { + captionText = additionalText + captionAssigned = true + } + } else { + captionText = "" + } var message = StandaloneSendEnqueueMessage( content: .arbitraryMedia( media: reference, text: StandaloneSendEnqueueMessage.Text( - string: additionalText, + string: captionText, entities: [] ) ), diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index c02be139c0..00176110eb 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -864,7 +864,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, } } - if threadId == nil, let peer = transaction.getPeer(peerId), (peer is TelegramChannel), peer.isForum { + if threadId == nil, let peer = transaction.getPeer(peerId), (peer is TelegramChannel), peer.isForum { threadId = 1 } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index c79d57b6d2..2cf6b25d25 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -217,6 +217,53 @@ public func standaloneSendEnqueueMessages( } } if allDone { + if peerId.namespace == Namespaces.Peer.SecretChat { + return postbox.transaction { transaction -> Signal in + var state = transaction.getPeerChatState(peerId) as? SecretChatState + + for (content, media, attributes) in allResults { + var text: String = "" + switch content.content { + case let .text(textValue): + text = textValue + case let .media(_, textValue): + text = textValue + default: + break + } + + if let currentState = state, let updatedState = enqueueSecretChatUploadedMessageContent( + transaction: transaction, + peerId: peerId, + state: currentState, + content: content, + text: text, + attributes: attributes, + media: media + ) { + state = updatedState + } else { + return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none)) + } + } + + return managedSecretChatOutgoingOperations( + auxiliaryMethods: auxiliaryMethods, + postbox: postbox, + network: network, + accountPeerId: accountPeerId, + mode: .standaloneComplete(peerId: peerId) + ) + |> castError(StandaloneSendMessagesError.self) + |> ignoreValues + } + |> castError(StandaloneSendMessagesError.self) + |> switchToLatest + |> map { _ -> StandaloneSendMessageStatus in + } + |> then(.single(.done)) + } + var sendSignals: [Signal] = [] for (content, media, attributes) in allResults { @@ -231,11 +278,10 @@ public func standaloneSendEnqueueMessages( } sendSignals.append(sendUploadedMessageContent( - auxiliaryMethods: auxiliaryMethods, postbox: postbox, network: network, stateManager: stateManager, - accountPeerId: stateManager.accountPeerId, + accountPeerId: accountPeerId, peerId: peerId, content: content, text: text, @@ -256,8 +302,52 @@ public func standaloneSendEnqueueMessages( } } +private func enqueueSecretChatUploadedMessageContent( + transaction: Transaction, + peerId: PeerId, + state: SecretChatState, + content: PendingMessageUploadedContentAndReuploadInfo, + text: String, + attributes: [MessageAttribute], + media: [Media] +) -> SecretChatState? { + var secretFile: SecretChatOutgoingFile? + switch content.content { + case let .secretMedia(file, size, key): + if let fileReference = SecretChatOutgoingFileReference(file) { + secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key) + } + default: + break + } + + let layer: SecretChatLayer + switch state.embeddedState { + case .terminated, .handshake: + return nil + case .basicLayer: + layer = .layer8 + case let .sequenceBasedLayer(sequenceState): + layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer + } + + let messageContents = StandaloneSecretMessageContents( + id: Int64.random(in: Int64.min ... Int64.max), + text: text, + attributes: attributes, + media: media.first, + file: secretFile + ) + + let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state) + if updatedState != state { + transaction.setPeerChatState(peerId, state: updatedState) + } + + return updatedState +} + private func sendUploadedMessageContent( - auxiliaryMethods: AccountAuxiliaryMethods, postbox: Postbox, network: Network, stateManager: AccountStateManager, @@ -271,55 +361,7 @@ private func sendUploadedMessageContent( ) -> Signal { return postbox.transaction { transaction -> Signal in if peerId.namespace == Namespaces.Peer.SecretChat { - var secretFile: SecretChatOutgoingFile? - switch content.content { - case let .secretMedia(file, size, key): - if let fileReference = SecretChatOutgoingFileReference(file) { - secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key) - } - default: - break - } - - var layer: SecretChatLayer? - let state = transaction.getPeerChatState(peerId) as? SecretChatState - if let state = state { - switch state.embeddedState { - case .terminated, .handshake: - break - case .basicLayer: - layer = .layer8 - case let .sequenceBasedLayer(sequenceState): - layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer - } - } - - if let state = state, let layer = layer { - let messageContents = StandaloneSecretMessageContents( - id: Int64.random(in: Int64.min ... Int64.max), - text: text, - attributes: attributes, - media: media.first, - file: secretFile - ) - - let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: peerId, operation: .sendStandaloneMessage(layer: layer, contents: messageContents), state: state) - if updatedState != state { - transaction.setPeerChatState(peerId, state: updatedState) - } - - return managedSecretChatOutgoingOperations( - auxiliaryMethods: auxiliaryMethods, - postbox: postbox, - network: network, - accountPeerId: accountPeerId, - mode: .standaloneComplete(peerId: peerId) - ) - |> castError(StandaloneSendMessagesError.self) - |> ignoreValues - } else { - return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none)) - } + return .fail(StandaloneSendMessagesError(peerId: peerId, reason: .none)) } else if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { var uniqueId: Int64 = 0 var forwardSourceInfoAttribute: ForwardSourceInfoAttribute? diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 0434b0e286..061e15daf7 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -1038,10 +1038,22 @@ public final class PendingMessageManager { var flags: Int32 = 0 + var topMsgId: Int32? + var monoforumPeerId: Api.InputPeer? + if let threadId = messages[0].0.threadId { + if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) + } + } else { + topMsgId = Int32(clamping: threadId) + } + } + for attribute in messages[0].0.attributes { if let replyAttribute = attribute as? ReplyMessageAttribute { replyMessageId = replyAttribute.messageId.id - if peerId != replyAttribute.messageId.peerId { + if peerId != replyAttribute.messageId.peerId || (replyAttribute.threadMessageId != nil && replyAttribute.threadMessageId?.id != topMsgId) { replyPeerId = replyAttribute.messageId.peerId } if replyAttribute.isQuote { @@ -1134,19 +1146,10 @@ public final class PendingMessageManager { } } - var topMsgId: Int32? - var monoforumPeerId: Api.InputPeer? - if let threadId = messages[0].0.threadId { - if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) { - if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { - monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) - } - } else { - flags |= Int32(1 << 9) - topMsgId = Int32(clamping: threadId) - } + if topMsgId != nil { + flags |= Int32(1 << 9) } - + var quickReplyShortcut: Api.InputQuickReplyShortcut? if let quickReply { if let threadId = messages[0].0.threadId { @@ -1233,20 +1236,7 @@ public final class PendingMessageManager { if bubbleUpEmojiOrStickersets { flags |= Int32(1 << 15) } - - var topMsgId: Int32? - var monoforumPeerId: Api.InputPeer? - if let threadId = messages[0].0.threadId { - if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) { - if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { - monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) - } - } else { - flags |= Int32(1 << 9) - topMsgId = Int32(clamping: threadId) - } - } - + var replyTo: Api.InputReplyTo? if let replyMessageId = replyMessageId { flags |= 1 << 0 @@ -1571,7 +1561,7 @@ public final class PendingMessageManager { for attribute in message.attributes { if let replyAttribute = attribute as? ReplyMessageAttribute { replyMessageId = replyAttribute.messageId.id - if peer.id != replyAttribute.messageId.peerId { + if peer.id != replyAttribute.messageId.peerId || (replyAttribute.threadMessageId != nil && replyAttribute.threadMessageId?.id != topMsgId) { replyPeerId = replyAttribute.messageId.peerId } if replyAttribute.isQuote { @@ -1851,19 +1841,9 @@ public final class PendingMessageManager { sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, scheduleRepeatPeriod: scheduleRepeatPeriod, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, effect: messageEffectId, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) |> map(NetworkRequestResult.result) case let .forward(sourceInfo): - var topMsgId: Int32? - var monoforumPeerId: Api.InputPeer? - if let threadId = message.threadId { - if let channel = peer as? TelegramChannel, channel.flags.contains(.isMonoforum) { - if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = transaction.getPeer(linkedMonoforumId) as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { - monoforumPeerId = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) - } - } else { - flags |= Int32(1 << 9) - topMsgId = Int32(clamping: threadId) - } + if topMsgId != nil { + flags |= Int32(1 << 9) } - var quickReplyShortcut: Api.InputQuickReplyShortcut? if let quickReply { if let threadId = message.threadId { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 333178e1de..8b404a767d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -1803,7 +1803,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { guard let textSelectionNode = self.textSelectionNode else { return nil } - guard let range = customRange ?? textSelectionNode.getSelection() else { + guard let rawRange = customRange ?? textSelectionNode.getSelection() else { return nil } guard let item = self.item else { @@ -1813,6 +1813,17 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { return nil } + func normalizedSelectionRange(_ range: NSRange, length: Int) -> NSRange { + let location = min(max(range.location, 0), length) + let upperBound = min(max(location, range.location + range.length), length) + return NSRange(location: location, length: upperBound - location) + } + + let range = normalizedSelectionRange(rawRange, length: string.length) + guard range.length > 0 else { + return nil + } + let nsString = string.string as NSString let substring = nsString.substring(with: range) let offset = range.location diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 3b4e28b601..82f83f0bb4 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -794,10 +794,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { if let emojiAttribute = emojiAttribute { AudioServicesPlaySystemSound(0x450) interaction.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])) + (strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View)?.revealHiddenPanels() } } else if case let .staticEmoji(staticEmoji) = item.content { AudioServicesPlaySystemSound(0x450) interaction.insertText(NSAttributedString(string: staticEmoji, attributes: [:])) + (strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View)?.revealHiddenPanels() } }) }, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift index c525591808..bb904a961c 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboard.swift @@ -1005,6 +1005,14 @@ public final class EntityKeyboardComponent: Component { pagerView.collapseTopPanel() } + public func revealHiddenPanels() { + guard let pagerView = self.pagerView.findTaggedView(tag: PagerComponentViewTag()) as? PagerComponent.View else { + return + } + + pagerView.revealHiddenPanels() + } + private func reorderPacks(category: ReorderCategory, items: [EntityKeyboardTopPanelComponent.Item]) { self.component?.reorderItems(category, items) } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift index 0c83494846..a7e42e6bca 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreenAvatarSetup.swift @@ -666,7 +666,11 @@ extension PeerInfoScreenImpl { } if mainController is ActionSheetController { - self.present(mainController, in: .window(.root)) + if let navigationController = self.navigationController, let topController = navigationController.topViewController as? ViewController { + topController.present(mainController, in: .window(.root)) + } else { + self.present(mainController, in: .window(.root)) + } } else { mainController.navigationPresentation = .flatModal mainController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 17a7f17b57..59da17e4f0 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -443,35 +443,60 @@ final class AuthorizedApplicationContext { return false } - - if let minimizedContainer = strongSelf.rootController.minimizedContainer, minimizedContainer.isExpanded { - minimizedContainer.collapse() - } else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController { - topContoller.minimizeIfNeeded() - } else if let topContoller = strongSelf.rootController.topViewController as? BrowserScreen { - topContoller.requestMinimize(topEdgeOffset: nil, initialVelocity: nil) + + let proceedAction: (Bool) -> Bool = { allowExpansion in + if let minimizedContainer = strongSelf.rootController.minimizedContainer, minimizedContainer.isExpanded { + minimizedContainer.collapse() + } else if let topContoller = strongSelf.rootController.topViewController as? AttachmentController { + topContoller.minimizeIfNeeded() + } else if let topContoller = strongSelf.rootController.topViewController as? BrowserScreen { + topContoller.requestMinimize(topEdgeOffset: nil, initialVelocity: nil) + } + + for controller in strongSelf.rootController.viewControllers { + if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) { + if allowExpansion { + return true + } else { + strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId) + + let chatController = ChatControllerImpl(context: strongSelf.context, chatLocation: chatLocation.asChatLocation, mode: .overlay(strongSelf.rootController)) + let presentationArguments = ChatControllerOverlayPresentationData(expandData: (nil, {})) + chatController.presentationArguments = presentationArguments + (strongSelf.rootController.viewControllers.last as? ViewController)?.present(chatController, in: .window(.root), with: presentationArguments) + return false + } + } + } + + strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId) + + var processed = false + for media in firstMessage.media { + if let action = media as? TelegramMediaAction, case .geoProximityReached = action.action { + strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController) + processed = true + break + } + } + + if !processed { + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: chatLocation)) + } + + return false } - - for controller in strongSelf.rootController.viewControllers { - if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) { - return true + + if let topController = strongSelf.rootController.topViewController as? ChatControllerImpl { + let didPresentAlert = topController.presentVoiceMessageDiscardAlert(action: { + let _ = proceedAction(false) + }, discardIfVideo: true, performAction: false) + if didPresentAlert { + return false } } - strongSelf.notificationController.removeItemsWithGroupingKey(firstMessage.id.peerId) - - var processed = false - for media in firstMessage.media { - if let action = media as? TelegramMediaAction, case .geoProximityReached = action.action { - strongSelf.context.sharedContext.openLocationScreen(context: strongSelf.context, messageId: firstMessage.id, navigationController: strongSelf.rootController) - processed = true - break - } - } - - if !processed { - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: chatLocation)) - } + return proceedAction(true) } return false }, expandAction: { expandData in diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index fc854dfdb9..a6ce9ab27f 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -27,6 +27,17 @@ private enum OptionsId: Hashable { case link } +private func chatLocationMatchesDestination(_ chatLocation: ChatLocation, peerId: EnginePeer.Id, threadId: Int64?) -> Bool { + switch chatLocation { + case let .peer(id): + return id == peerId && threadId == nil + case let .replyThread(replyThreadMessage): + return replyThreadMessage.peerId == peerId && replyThreadMessage.threadId == threadId + case .customChatContents: + return false + } +} + private func presentChatInputOptions(selfController: ChatControllerImpl, sourceView: UIView, initialId: OptionsId) { var getContextController: (() -> ContextController?)? @@ -623,14 +634,13 @@ func moveReplyMessageToAnotherChat(selfController: ChatControllerImpl, replySubj return } let peerId = peer.id - //let accountPeerId = selfController.context.account.peerId var isPinnedMessages = false if case .pinnedMessages = selfController.presentationInterfaceState.subject { isPinnedMessages = true } - if case .peer(peerId) = selfController.chatLocation, selfController.parentController == nil, !isPinnedMessages { + if chatLocationMatchesDestination(selfController.chatLocation, peerId: peerId, threadId: threadId), selfController.parentController == nil, !isPinnedMessages { selfController.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageSubject(replySubject).withoutSelectionState() }).updatedSearch(nil) }) selfController.updateItemNodesSearchTextHighlightStates() selfController.searchResultsController = nil @@ -651,7 +661,7 @@ func moveReplyToChat(selfController: ChatControllerImpl, peerId: EnginePeer.Id, if let navigationController = selfController.effectiveNavigationController { for controller in navigationController.viewControllers { if let maybeChat = controller as? ChatControllerImpl { - if case .peer(peerId) = maybeChat.chatLocation { + if chatLocationMatchesDestination(maybeChat.chatLocation, peerId: peerId, threadId: threadId) { var isChatPinnedMessages = false if case .pinnedMessages = maybeChat.presentationInterfaceState.subject { isChatPinnedMessages = true diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 57293d2fef..692ce77bed 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10304,21 +10304,35 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(controller, in: .window(.root)) } - func presentVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool { - if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState { + func presentVoiceMessageDiscardAlert( + action: @escaping () -> Void = {}, + alertAction: (() -> Void)? = nil, + discardIfVideo: Bool = false, + delay: Bool = false, + performAction: Bool = true + ) -> Bool { + if let mediaRecordingState = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState { + var discard = false + if discardIfVideo, case .video = mediaRecordingState { + discard = true + } alertAction?() Queue.mainQueue().after(delay ? 0.2 : 0.0) { let alertController = textAlertController( context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, - text: self.presentationData.strings.Conversation_StopVoiceMessageDescription, + text: discard ? self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription : self.presentationData.strings.Conversation_StopVoiceMessageDescription, actions: [ TextAlertAction( type: .defaultAction, - title: self.presentationData.strings.Conversation_StopVoiceMessagePauseAction, + title: discard ? self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction : self.presentationData.strings.Conversation_StopVoiceMessagePauseAction, action: { [weak self] in - self?.stopMediaRecorder(pause: true) + if discard { + self?.dismissMediaRecorder(.dismiss) + } else { + self?.stopMediaRecorder(pause: true) + } Queue.mainQueue().after(0.1) { action() } @@ -10343,26 +10357,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } return false } - - func presentRecordedVoiceMessageDiscardAlert(action: @escaping () -> Void = {}, alertAction: (() -> Void)? = nil, delay: Bool = false, performAction: Bool = true) -> Bool { - if let _ = self.presentationInterfaceState.interfaceState.mediaDraftState { - alertAction?() - Queue.mainQueue().after(delay ? 0.2 : 0.0) { - self.present(textAlertController(context: self.context, updatedPresentationData: self.updatedPresentationData, title: nil, text: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageDescription, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_DiscardRecordedVoiceMessageAction, action: { [weak self] in - self?.stopMediaRecorder() - Queue.mainQueue().after(0.1) { - action() - } - })]), in: .window(.root)) - } - - return true - } else if performAction { - action() - } - return false - } - + func presentAutoremoveSetup() { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return diff --git a/submodules/TelegramUI/Sources/ComposeController.swift b/submodules/TelegramUI/Sources/ComposeController.swift index e5788e4079..cf07ca09e6 100644 --- a/submodules/TelegramUI/Sources/ComposeController.swift +++ b/submodules/TelegramUI/Sources/ComposeController.swift @@ -226,7 +226,7 @@ public class ComposeControllerImpl: ViewController, ComposeController { DeviceAccess.authorizeAccess(to: .contacts) default: let presentationData = strongSelf.presentationData - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { self?.context.sharedContext.applicationBindings.openSettings() })]), in: .window(.root)) } diff --git a/submodules/TelegramUI/Sources/OpenAddContact.swift b/submodules/TelegramUI/Sources/OpenAddContact.swift index d71ffc1abe..c09f9d5066 100644 --- a/submodules/TelegramUI/Sources/OpenAddContact.swift +++ b/submodules/TelegramUI/Sources/OpenAddContact.swift @@ -39,7 +39,7 @@ func openAddContactImpl(context: AccountContext, peer: EnginePeer?, firstName: S DeviceAccess.authorizeAccess(to: .contacts) default: let presentationData = context.sharedContext.currentPresentationData.with { $0 } - present(textAlertController(context: context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(textAlertController(context: context, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { context.sharedContext.applicationBindings.openSettings() })]), nil) } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 2231f78cbd..2ce5bdb88b 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -120,6 +120,15 @@ func openResolvedUrlImpl( } ) } + let isStartGroup: Bool + switch peerType { + case .group?: + isStartGroup = true + default: + isStartGroup = false + } + let shouldForceStartGroupStartFlow = isStartGroup && !payload.isEmpty && adminRights == nil + let shouldCheckExistingGroupAdmin = isStartGroup && !payload.isEmpty && adminRights != nil var filter: ChatListNodePeersFilter = [.onlyGroupsAndChannels, .onlyManageable, .excludeDisabled, .excludeRecent, .doNotSearchMessages] var title: String = presentationData.strings.Bot_AddToChat_Title @@ -198,42 +207,100 @@ func openResolvedUrlImpl( } }), TextAlertAction(type: .genericAction, title: strings.Common_Cancel, action: {}) - ] + ], + actionLayout: .vertical ) present(alertController, nil) } + let openAdminControllerImpl: (TelegramChatAdminRightsFlags?) -> Void = { initialAdminRights in + let adminController = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in + if shouldCheckExistingGroupAdmin { + Queue.mainQueue().after(0.1) { + addMemberImpl() + } + } else { + controller?.dismiss() + } + }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) + navigationController?.pushViewController(adminController) + } + let openAdminControllerWithResolvedRightsImpl: (Bool) -> Void = { isGroup in + if adminRights == nil { + let _ = (defaultAdminRights.get() + |> take(1) + |> deliverOnMainQueue).start(next: { defaultAdminRights in + let initialAdminRights = isGroup ? defaultAdminRights?.group?.rights : defaultAdminRights?.channel?.rights + openAdminControllerImpl(initialAdminRights) + }) + } else { + openAdminControllerImpl(adminRights?.chatAdminRights) + } + } if case let .channel(peer) = peer { var isGroup = false if case .group = peer.info { isGroup = true } - if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canAddAdmins) == true { - let _ = (defaultAdminRights.get() - |> take(1) - |> deliverOnMainQueue).start(next: { defaultAdminRights in - let initialAdminRights = adminRights?.chatAdminRights ?? (isGroup ? defaultAdminRights?.group?.rights : defaultAdminRights?.channel?.rights) - let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in - controller?.dismiss() - }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) - navigationController?.pushViewController(controller) - }) + if shouldForceStartGroupStartFlow { + addMemberImpl() + } else if peer.flags.contains(.isCreator) || peer.adminRights?.rights.contains(.canAddAdmins) == true { + if shouldCheckExistingGroupAdmin && isGroup { + let _ = (context.engine.peers.fetchChannelParticipant(peerId: peerId, participantId: botPeerId) + |> deliverOnMainQueue).start(next: { participant in + let isBotAlreadyAdmin: Bool + if let participant = participant { + switch participant { + case .creator: + isBotAlreadyAdmin = true + case let .member(_, _, adminInfo, _, _, _): + isBotAlreadyAdmin = adminInfo != nil + } + } else { + isBotAlreadyAdmin = false + } + if isBotAlreadyAdmin { + addMemberImpl() + } else { + openAdminControllerWithResolvedRightsImpl(isGroup) + } + }) + } else { + openAdminControllerWithResolvedRightsImpl(isGroup) + } } else { addMemberImpl() } } else if case let .legacyGroup(peer) = peer { - if case .member = peer.role { + if shouldForceStartGroupStartFlow { addMemberImpl() - } else { - let _ = (defaultAdminRights.get() - |> take(1) - |> deliverOnMainQueue).start(next: { defaultAdminRights in - let initialAdminRights = adminRights?.chatAdminRights ?? defaultAdminRights?.group?.rights - let controller = channelAdminController(context: context, peerId: peerId, adminId: botPeerId, initialParticipant: nil, invite: true, initialAdminRights: initialAdminRights, updated: { _ in - controller?.dismiss() - }, upgradedToSupergroup: { _, _ in }, transferedOwnership: { _ in }) - navigationController?.pushViewController(controller) + } else if case .member = peer.role { + addMemberImpl() + } else if shouldCheckExistingGroupAdmin { + let _ = (context.engine.peers.fetchAndUpdateCachedPeerData(peerId: peerId) + |> mapToSignal { _ in + context.engine.data.get(TelegramEngine.EngineData.Item.Peer.LegacyGroupParticipants(id: peerId)) + } + |> deliverOnMainQueue).start(next: { participants in + let isBotAlreadyAdmin: Bool + if let participant = participants.knownValue?.first(where: { $0.peerId == botPeerId }) { + switch participant { + case .creator, .admin: + isBotAlreadyAdmin = true + case .member: + isBotAlreadyAdmin = false + } + } else { + isBotAlreadyAdmin = false + } + if isBotAlreadyAdmin { + addMemberImpl() + } else { + openAdminControllerWithResolvedRightsImpl(true) + } }) + } else { + openAdminControllerWithResolvedRightsImpl(true) } } } @@ -788,7 +855,7 @@ func openResolvedUrlImpl( navigationController?.pushViewController(controller) default: let presentationData = context.sharedContext.currentPresentationData.with { $0 } - present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: { + present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: presentationData.strings.AccessDenied_Title, text: presentationData.strings.Contacts_AccessDeniedError, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.AccessDenied_Settings, action: { context.sharedContext.applicationBindings.openSettings() })]), nil) } diff --git a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift index a779d44732..9754b3e8dd 100644 --- a/submodules/TextSelectionNode/Sources/TextSelectionNode.swift +++ b/submodules/TextSelectionNode/Sources/TextSelectionNode.swift @@ -531,7 +531,13 @@ public final class TextSelectionNode: ASDisplayNode { }) } - return adjustedRange + func normalizedSelectionRange(_ range: NSRange, length: Int) -> NSRange { + let location = min(max(range.location, 0), length) + let upperBound = min(max(location, range.location + range.length), length) + return NSRange(location: location, length: upperBound - location) + } + + return normalizedSelectionRange(adjustedRange, length: attributedString.length) } private func convertSelectionFromOriginalText(attributedString: NSAttributedString, range: NSRange) -> NSRange { diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 34257ee68c..3a06e074c0 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -54,6 +54,9 @@ extension ResolvedBotAdminRights { if components.contains("manage_chat") { rawValue |= ResolvedBotAdminRights.manageChat.rawValue } + if components.contains("manage_topics") { + rawValue |= ResolvedBotAdminRights.manageTopics.rawValue + } if components.contains("anonymous") { rawValue |= ResolvedBotAdminRights.canBeAnonymous.rawValue }