diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index e91427bed8..4723e91955 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -3749,20 +3749,21 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget, self.failedMessageEventsDisposable.set((self.context.account.pendingMessageManager.failedMessageEvents(peerId: peerId) |> deliverOnMainQueue).start(next: { [weak self] reason in if let strongSelf = self { - let subjectFlags: TelegramChatBannedRightsFlags = .banSendMedia - let text: String let moreInfo: Bool switch reason { - case .flood: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood - moreInfo = true - case .publicBan: - text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted - moreInfo = true - case .mediaRestricted: - strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) - return + case .flood: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorFlood + moreInfo = true + case .publicBan: + text = strongSelf.presentationData.strings.Conversation_SendMessageErrorGroupRestricted + moreInfo = true + case .mediaRestricted: + strongSelf.interfaceInteraction?.displayRestrictedInfo(.mediaRecording, .alert) + return + case .slowmodeActive: + text = strongSelf.presentationData.strings.Chat_SlowmodeSendError + moreInfo = false } let actions: [TextAlertAction] if moreInfo { @@ -5826,7 +5827,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget, return nil } return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) - |> mapToSignal { status -> Signal in + |> mapToSignal { status, _ -> Signal in if status != nil { return .never() } else { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index a14ea83239..3151b18fde 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -195,7 +195,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { var updatedPlaybackStatus: Signal? if let updatedFile = updatedFile, updatedMedia || updatedMessageId { - updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(context: item.context, file: updatedFile, message: item.message, isRecentActions: item.associatedData.isRecentActions), item.context.account.pendingMessageManager.pendingMessageStatus(item.message.id)) + updatedPlaybackStatus = combineLatest(messageFileMediaResourceStatus(context: item.context, file: updatedFile, message: item.message, isRecentActions: item.associatedData.isRecentActions), item.context.account.pendingMessageManager.pendingMessageStatus(item.message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in if let pendingStatus = pendingStatus { var progress = pendingStatus.progress diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift index 074ffc7141..c0f9be3518 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -555,7 +555,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { if statusUpdated { if let image = media as? TelegramMediaImage { if message.flags.isSending { - updatedStatusSignal = combineLatest(chatMessagePhotoStatus(context: context, messageId: message.id, photoReference: .message(message: MessageReference(message), media: image)), context.account.pendingMessageManager.pendingMessageStatus(message.id)) + updatedStatusSignal = combineLatest(chatMessagePhotoStatus(context: context, messageId: message.id, photoReference: .message(message: MessageReference(message), media: image)), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> (MediaResourceStatus, MediaResourceStatus?) in if let pendingStatus = pendingStatus { let adjustedProgress = max(pendingStatus.progress, 0.027) @@ -571,7 +571,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } } else if let file = media as? TelegramMediaFile { - updatedStatusSignal = combineLatest(messageMediaFileStatus(context: context, messageId: message.id, file: file), context.account.pendingMessageManager.pendingMessageStatus(message.id)) + updatedStatusSignal = combineLatest(messageMediaFileStatus(context: context, messageId: message.id, file: file), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 }) |> map { resourceStatus, pendingStatus -> (MediaResourceStatus, MediaResourceStatus?) in if let pendingStatus = pendingStatus { let adjustedProgress = max(pendingStatus.progress, 0.027) diff --git a/submodules/TelegramUI/TelegramUI/FileMediaResourceStatus.swift b/submodules/TelegramUI/TelegramUI/FileMediaResourceStatus.swift index 7e332a5a4b..c148f57a0b 100644 --- a/submodules/TelegramUI/TelegramUI/FileMediaResourceStatus.swift +++ b/submodules/TelegramUI/TelegramUI/FileMediaResourceStatus.swift @@ -52,7 +52,7 @@ func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMedia } if message.flags.isSending { - return combineLatest(messageMediaFileStatus(context: context, messageId: message.id, file: file), context.account.pendingMessageManager.pendingMessageStatus(message.id), playbackStatus) + return combineLatest(messageMediaFileStatus(context: context, messageId: message.id, file: file), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 }, playbackStatus) |> map { resourceStatus, pendingStatus, playbackStatus -> FileMediaResourceStatus in let mediaStatus: FileMediaResourceMediaStatus if let playbackStatus = playbackStatus { diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift index aeb8105776..c6f118151c 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift @@ -663,7 +663,7 @@ public class PeerMediaCollectionController: TelegramController { return nil } return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id) - |> mapToSignal { status -> Signal in + |> mapToSignal { status, _ -> Signal in if status != nil { return .never() } else { diff --git a/submodules/TelegramUI/TelegramUI/Resources/PresentationStrings.mapping b/submodules/TelegramUI/TelegramUI/Resources/PresentationStrings.mapping index 0aee61edc8..8b57bd3f0a 100644 Binary files a/submodules/TelegramUI/TelegramUI/Resources/PresentationStrings.mapping and b/submodules/TelegramUI/TelegramUI/Resources/PresentationStrings.mapping differ diff --git a/submodules/TelegramUI/TelegramUI/ShareController.swift b/submodules/TelegramUI/TelegramUI/ShareController.swift index 047436bf87..1e7be6f647 100644 --- a/submodules/TelegramUI/TelegramUI/ShareController.swift +++ b/submodules/TelegramUI/TelegramUI/ShareController.swift @@ -340,6 +340,11 @@ public final class ShareController: ViewController { override public func loadDisplayNode() { self.displayNode = ShareControllerNode(sharedContext: self.sharedContext, defaultAction: self.defaultAction, requestLayout: { [weak self] transition in self?.requestLayout(transition: transition) + }, presentError: { [weak self] title, text in + guard let strongSelf = self else { + return + } + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) }, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare) self.controllerNode.dismiss = { [weak self] shared in self?.presentingViewController?.dismiss(animated: false, completion: nil) @@ -353,98 +358,141 @@ public final class ShareController: ViewController { }) } self.controllerNode.share = { [weak self] text, peerIds in - if let strongSelf = self { - switch strongSelf.subject { - case let .url(url): - for peerId in peerIds { - var messages: [EnqueueMessage] = [] - if !text.isEmpty { - messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - } - messages.append(.message(text: url, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - let _ = enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages).start() - } - return .complete() - case let .text(string): - for peerId in peerIds { - var messages: [EnqueueMessage] = [] - if !text.isEmpty { - messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - } - messages.append(.message(text: string, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - let _ = enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages).start() - } - return .complete() - case let .quote(string, url): - for peerId in peerIds { - var messages: [EnqueueMessage] = [] - if !text.isEmpty { - messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - } - let attributedText = NSMutableAttributedString(string: string, attributes: [ChatTextInputAttributes.italic: true as NSNumber]) - attributedText.append(NSAttributedString(string: "\n\n\(url)")) - let entities = generateChatInputTextEntities(attributedText) - messages.append(.message(text: attributedText.string, attributes: [TextEntitiesMessageAttribute(entities: entities)], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - let _ = enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages).start() - } - return .complete() - case let .image(representations): - for peerId in peerIds { - var messages: [EnqueueMessage] = [] - if !text.isEmpty { - messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - } - messages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()), representations: representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil)), replyToMessageId: nil, localGroupingKey: nil)) - let _ = enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages).start() - } - return .complete() - case let .media(mediaReference): - for peerId in peerIds { - var messages: [EnqueueMessage] = [] - if !text.isEmpty { - messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - } - messages.append(.message(text: "", attributes: [], mediaReference: mediaReference, replyToMessageId: nil, localGroupingKey: nil)) - let _ = enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages).start() - } - return .complete() - case let .mapMedia(media): - for peerId in peerIds { - var messages: [EnqueueMessage] = [] - if !text.isEmpty { - messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - } - messages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil)) - let _ = enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages).start() - } - return .complete() - case let .messages(messages): - for peerId in peerIds { - var messagesToEnqueue: [EnqueueMessage] = [] - if !text.isEmpty { - messagesToEnqueue.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) - } - for message in messages { - messagesToEnqueue.append(.forward(source: message.id, grouping: .auto)) - } - let _ = enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messagesToEnqueue).start() - } - return .single(.done) - case let .fromExternal(f): - return f(peerIds, text, strongSelf.currentAccount) - |> map { state -> ShareState in - switch state { - case .preparing: - return .preparing - case let .progress(value): - return .progress(value) - case .done: - return .done - } - } + guard let strongSelf = self else { + return .complete() + } + var shareSignals: [Signal<[MessageId?], NoError>] = [] + switch strongSelf.subject { + case let .url(url): + for peerId in peerIds { + var messages: [EnqueueMessage] = [] + if !text.isEmpty { + messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + } + messages.append(.message(text: url, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages)) + } + case let .text(string): + for peerId in peerIds { + var messages: [EnqueueMessage] = [] + if !text.isEmpty { + messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + } + messages.append(.message(text: string, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages)) + } + case let .quote(string, url): + for peerId in peerIds { + var messages: [EnqueueMessage] = [] + if !text.isEmpty { + messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + } + let attributedText = NSMutableAttributedString(string: string, attributes: [ChatTextInputAttributes.italic: true as NSNumber]) + attributedText.append(NSAttributedString(string: "\n\n\(url)")) + let entities = generateChatInputTextEntities(attributedText) + messages.append(.message(text: attributedText.string, attributes: [TextEntitiesMessageAttribute(entities: entities)], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages)) + } + case let .image(representations): + for peerId in peerIds { + var messages: [EnqueueMessage] = [] + if !text.isEmpty { + messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + } + messages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: arc4random64()), representations: representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil)), replyToMessageId: nil, localGroupingKey: nil)) + shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages)) + } + case let .media(mediaReference): + for peerId in peerIds { + var messages: [EnqueueMessage] = [] + if !text.isEmpty { + messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + } + messages.append(.message(text: "", attributes: [], mediaReference: mediaReference, replyToMessageId: nil, localGroupingKey: nil)) + shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages)) + } + case let .mapMedia(media): + for peerId in peerIds { + var messages: [EnqueueMessage] = [] + if !text.isEmpty { + messages.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + } + messages.append(.message(text: "", attributes: [], mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: nil)) + shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messages)) + } + case let .messages(messages): + for peerId in peerIds { + var messagesToEnqueue: [EnqueueMessage] = [] + if !text.isEmpty { + messagesToEnqueue.append(.message(text: text, attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)) + } + for message in messages { + messagesToEnqueue.append(.forward(source: message.id, grouping: .auto)) + } + shareSignals.append(enqueueMessages(account: strongSelf.currentAccount, peerId: peerId, messages: messagesToEnqueue)) + } + case let .fromExternal(f): + return f(peerIds, text, strongSelf.currentAccount) + |> map { state -> ShareState in + switch state { + case .preparing: + return .preparing + case let .progress(value): + return .progress(value) + case .done: + return .done + } } } - return .complete() + let account = strongSelf.currentAccount + let queue = Queue.mainQueue() + var displayedError = false + return combineLatest(queue: queue, shareSignals) + |> mapToSignal { messageIdSets -> Signal in + var statuses: [Signal<(MessageId, PendingMessageStatus?, PendingMessageFailureReason?), NoError>] = [] + for messageIds in messageIdSets { + for case let id? in messageIds { + statuses.append(account.pendingMessageManager.pendingMessageStatus(id) + |> map { status, error -> (MessageId, PendingMessageStatus?, PendingMessageFailureReason?) in + return (id, status, error) + }) + } + } + return combineLatest(queue: queue, statuses) + |> mapToSignal { statuses -> Signal in + var hasStatuses = false + for (id, status, error) in statuses { + if let error = error { + Queue.mainQueue().async { + let _ = (account.postbox.transaction { transaction -> Peer? in + transaction.deleteMessages([id]) + return transaction.getPeer(id.peerId) + } + |> deliverOnMainQueue).start(next: { peer in + guard let strongSelf = self, let peer = peer else { + return + } + if !displayedError, case .slowmodeActive = error { + displayedError = true + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: peer.displayTitle, text: strongSelf.presentationData.strings.Chat_SlowmodeSendError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + } + }) + } + } + let _ = account.postbox.transaction({ transaction in + + }).start() + if status != nil { + hasStatuses = true + } + } + if !hasStatuses { + return .single(.done) + } + return .complete() + } + |> take(1) + } } self.controllerNode.shareExternal = { [weak self] in if let strongSelf = self { diff --git a/submodules/TelegramUI/TelegramUI/ShareControllerNode.swift b/submodules/TelegramUI/TelegramUI/ShareControllerNode.swift index 257ba16385..2c513d1eff 100644 --- a/submodules/TelegramUI/TelegramUI/ShareControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ShareControllerNode.swift @@ -30,6 +30,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate private let defaultAction: ShareControllerAction? private let requestLayout: (ContainedViewLayoutTransition) -> Void + private let presentError: (String?, String) -> Void private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat)? @@ -70,11 +71,12 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate private var hapticFeedback: HapticFeedback? - init(sharedContext: SharedAccountContext, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, externalShare: Bool, immediateExternalShare: Bool) { + init(sharedContext: SharedAccountContext, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool) { self.sharedContext = sharedContext self.presentationData = sharedContext.currentPresentationData.with { $0 } self.externalShare = externalShare self.immediateExternalShare = immediateExternalShare + self.presentError = presentError self.defaultAction = defaultAction self.requestLayout = requestLayout @@ -500,6 +502,15 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate defaultAction.action() } } else { + if !self.inputFieldNode.text.isEmpty { + for peer in self.controllerInteraction!.selectedPeers { + if let channel = peer.peer as? TelegramChannel, channel.isRestrictedBySlowmode { + self.presentError(channel.title, self.presentationData.strings.Share_MultipleMessagesDisabled) + return + } + } + } + self.inputFieldNode.deactivateInput() let transition = ContainedViewLayoutTransition.animated(duration: 0.12, curve: .easeInOut) transition.updateAlpha(node: self.actionButtonNode, alpha: 0.0)