From 9554305a3381068f506eff6ec7d5f8932a453992 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 27 Mar 2024 19:54:56 +0400 Subject: [PATCH 1/2] Various improvements --- .../Sources/Node/ChatListItem.swift | 8 +- .../SelectivePrivacySettingsController.swift | 2 +- .../ChatEmptyNode/Sources/ChatEmptyNode.swift | 7 +- .../PeerInfoScreenPersonalChannelItem.swift | 3 +- ...aticBusinessMessageListItemComponent.swift | 3 +- .../Sources/QuickReplySetupScreen.swift | 3 +- .../Sources/BusinessIntroSetupScreen.swift | 199 +++++++++++++++++- .../TelegramUI/Sources/ChatController.swift | 8 + .../Sources/ChatTextInputPanelNode.swift | 11 +- .../CommandChatInputContextPanelNode.swift | 3 +- 10 files changed, 234 insertions(+), 13 deletions(-) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index a499049300..02b3e25993 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -97,13 +97,15 @@ public enum ChatListItemContent { public var messageCount: Int? public var hideSeparator: Bool public var hideDate: Bool + public var hidePeerStatus: Bool - public init(commandPrefix: String?, searchQuery: String?, messageCount: Int?, hideSeparator: Bool, hideDate: Bool) { + public init(commandPrefix: String?, searchQuery: String?, messageCount: Int?, hideSeparator: Bool, hideDate: Bool, hidePeerStatus: Bool) { self.commandPrefix = commandPrefix self.searchQuery = searchQuery self.messageCount = messageCount self.hideSeparator = hideSeparator self.hideDate = hideDate + self.hidePeerStatus = hidePeerStatus } } @@ -2340,7 +2342,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { attributedText = foldLineBreaks(draftText) } - } else if let message = messages.first { + } else if let message = messages.last { var composedString: NSMutableAttributedString if let peerText = peerText { @@ -2942,7 +2944,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { break } } else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer { - if case let .peer(peerData) = item.content, peerData.customMessageListData != nil { + if case let .peer(peerData) = item.content, peerData.customMessageListData?.hidePeerStatus == true { currentCredibilityIconContent = nil } else if case .savedMessagesChats = item.chatListLocation, peer.id == item.context.account.peerId { currentCredibilityIconContent = nil diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 7a8010d034..5c814cf46d 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -1243,7 +1243,7 @@ public func selectivePrivacySettingsController( break } - let controller = selectivePrivacyPeersController(context: context, title: title, initialPeers: peerIds, initialEnableForPremium: stateValue.with({ $0 }).enableForPremium, displayPremiumCategory: displayPremiumCategory, updated: { updatedPeerIds, enableForPremium in + let controller = selectivePrivacyPeersController(context: context, title: title, initialPeers: peerIds, initialEnableForPremium: displayPremiumCategory && stateValue.with({ $0 }).enableForPremium, displayPremiumCategory: displayPremiumCategory, updated: { updatedPeerIds, enableForPremium in updateState { state in if enable { switch target { diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift index 5f443fd4d7..81ec42bd4d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift @@ -180,7 +180,7 @@ public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNod let previousCustomStickerFile = self.currentCustomStickerFile self.currentCustomStickerFile = customStickerFile - let stickerSize: CGSize + var stickerSize: CGSize let inset: CGFloat if size.width == 320.0 { stickerSize = CGSize(width: 106.0, height: 106.0) @@ -189,6 +189,11 @@ public final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNod stickerSize = CGSize(width: 160.0, height: 160.0) inset = 15.0 } + + if let customStickerFile, let dimensions = customStickerFile.dimensions?.cgSize { + stickerSize = dimensions.aspectFitted(stickerSize) + } + if let item = self.stickerItem, previousCustomStickerFile == customStickerFile { self.stickerNode.updateLayout(item: item, size: stickerSize, isVisible: true, synchronousLoads: true) } else if !self.didSetupSticker || previousCustomStickerFile != customStickerFile { diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift index 46ad4f7be8..ce5141dff6 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenPersonalChannelItem.swift @@ -588,7 +588,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod searchQuery: nil, messageCount: nil, hideSeparator: true, - hideDate: isLoading + hideDate: isLoading, + hidePeerStatus: false ) )), editing: false, diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift index 481cb62d72..08c3dfa7f6 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageListItemComponent.swift @@ -242,7 +242,8 @@ final class GreetingMessageListItemComponent: Component { searchQuery: nil, messageCount: component.count, hideSeparator: true, - hideDate: true + hideDate: true, + hidePeerStatus: true ) )), editing: false, diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 79afffec25..33c57f92ff 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -272,7 +272,8 @@ final class QuickReplySetupScreenComponent: Component { searchQuery: nil, messageCount: item.totalCount, hideSeparator: false, - hideDate: true + hideDate: true, + hidePeerStatus: true ) )), editing: isEditing, diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift index be316dc4ed..e196256e42 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift @@ -294,7 +294,7 @@ final class BusinessIntroSetupScreenComponent: Component { if !self.isUpdating { self.state?.updated(transition: .immediate) } - case let .text(rawQuery, _): + case let .text(rawQuery, languageCode): let query = rawQuery.trimmingCharacters(in: .whitespacesAndNewlines) if query.isEmpty { @@ -304,7 +304,7 @@ final class BusinessIntroSetupScreenComponent: Component { } else { let context = component.context - let localSets = context.engine.stickers.searchStickerSets(query: query) + /*let localSets = context.engine.stickers.searchStickerSets(query: query) let remoteSets: Signal = .single(nil) |> then( context.engine.stickers.searchStickerSetsRemotely(query: query) |> map(Optional.init) @@ -349,6 +349,199 @@ final class BusinessIntroSetupScreenComponent: Component { items.append(item) } + return .single([EmojiPagerContentComponent.ItemGroup( + supergroupId: "search", + groupId: "search", + title: nil, + subtitle: nil, + badge: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + hasEdit: false, + collapsedLineCount: nil, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: false, + items: items + )]) + }*/ + + let stickers: Signal<[(String?, FoundStickerItem)], NoError> = Signal { subscriber in + var signals: Signal<[Signal<(String?, [FoundStickerItem]), NoError>], NoError> = .single([]) + + if query.isSingleEmoji { + signals = .single([context.engine.stickers.searchStickers(query: [query.basicEmoji.0]) + |> map { (nil, $0.items) }]) + } else if query.count > 1, !languageCode.isEmpty && languageCode != "emoji" { + var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3) + if !languageCode.lowercased().hasPrefix("en") { + signal = signal + |> mapToSignal { keywords in + return .single(keywords) + |> then( + context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query.lowercased(), completeMatch: query.count < 3) + |> map { englishKeywords in + return keywords + englishKeywords + } + ) + } + } + + signals = signal + |> map { keywords -> [Signal<(String?, [FoundStickerItem]), NoError>] in + var signals: [Signal<(String?, [FoundStickerItem]), NoError>] = [] + let emoticons = keywords.flatMap { $0.emoticons } + for emoji in emoticons { + signals.append(context.engine.stickers.searchStickers(query: [emoji.basicEmoji.0]) + |> take(1) + |> map { (emoji, $0.items) }) + } + return signals + } + } + + return (signals + |> mapToSignal { signals in + return combineLatest(signals) + }).start(next: { results in + var result: [(String?, FoundStickerItem)] = [] + for (emoji, stickers) in results { + for sticker in stickers { + result.append((emoji, sticker)) + } + } + subscriber.putNext(result) + }, completed: { + subscriber.putCompletion() + }) + } + + let currentRemotePacks = Atomic(value: nil) + + let local = context.engine.stickers.searchStickerSets(query: query) + let remote = context.engine.stickers.searchStickerSetsRemotely(query: query) + |> delay(0.2, queue: Queue.mainQueue()) + let rawPacks = local + |> mapToSignal { result -> Signal<(FoundStickerSets, Bool, FoundStickerSets?), NoError> in + var localResult = result + if let currentRemote = currentRemotePacks.with ({ $0 }) { + localResult = localResult.merge(with: currentRemote) + } + return .single((localResult, false, nil)) + |> then( + remote + |> map { remote -> (FoundStickerSets, Bool, FoundStickerSets?) in + return (result.merge(with: remote), true, remote) + } + ) + } + + let installedPackIds = context.account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])]) + |> map { view -> Set in + var installedPacks = Set() + if let stickerPacksView = view.views[.itemCollectionInfos(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])] as? ItemCollectionInfosView { + if let packsEntries = stickerPacksView.entriesByNamespace[Namespaces.ItemCollection.CloudStickerPacks] { + for entry in packsEntries { + installedPacks.insert(entry.id) + } + } + } + return installedPacks + } + |> distinctUntilChanged + let packs = combineLatest(rawPacks, installedPackIds) + |> map { packs, installedPackIds -> (FoundStickerSets, Bool, FoundStickerSets?) in + var (localPacks, completed, remotePacks) = packs + + for i in 0 ..< localPacks.infos.count { + let installed = installedPackIds.contains(localPacks.infos[i].0) + if installed != localPacks.infos[i].3 { + localPacks.infos[i].3 = installed + } + } + + if remotePacks != nil { + for i in 0 ..< remotePacks!.infos.count { + let installed = installedPackIds.contains(remotePacks!.infos[i].0) + if installed != remotePacks!.infos[i].3 { + remotePacks!.infos[i].3 = installed + } + } + } + + return (localPacks, completed, remotePacks) + } + + let signal = combineLatest(stickers, packs) + |> map { stickers, packs -> ([(String?, FoundStickerItem)], FoundStickerSets, Bool, FoundStickerSets?)? in + return (stickers, packs.0, packs.1, packs.2) + } + + let resultSignal: Signal<[EmojiPagerContentComponent.ItemGroup], NoError> = signal + |> mapToSignal { result in + guard let result else { + return .complete() + } + + let (foundItems, localSets, complete, remoteSets) = result + + var items: [EmojiPagerContentComponent.Item] = [] + + var existingIds = Set() + for (_, entry) in foundItems { + let itemFile = entry.file + + if existingIds.contains(itemFile.fileId) { + continue + } + existingIds.insert(itemFile.fileId) + + let animationData = EntityKeyboardAnimationData(file: itemFile) + let item = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: itemFile, + subgroupId: nil, + icon: .none, + tintMode: animationData.isTemplate ? .primary : .none + ) + items.append(item) + } + + var mergedSets = localSets + if let remoteSets { + mergedSets = mergedSets.merge(with: remoteSets) + } + for entry in mergedSets.entries { + guard let stickerPackItem = entry.item as? StickerPackItem else { + continue + } + let itemFile = stickerPackItem.file + + if existingIds.contains(itemFile.fileId) { + continue + } + existingIds.insert(itemFile.fileId) + + let animationData = EntityKeyboardAnimationData(file: itemFile) + let item = EmojiPagerContentComponent.Item( + animationData: animationData, + content: .animation(animationData), + itemFile: itemFile, + subgroupId: nil, + icon: .none, + tintMode: animationData.isTemplate ? .primary : .none + ) + items.append(item) + } + + if items.isEmpty && !complete { + return .complete() + } + return .single([EmojiPagerContentComponent.ItemGroup( supergroupId: "search", groupId: "search", @@ -825,7 +1018,7 @@ final class BusinessIntroSetupScreenComponent: Component { var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults? if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { stickerSearchResults = EmojiPagerContentComponent.EmptySearchResults( - text: environment.strings.EmojiSearch_SearchStickersEmptyResult, + text: "No stickers found", iconFile: nil ) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 49dc474717..08812f0de3 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -8459,6 +8459,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G customChatContents.enqueueMessages(messages: messages) strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() case let .businessLinkSetup(link): + if messages.count > 1 { + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: "The message text limit is 4096 characters", actions: [ + TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {}) + ]), in: .window(.root)) + + return + } + var text: String = "" var entities: [MessageTextEntity] = [] if let message = messages.first { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 08caf362ab..57782a3ef5 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -3321,7 +3321,16 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } private func updateCounterTextNode(transition: ContainedViewLayoutTransition) { - if let textInputNode = self.textInputNode, let presentationInterfaceState = self.presentationInterfaceState, let editMessage = presentationInterfaceState.interfaceState.editMessage, let inputTextMaxLength = editMessage.inputTextMaxLength { + var inputTextMaxLength: Int32? + if let presentationInterfaceState = self.presentationInterfaceState { + if let editMessage = presentationInterfaceState.interfaceState.editMessage, let inputTextMaxLengthValue = editMessage.inputTextMaxLength { + inputTextMaxLength = inputTextMaxLengthValue + } else if case let .customChatContents(customChatContents) = presentationInterfaceState.subject, case .businessLinkSetup = customChatContents.kind { + inputTextMaxLength = 4096 + } + } + + if let presentationInterfaceState = self.presentationInterfaceState, let textInputNode = self.textInputNode, let inputTextMaxLength { let textCount = Int32(textInputNode.textView.text.count) let counterColor: UIColor = textCount > inputTextMaxLength ? presentationInterfaceState.theme.chat.inputPanel.panelControlDestructiveColor : presentationInterfaceState.theme.chat.inputPanel.panelControlColor diff --git a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift index 44e04bd4b1..a54d468096 100644 --- a/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/CommandChatInputContextPanelNode.swift @@ -227,7 +227,8 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable { searchQuery: command.searchQuery.flatMap { "/\($0)"}, messageCount: shortcut.totalCount, hideSeparator: false, - hideDate: true + hideDate: true, + hidePeerStatus: true ) )), editing: false, From 9e1fdb3c8ddaa49c86d5692e10a6e3f5e259d98f Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 27 Mar 2024 23:03:32 +0400 Subject: [PATCH 2/2] Remove ad animation --- .../TelegramEngine/Messages/AdMessages.swift | 31 +++++++++++++++++++ .../Sources/ChatControllerRemoveAd.swift | 25 +++++++++++++++ .../ChatInterfaceStateContextMenus.swift | 10 ++++-- 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index f4b5546dbf..5ae2763d04 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -764,6 +764,31 @@ private class AdMessagesHistoryContextImpl { } let _ = signal.start() } + + func remove(opaqueId: Data) { + if var stateValue = self.stateValue { + if let index = stateValue.messages.firstIndex(where: { $0.adAttribute?.opaqueId == opaqueId }) { + stateValue.messages.remove(at: index) + self.stateValue = stateValue + } + } + + let peerId = self.peerId + let _ = (self.account.postbox.transaction { transaction -> Void in + let key = ValueBoxKey(length: 8) + key.setInt64(0, value: peerId.toInt64()) + let id = ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedAdMessageStates, key: key) + guard var cachedState = transaction.retrieveItemCacheEntry(id: id)?.get(CachedState.self) else { + return + } + if let index = cachedState.messages.firstIndex(where: { $0.opaqueId == opaqueId }) { + cachedState.messages.remove(at: index) + if let entry = CodableEntry(cachedState) { + transaction.putItemCacheEntry(id: id, entry: entry) + } + } + }).start() + } } public class AdMessagesHistoryContext { @@ -803,4 +828,10 @@ public class AdMessagesHistoryContext { impl.markAction(opaqueId: opaqueId) } } + + public func remove(opaqueId: Data) { + self.impl.with { impl in + impl.remove(opaqueId: opaqueId) + } + } } diff --git a/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift b/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift new file mode 100644 index 0000000000..c8e4710f9d --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatControllerRemoveAd.swift @@ -0,0 +1,25 @@ +import Foundation +import TelegramPresentationData +import AccountContext +import Postbox +import TelegramCore +import SwiftSignalKit +import Display +import TelegramPresentationData +import PresentationDataUtils +import ChatMessageItemView + +extension ChatControllerImpl { + func removeAd(opaqueId: Data) { + var foundItemNode: ChatMessageItemView? + self.chatDisplayNode.historyNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, let adAttribute = item.message.adAttribute, adAttribute.opaqueId == opaqueId { + foundItemNode = itemNode + } + } + if let foundItemNode, let message = foundItemNode.item?.message { + self.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(Set([message.stableId])) + } + self.chatDisplayNode.historyNode.adMessagesContext?.remove(opaqueId: opaqueId) + } +} diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 897acd9de9..3c59fe4e40 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -499,8 +499,14 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState opaqueId: adAttribute.opaqueId, title: title, options: options, - completed: { - + completed: { [weak interfaceInteraction] in + guard let interfaceInteraction else { + return + } + guard let chatController = interfaceInteraction.chatController() as? ChatControllerImpl else { + return + } + chatController.removeAd(opaqueId: adAttribute.opaqueId) } ) )