diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 895ea27ab9..3c13a0edfa 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -15983,3 +15983,12 @@ Error: %8$@"; "Notification.PollAddedOptionYou" = "You added the option \"%1$@\" to the poll"; "Notification.ManagedBotCreated" = "%@ bot created"; + +"Chat.PolOptionAddedTimestamp.Date" = "Added by %1$@ %2$@"; +"Chat.PolOptionAddedTimestamp.TodayAt" = "Added by %1$@ today at %2$@"; +"Chat.PolOptionAddedTimestamp.YesterdayAt" = "Added by %1$@ yesterday at %2$@"; + + +"Chat.PolOptionAddedTimestampYou.Date" = "Added by you %1$@"; +"Chat.PolOptionAddedTimestampYou.TodayAt" = "Added by you today at %1$@"; +"Chat.PolOptionAddedTimestampYou.YesterdayAt" = "Added by you yesterday at %1$@"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 39d1645bff..08065e1962 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1390,7 +1390,7 @@ public protocol SharedAccountContext: AnyObject { func makeBirthdayPrivacyController(context: AccountContext, settings: Promise, openedFromBirthdayScreen: Bool, present: @escaping (ViewController) -> Void) func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController func makeStorageManagementController(context: AccountContext) -> ViewController - func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference]) -> Void) -> AttachmentFileController + func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference], Bool, Int32?, NSAttributedString?) -> Void) -> AttachmentFileController func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, isScheduledMessages: Bool, isFile: Bool, hasTimer: Bool, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject? func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String, stories: Bool, forceDark: Bool) -> ViewController func makeStorySearchController(context: AccountContext, scope: StorySearchControllerScope, listContext: SearchStoryListContext?) -> ViewController diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 5a1a76d0df..af42bca66a 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -263,7 +263,7 @@ public enum ChatControllerInteractionLongTapAction { public enum ChatHistoryMessageSelection: Equatable { case none - case selectable(selected: Bool) + case selectable(selected: Bool, num: Int?) public static func ==(lhs: ChatHistoryMessageSelection, rhs: ChatHistoryMessageSelection) -> Bool { switch lhs { @@ -273,8 +273,8 @@ public enum ChatHistoryMessageSelection: Equatable { } else { return false } - case let .selectable(selected): - if case .selectable(selected) = rhs { + case let .selectable(selected, num): + if case .selectable(selected, num) = rhs { return true } else { return false diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 79ec408810..4a8d409864 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1148,7 +1148,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { }) } } - let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none + let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0, num: nil) } ?? .none var isMedia = false if let tagMask, tagMask != .photoOrVideo { isMedia = true @@ -5886,7 +5886,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { associatedStories: [:] ) - return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: true, isGlobalSearchResult: true) + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false, num: nil) : .none, displayHeader: false, customHeader: nil, hintIsLink: true, isGlobalSearchResult: true) case .files: var media: [EngineMedia] = [] media.append(.file(TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: 0, attributes: [.FileName(fileName: "Text.txt")], alternativeRepresentations: []))) @@ -5917,7 +5917,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { associatedStories: [:] ) - return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false, num: nil) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) case .music: var media: [EngineMedia] = [] media.append(.file(TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: false, duration: 0, title: nil, performer: nil, waveform: Data())], alternativeRepresentations: []))) @@ -5948,7 +5948,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { associatedStories: [:] ) - return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false, num: nil) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) case .voice, .instantVideo: var media: [EngineMedia] = [] media.append(.file(TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: true, duration: 0, title: nil, performer: nil, waveform: Data())], alternativeRepresentations: []))) @@ -5979,7 +5979,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { associatedStories: [:] ) - return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false, num: nil) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true) } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 1f78d6bbac..b4efbf9c0b 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2395,7 +2395,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { selectionControlStyle = .compact } - let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, item.selected, selectionControlStyle) + let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, item.selected, selectionControlStyle, nil) if promoInfo == nil && !isPeerGroup { selectableControlSizeAndApply = sizeAndApply } diff --git a/submodules/Components/BalancedTextComponent/Sources/BalancedTextComponent.swift b/submodules/Components/BalancedTextComponent/Sources/BalancedTextComponent.swift index 3908f9ae06..d12bd455a1 100644 --- a/submodules/Components/BalancedTextComponent/Sources/BalancedTextComponent.swift +++ b/submodules/Components/BalancedTextComponent/Sources/BalancedTextComponent.swift @@ -184,7 +184,7 @@ public final class BalancedTextComponent: Component { var bestSize: (availableWidth: CGFloat, info: TextNodeLayout) - let info = self.textView.updateLayoutFullInfo(availableSize) + let info = self.textView.updateLayoutFullInfo(availableSize) bestSize = (availableSize.width, info) if component.balanced && info.numberOfLines > 1 { diff --git a/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift b/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift index b4d71c200a..475a235163 100644 --- a/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift +++ b/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift @@ -191,7 +191,7 @@ public class ItemListAddressItemNode: ListViewItemNode { var leftOffset: CGFloat = 0.0 var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? if let selected = item.selected { - let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, .regular) + let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, .regular, nil) selectionNodeWidthAndApply = (selectionWidth, selectionApply) leftOffset += selectionWidth - 8.0 } diff --git a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift index 149742ffdd..620f6d9657 100644 --- a/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift +++ b/submodules/ItemListStickerPackItem/Sources/ItemListStickerPackItem.swift @@ -451,7 +451,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { if case let .check(checked) = item.control { selected = checked } - let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, selected, .compact) + let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, selected, .compact, nil) selectableControlSizeAndApply = sizeAndApply editingOffset = sizeAndApply.0 } else { diff --git a/submodules/ItemListUI/Sources/ItemListSelectableControlNode.swift b/submodules/ItemListUI/Sources/ItemListSelectableControlNode.swift index ceac041c53..74fa1c6a5b 100644 --- a/submodules/ItemListUI/Sources/ItemListSelectableControlNode.swift +++ b/submodules/ItemListUI/Sources/ItemListSelectableControlNode.swift @@ -22,8 +22,8 @@ public final class ItemListSelectableControlNode: ASDisplayNode { self.addSubnode(self.checkNode) } - public static func asyncLayout(_ node: ItemListSelectableControlNode?) -> (_ strokeColor: UIColor, _ fillColor: UIColor, _ foregroundColor: UIColor, _ selected: Bool, _ style: Style) -> (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode) { - return { strokeColor, fillColor, foregroundColor, selected, style in + public static func asyncLayout(_ node: ItemListSelectableControlNode?) -> (_ strokeColor: UIColor, _ fillColor: UIColor, _ foregroundColor: UIColor, _ selected: Bool, _ style: Style, _ num: Int?) -> (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode) { + return { strokeColor, fillColor, foregroundColor, selected, style, num in let resultNode: ItemListSelectableControlNode if let node = node { resultNode = node @@ -53,6 +53,9 @@ public final class ItemListSelectableControlNode: ASDisplayNode { checkOffset = 16.0 } resultNode.checkNode.frame = CGRect(origin: CGPoint(x: checkOffset, y: floorToScreenPixels((size.height - checkSize.height) / 2.0)), size: checkSize) + if let num { + resultNode.checkNode.content = .counter(num) + } resultNode.checkNode.setSelected(selected, animated: animated) return resultNode }) diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift index 9e3232df86..5a3b7c5f0e 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextWithLabelItem.swift @@ -189,7 +189,7 @@ public class ItemListTextWithLabelItemNode: ListViewItemNode { var leftOffset: CGFloat = 0.0 var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? if let selected = item.selected { - let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, selected, .regular) + let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, selected, .regular, nil) selectionNodeWidthAndApply = (selectionWidth, selectionApply) leftOffset += selectionWidth - 8.0 } diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 5730fd5119..587db4b463 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -638,8 +638,8 @@ public final class ListMessageFileItemNode: ListMessageNode { var leftOffset: CGFloat = 0.0 var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? - if case let .selectable(selected) = item.selection { - let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, .regular) + if case let .selectable(selected, num) = item.selection { + let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, .regular, num.flatMap { $0 + 1 }) selectionNodeWidthAndApply = (selectionWidth, selectionApply) leftOffset += selectionWidth } diff --git a/submodules/ListMessageItem/Sources/ListMessageItem.swift b/submodules/ListMessageItem/Sources/ListMessageItem.swift index 1cdd52400d..2ca7fac4da 100644 --- a/submodules/ListMessageItem/Sources/ListMessageItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageItem.swift @@ -245,7 +245,7 @@ public final class ListMessageItem: ListViewItem, ItemListItem { return } - if case let .selectable(selected) = self.selection { + if case let .selectable(selected, _) = self.selection { self.interaction.toggleMessagesSelection([message.id], !selected) } else { if !self.displayFileInfo || self.isAttachMusic { diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index fd7134aa1c..3f80e48111 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -267,8 +267,8 @@ public final class ListMessageSnippetItemNode: ListMessageNode { var leftOffset: CGFloat = 0.0 var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)? - if case let .selectable(selected) = item.selection { - let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, .regular) + if case let .selectable(selected, num) = item.selection { + let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, .regular, num.flatMap { $0 + 1 }) selectionNodeWidthAndApply = (selectionWidth, selectionApply) leftOffset += selectionWidth } diff --git a/submodules/PremiumUI/Sources/GiftOptionItem.swift b/submodules/PremiumUI/Sources/GiftOptionItem.swift index c11806ae8d..5dee8b306b 100644 --- a/submodules/PremiumUI/Sources/GiftOptionItem.swift +++ b/submodules/PremiumUI/Sources/GiftOptionItem.swift @@ -303,7 +303,7 @@ class GiftOptionItemNode: ItemListRevealOptionsItemNode { var editingOffset: CGFloat = 0.0 if let isSelected = item.isSelected { - let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, isSelected, .regular) + let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, isSelected, .regular, nil) selectableControlSizeAndApply = sizeAndApply editingOffset = sizeAndApply.0 } diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift index 77775cff94..043f68f6f6 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileController.swift @@ -404,7 +404,7 @@ final class AttachmentFileContext: AttachmentMediaPickerContext { } func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode, parameters: ChatSendMessageActionSheetController.SendParameters?) { - self.controller?.mulitpleCompletion?(mode, .files, parameters) + self.controller?.mulitpleCompletion?(mode, .files, parameters, self.controller?.caption) } func schedule(parameters: ChatSendMessageActionSheetController.SendParameters?) { @@ -436,7 +436,7 @@ public class AttachmentFileControllerImpl: ItemListController, AttachmentFileCon fileprivate var bottomEdgeColor: UIColor = .clear - fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?) -> Void)? + fileprivate var mulitpleCompletion: ((AttachmentMediaPickerSendMode, AttachmentMediaPickerAttachmentMode, ChatSendMessageActionSheetController.SendParameters?, NSAttributedString?) -> Void)? var delayDisappear = false @@ -508,15 +508,19 @@ private struct AttachmentFileControllerState: Equatable { var searching: Bool var savedMusicExpanded: Bool var recentMusicExpanded: Bool - var selectedMessageIds: Set? + var selectedMessageIds: [MessageId]? var messageMap: [MessageId: EngineMessage] } private func messageSelectionState(state: AttachmentFileControllerState, message: Message?) -> ChatHistoryMessageSelection { - guard let message = message, let selectedMessageIds = state.selectedMessageIds else { + guard let message, let selectedMessageIds = state.selectedMessageIds else { return .none } - return .selectable(selected: selectedMessageIds.contains(message.id)) + if let index = selectedMessageIds.firstIndex(where: { $0 == message.id }) { + return .selectable(selected: true, num: index) + } else { + return .selectable(selected: false, num: nil) + } } public enum AttachmentFileControllerMode { @@ -551,7 +555,7 @@ public func makeAttachmentFileControllerImpl( presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, - send: @escaping ([AnyMediaReference]) -> Void + send: @escaping ([AnyMediaReference], Bool, Int32?, NSAttributedString?) -> Void ) -> AttachmentFileController { let actionsDisposable = DisposableSet() @@ -609,7 +613,7 @@ public func makeAttachmentFileControllerImpl( send: { message in if message.id.namespace == Namespaces.Message.Local { if let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile { - send([.standalone(media: file)]) + send([.standalone(media: file)], false, nil, nil) dismissImpl?() } } else { @@ -625,7 +629,7 @@ public func makeAttachmentFileControllerImpl( } |> deliverOnMainQueue).startStandalone(next: { messages in if let message = messages.first, let file = message.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile { - send([.message(message: MessageReference(message), media: file)]) + send([.message(message: MessageReference(message), media: file)], false, nil, nil) } dismissImpl?() }) @@ -647,9 +651,9 @@ public func makeAttachmentFileControllerImpl( } let messageId = message.id if selectedMessageIds.contains(messageId) { - selectedMessageIds.remove(messageId) + selectedMessageIds.removeAll(where: { $0 == messageId }) } else { - selectedMessageIds.insert(messageId) + selectedMessageIds.append(messageId) } var updatedState = state updatedState.selectedMessageIds = selectedMessageIds @@ -665,9 +669,9 @@ public func makeAttachmentFileControllerImpl( } for messageId in messageIds { if value { - selectedMessageIds.insert(messageId) + selectedMessageIds.append(messageId) } else { - selectedMessageIds.remove(messageId) + selectedMessageIds.removeAll(where: { $0 == messageId }) } } var updatedState = state @@ -701,8 +705,8 @@ public func makeAttachmentFileControllerImpl( c?.dismiss(completion: {}) updateState { state in var updatedState = state - var selectedMessageIds = updatedState.selectedMessageIds ?? Set() - selectedMessageIds.insert(message.id) + var selectedMessageIds = updatedState.selectedMessageIds ?? [] + selectedMessageIds.append(message.id) updatedState.selectedMessageIds = selectedMessageIds updatedState.messageMap[message.id] = message updateSelectionCountImpl?(selectedMessageIds.count) @@ -932,7 +936,7 @@ public func makeAttachmentFileControllerImpl( } let controller = AttachmentFileControllerImpl(context: context, state: signal, hideNavigationBarBackground: true) - controller.mulitpleCompletion = { _, _, _ in + controller.mulitpleCompletion = { sendMode, _, _, caption in let _ = stateValue.with({ state in if let selectedMessageIds = state.selectedMessageIds { var mediaReferences: [AnyMediaReference] = [] @@ -941,7 +945,7 @@ public func makeAttachmentFileControllerImpl( mediaReferences.append(.standalone(media: file)) } } - send(mediaReferences) + send(mediaReferences, sendMode == .silently, nil, caption) dismissImpl?() } }) @@ -1026,7 +1030,7 @@ public func storyAudioPickerController( let filePickerController = makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: .audio(story: true), bannedSendMedia: nil, presentGallery: {}, presentFiles: { selectFromFiles() dismissImpl?() - }, presentDocumentScanner: nil, send: { files in + }, presentDocumentScanner: nil, send: { files, _, _, _ in completion(files.first!) dismissImpl?() }) as! AttachmentFileControllerImpl diff --git a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift index 5b26071ced..c016ebcff6 100644 --- a/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift +++ b/submodules/TelegramUI/Components/AttachmentFileController/Sources/AttachmentFileSearchItem.swift @@ -348,16 +348,30 @@ struct AttachmentFileSearchContainerTransition { let isSearching: Bool let isEmpty: Bool let query: String + let crossfade: Bool } -private func attachmentFileSearchContainerPreparedRecentTransition(from fromEntries: [AttachmentFileSearchEntry], to toEntries: [AttachmentFileSearchEntry], isSearching: Bool, isEmpty: Bool, query: String, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, interaction: AttachmentFileSearchContainerInteraction, mode: AttachmentFileControllerMode) -> AttachmentFileSearchContainerTransition { +private func attachmentFileSearchContainerPreparedRecentTransition( + from fromEntries: [AttachmentFileSearchEntry], + to toEntries: [AttachmentFileSearchEntry], + isSearching: Bool, + isEmpty: Bool, + query: String, + context: AccountContext, + presentationData: PresentationData, + nameSortOrder: PresentationPersonNameOrder, + nameDisplayOrder: PresentationPersonNameOrder, + interaction: AttachmentFileSearchContainerInteraction, + mode: AttachmentFileControllerMode, + crossfade: Bool +) -> AttachmentFileSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction, mode: mode), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, interaction: interaction, mode: mode), directionHint: nil) } - return AttachmentFileSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, isEmpty: isEmpty, query: query) + return AttachmentFileSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching, isEmpty: isEmpty, query: query, crossfade: crossfade) } @@ -553,7 +567,9 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon } ) - if let data = context.currentAppConfiguration.with({ $0 }).data, let searchBot = data["music_search_username"] as? String, !searchBot.isEmpty { + let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines) + + if let data = context.currentAppConfiguration.with({ $0 }).data, let searchBot = data["music_search_username"] as? String, !searchBot.isEmpty, trimmedQuery.count >= 3 { globalMusic = .single(nil) |> then( context.engine.peers.resolvePeerByName(name: searchBot, referrer: nil) @@ -567,7 +583,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon guard let peer = peer else { return .single(nil) } - return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: query, offset: "") + return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: trimmedQuery, offset: "") |> map { results -> ChatContextResultCollection? in return results?.results } @@ -673,13 +689,27 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon } let previousSearchItems = Atomic<[AttachmentFileSearchEntry]?>(value: nil) + let previousHadGlobalItems = Atomic(value: false) self.searchDisposable.set((combineLatest(searchQuery, foundItems, self.presentationDataPromise.get()) |> deliverOnMainQueue).startStrict(next: { [weak self] query, entries, presentationData in if let strongSelf = self { let previousEntries = previousSearchItems.swap(entries) updateActivity(false) let firstTime = previousEntries == nil - let transition = attachmentFileSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, isEmpty: entries?.isEmpty ?? false, query: query ?? "", context: context, presentationData: presentationData, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, interaction: interaction, mode: mode) + + var hasGlobalItems = false + if let entries { + for entry in entries { + if case let .header(_, section) = entry, section == 2 { + hasGlobalItems = true + } + } + } + let hadGlobalItems = previousHadGlobalItems.swap(hasGlobalItems) + + let crossfade = hadGlobalItems != hasGlobalItems + + let transition = attachmentFileSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, isEmpty: entries?.isEmpty ?? false, query: query ?? "", context: context, presentationData: presentationData, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, interaction: interaction, mode: mode, crossfade: crossfade) strongSelf.enqueueTransition(transition, firstTime: firstTime) } })) @@ -739,6 +769,10 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon //options.insert(.AnimateInsertion) + if transition.crossfade { + options.insert(.AnimateCrossfade) + } + let isSearching = transition.isSearching self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 8941219f71..c5713170fd 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2183,7 +2183,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI switch selection { case .none: break - case let .selectable(selected): + case let .selectable(selected, _): itemSelection = selected } break diff --git a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift index e4a1a34b54..4e634d15ab 100644 --- a/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/ComposePollScreen/Sources/PollAttachmentScreen.swift @@ -100,7 +100,7 @@ public func presentPollAttachmentScreen( //TODO }, presentDocumentScanner: nil, - send: { mediaReferences in + send: { mediaReferences, _, _, _ in completion(mediaReferences.first!) } ) as! AttachmentFileControllerImpl diff --git a/submodules/TelegramUI/Components/ContextControllerImpl/BUILD b/submodules/TelegramUI/Components/ContextControllerImpl/BUILD index 0f4c45d696..3bfa30b1d9 100644 --- a/submodules/TelegramUI/Components/ContextControllerImpl/BUILD +++ b/submodules/TelegramUI/Components/ContextControllerImpl/BUILD @@ -35,6 +35,7 @@ swift_library( "//submodules/UIKitRuntimeUtils", "//submodules/UndoUI", "//submodules/TelegramUI/Components/LensTransition", + "//submodules/Components/BalancedTextComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerActionsStackNode.swift b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerActionsStackNode.swift index 3d9e9e9f9d..5ff115f2f1 100644 --- a/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/TelegramUI/Components/ContextControllerImpl/Sources/ContextControllerActionsStackNode.swift @@ -20,6 +20,7 @@ import LottieComponent import TextNodeWithEntities import ContextUI import LensTransition +import BalancedTextComponent public protocol ContextControllerActionsListItemNode: ASDisplayNode { func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) @@ -39,6 +40,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking private var item: ContextMenuActionItem private let titleLabelNode: ImmediateTextNodeWithEntities + private let balancedTitleLabel = ComponentView() private let subtitleNode: ImmediateTextNode private let iconNode: ASImageNode private let additionalIconNode: ASImageNode @@ -282,7 +284,13 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking titleColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4) } + var balanceTitleAttributedText: NSAttributedString? if self.item.parseMarkdown || !self.item.entities.isEmpty { + var balancedTextLayout = false + if self.item.parseMarkdown, case .twoLinesMax = self.item.textLayout { + balancedTextLayout = true + } + let attributedText: NSAttributedString if !self.item.entities.isEmpty { let inputStateText = ChatTextInputStateText(text: self.item.text, attributes: self.item.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in @@ -324,18 +332,25 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking ) ) } - self.titleLabelNode.attributedText = attributedText - self.titleLabelNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.5) - self.titleLabelNode.highlightAttributeAction = { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { - return NSAttributedString.Key(rawValue: "URL") - } else { - return nil + + if self.item.entities.isEmpty && balancedTextLayout { + self.titleLabelNode.isHidden = true + balanceTitleAttributedText = attributedText + } else { + self.titleLabelNode.isHidden = false + self.titleLabelNode.attributedText = attributedText + self.titleLabelNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.5) + self.titleLabelNode.highlightAttributeAction = { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } } - } - self.titleLabelNode.tapAttributeAction = { [weak item] attributes, _ in - if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { - item?.textLinkAction() + self.titleLabelNode.tapAttributeAction = { [weak item] attributes, _ in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + item?.textLinkAction() + } } } } else { @@ -506,7 +521,22 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking maxTextWidth = max(1.0, maxTextWidth) - let titleSize = self.titleLabelNode.updateLayout(CGSize(width: maxTextWidth, height: 1000.0)) + let titleSize: CGSize + if let balanceTitleAttributedText { + titleSize = self.balancedTitleLabel.update( + transition: .immediate, + component: AnyComponent( + BalancedTextComponent( + text: .plain(balanceTitleAttributedText), + maximumNumberOfLines: 2 + ) + ), + environment: {}, + containerSize: CGSize(width: maxTextWidth, height: 1000.0) + ) + } else { + titleSize = self.titleLabelNode.updateLayout(CGSize(width: maxTextWidth, height: 1000.0)) + } let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: maxTextWidth, height: 1000.0)) var minSize = CGSize() @@ -549,7 +579,16 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking subtitleFrame.origin.x = titleFrame.minX } - transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame) + if let _ = balanceTitleAttributedText { + if let balancedTitleLabelView = self.balancedTitleLabel.view { + if balancedTitleLabelView.superview == nil { + self.view.addSubview(balancedTitleLabelView) + } + transition.updateFrameAdditive(view: balancedTitleLabelView, frame: titleFrame) + } + } else { + transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame) + } transition.updateFrameAdditive(node: self.subtitleNode, frame: subtitleFrame) if let badgeIconNode = self.badgeIconNode, let iconSize = badgeIconNode.image?.size { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift index bd7343013d..8a8b05bcef 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoVisualMediaPaneNode.swift @@ -532,7 +532,7 @@ private final class ItemView: UIView, SparseItemGridView { } if let item = self.item, let messageItem = self.messageItem, let itemNode = itemNode as? ListMessageFileItemNode { - if case let .selectable(selected) = messageItem.selection { + if case let .selectable(selected, _) = messageItem.selection { self.interaction?.toggleMessagesSelection([item.message.id], !selected) } else { itemNode.activateMedia() @@ -560,7 +560,7 @@ private final class ItemView: UIView, SparseItemGridView { interaction: interaction, message: item.message, selection: isSelected.flatMap { isSelected in - return .selectable(selected: isSelected) + return .selectable(selected: isSelected, num: nil) } ?? .none, displayHeader: false ) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 0b7f591261..6403be6e51 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -1948,7 +1948,7 @@ final class StoryItemSetContainerSendMessage: @unchecked(Sendable) { } attachmentController?.dismiss(animated: true) self.presentICloudFileGallery(view: view, peer: peer, replyMessageId: nil, replyToStoryId: focusedStoryId) - }, presentDocumentScanner: nil, send: { [weak view] mediaReferences in + }, presentDocumentScanner: nil, send: { [weak view] mediaReferences, _, _, _ in guard let view, let component = view.component else { return } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollContextMenu.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollContextMenu.swift index 3076e11e60..032ba7e222 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollContextMenu.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenPollContextMenu.swift @@ -14,6 +14,7 @@ import ChatControllerInteraction import Pasteboard import TelegramStringFormatting import TelegramPresentationData +import AvatarNode private enum OptionsId: Hashable { case item @@ -37,8 +38,16 @@ extension ChatControllerImpl { } } - let _ = (contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: params.messageNode as? ChatMessageItemView) - |> deliverOnMainQueue).start(next: { [weak self] actions in + var addedByPeer: Signal = .single(nil) + if let peerId = pollOption.addedBy { + addedByPeer = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) + } + + let _ = combineLatest( + queue: Queue.mainQueue(), + contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState: self.presentationInterfaceState, context: self.context, messages: [message], controllerInteraction: self.controllerInteraction, selectAll: false, interfaceInteraction: self.interfaceInteraction, messageNode: params.messageNode as? ChatMessageItemView), + addedByPeer + ).start(next: { [weak self] actions, addedByPeer in guard let self else { return } @@ -146,7 +155,7 @@ extension ChatControllerImpl { }))) } - if pollOption.date != nil { + if let addedByPeer, let date = pollOption.date { //TODO:localize items.append(.action(ContextMenuActionItem(text: "Remove", textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in f(.default) @@ -156,6 +165,49 @@ extension ChatControllerImpl { } let _ = self.context.engine.messages.deletePollOption(messageId: message.id, opaqueIdentifier: pollOption.opaqueIdentifier).start() }))) + + items.append(.separator) + + let peerName = "**\(addedByPeer.compactDisplayTitle)**" + let dateText = humanReadableStringForTimestamp(strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, timestamp: date, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat( + dateFormatString: { value in + if addedByPeer.id == self.context.account.peerId { + return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestampYou_Date(value).string, ranges: []) + } else { + return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestamp_Date(peerName, value).string, ranges: []) + } + }, + tomorrowFormatString: { value in + if addedByPeer.id == self.context.account.peerId { + return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestampYou_TodayAt(value).string, ranges: []) + } else { + return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestamp_TodayAt(peerName, value).string, ranges: []) + } + }, + todayFormatString: { value in + if addedByPeer.id == self.context.account.peerId { + return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestampYou_TodayAt(value).string, ranges: []) + } else { + return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestamp_TodayAt(peerName, value).string, ranges: []) + } + }, + yesterdayFormatString: { value in + if addedByPeer.id == self.context.account.peerId { + return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestampYou_YesterdayAt(value).string, ranges: []) + } else { + return PresentationStrings.FormattedString(string: self.presentationData.strings.Chat_PolOptionAddedTimestamp_YesterdayAt(peerName, value).string, ranges: []) + } + } + )).string + + let avatarSize = CGSize(width: 24.0, height: 24.0) + items.append(.action(ContextMenuActionItem(text: dateText, textFont: .small, parseMarkdown: true, icon: { _ in return nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: self.context.account, peer: addedByPeer, size: avatarSize)), action: { [weak self] _, f in + f(.default) + guard let self else { + return + } + self.openPeer(peer: addedByPeer, navigation: .chat(textInputState: nil, subject: nil, peekData: nil), fromMessage: nil) + }))) } self.canReadHistory.set(false) diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 4d93ae830c..2521594391 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -381,7 +381,7 @@ extension ChatControllerImpl { self?.presentICloudFileGallery() }, presentDocumentScanner: { [weak self] in self?.presentDocumentScanner() - }, send: { [weak self] mediaReferences in + }, send: { [weak self] mediaReferences, silentPosting, scheduleTime, caption in guard let self else { return } @@ -390,9 +390,26 @@ extension ChatControllerImpl { if mediaReferences.count > 1 { groupingKey = Int64.random(in: .min ..< .max) } - for mediaReference in mediaReferences { - messages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + + var attributes: [MessageAttribute] = [] + var text = "" + if let caption { + text = caption.string + let entities = generateTextEntities(text, enabledTypes: .all, currentEntities: generateChatInputTextEntities(caption)) + if !entities.isEmpty { + attributes.append(TextEntitiesMessageAttribute(entities: entities)) + } } + + for mediaReference in mediaReferences { + if messages.count == 10 { + groupingKey = Int64.random(in: .min ..< .max) + } + let isLast = mediaReference == mediaReferences.last + + messages.append(.message(text: isLast ? text : "", attributes: isLast ? attributes : [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + } + messages = self.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime, repeatPeriod: nil, postpone: false) self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in self?.sendMessages(messages, media: true, postpone: postpone) }) @@ -414,7 +431,7 @@ extension ChatControllerImpl { }, presentFiles: { [weak self, weak attachmentController] in attachmentController?.dismiss(animated: true) self?.presentICloudFileGallery(documentTypes: ["public.mp3", "public.mpeg-4-audio", "public.aac-audio", "org.xiph.flac"]) - }, presentDocumentScanner: nil, send: { [weak self] mediaReferences in + }, presentDocumentScanner: nil, send: { [weak self] mediaReferences, silentPosting, scheduleTime, caption in guard let self else { return } @@ -423,9 +440,26 @@ extension ChatControllerImpl { if mediaReferences.count > 1 { groupingKey = Int64.random(in: .min ..< .max) } - for mediaReference in mediaReferences { - messages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + + var attributes: [MessageAttribute] = [] + var text = "" + if let caption { + text = caption.string + let entities = generateTextEntities(text, enabledTypes: .all, currentEntities: generateChatInputTextEntities(caption)) + if !entities.isEmpty { + attributes.append(TextEntitiesMessageAttribute(entities: entities)) + } } + + for mediaReference in mediaReferences { + if messages.count == 10 { + groupingKey = Int64.random(in: .min ..< .max) + } + let isLast = mediaReference == mediaReferences.last + + messages.append(.message(text: isLast ? text : "", attributes: isLast ? attributes : [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: [])) + } + messages = self.transformEnqueueMessages(messages, silentPosting: silentPosting, scheduleTime: scheduleTime, repeatPeriod: nil, postpone: false) self.presentPaidMessageAlertIfNeeded(completion: { [weak self] postpone in self?.sendMessages(messages, media: true, postpone: postpone) }) diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 0206441553..b5f894129e 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -247,7 +247,7 @@ func chatHistoryEntriesForView( if let messageGroupingKey = message.groupingKey { let selection: ChatHistoryMessageSelection if let selectedMessages = selectedMessages { - selection = .selectable(selected: selectedMessages.contains(message.id)) + selection = .selectable(selected: selectedMessages.contains(message.id), num: nil) } else { selection = .none } @@ -293,7 +293,7 @@ func chatHistoryEntriesForView( } else { let selection: ChatHistoryMessageSelection if let selectedMessages = selectedMessages { - selection = .selectable(selected: selectedMessages.contains(message.id)) + selection = .selectable(selected: selectedMessages.contains(message.id), num: nil) } else { selection = .none } @@ -308,7 +308,7 @@ func chatHistoryEntriesForView( } else { let selection: ChatHistoryMessageSelection if let selectedMessages = selectedMessages { - selection = .selectable(selected: selectedMessages.contains(message.id)) + selection = .selectable(selected: selectedMessages.contains(message.id), num: nil) } else { selection = .none } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 8e6fb485bf..52a63d93ba 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2750,7 +2750,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { }) } - public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference]) -> Void) -> AttachmentFileController { + public func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, audio: Bool, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, presentDocumentScanner: (() -> Void)?, send: @escaping ([AnyMediaReference], Bool, Int32?, NSAttributedString?) -> Void) -> AttachmentFileController { return makeAttachmentFileControllerImpl(context: context, updatedPresentationData: updatedPresentationData, mode: audio ? .audio(story: false) : .recent, bannedSendMedia: bannedSendMedia, presentGallery: presentGallery, presentFiles: presentFiles, presentDocumentScanner: presentDocumentScanner, send: send) }