diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 266ac1fa0d..f8c5ab6648 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -593,8 +593,10 @@ private struct NotificationContent: CustomStringConvertible { if !self.userInfo.isEmpty { content.userInfo = self.userInfo } - if !self.attachments.isEmpty { - content.attachments = self.attachments + if self.isLockedMessage == nil { + if !self.attachments.isEmpty { + content.attachments = self.attachments + } } if #available(iOS 15.0, *) { diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index fc45da72d2..5b5404e004 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13576,3 +13576,8 @@ Sorry for the inconvenience."; "Notification.StarGift.Subtitle.Downgraded" = "This gift was downgraded because a request to refund the payment related to this gift was made, and the money was returned."; "Gift.View.KeepOrUpgradeDescription" = "You can keep this gift or upgrade it."; + +"VideoChat.IncomingVideoQuality.AudioOnly" = "Audio Only"; +"VideoChat.IncomingVideoQuality.Title" = "Receive Video Quality"; + +"ChatList.EmptyResult.SearchInAll" = "Search in All Messages"; diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 3d020f0fea..4d2512beef 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -111,6 +111,7 @@ swift_library( "//submodules/ComposePollUI", "//submodules/ChatPresentationInterfaceState", "//submodules/ShimmerEffect:ShimmerEffect", + "//submodules/TelegramUI/Components/LottieComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 2bdbf352a0..9991b69e9e 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -380,6 +380,7 @@ public enum ChatListSearchEntryStableId: Hashable { case globalPeerId(EnginePeer.Id) case messageId(EngineMessage.Id, ChatListSearchEntry.MessageSection) case messagePlaceholder(Int32) + case emptyMessagesFooter case addContact } @@ -442,6 +443,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType, PeerStoryStats?, Bool, String?) case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, EngineMessageHistoryThread.Info?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int64, isFirstInList: Bool)?, MessageSection, Bool, PeerStoryStats?, Bool, TelegramSearchPeersScope) case messagePlaceholder(Int32, ChatListPresentationData, TelegramSearchPeersScope) + case emptyMessagesFooter(ChatListPresentationData, TelegramSearchPeersScope, String?) case addContact(String, PresentationTheme, PresentationStrings) public var stableId: ChatListSearchEntryStableId { @@ -458,6 +460,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return .messageId(message.id, section) case let .messagePlaceholder(index, _, _): return .messagePlaceholder(index) + case .emptyMessagesFooter: + return .emptyMessagesFooter case .addContact: return .addContact } @@ -561,6 +565,21 @@ public enum ChatListSearchEntry: Comparable, Identifiable { } else { return false } + case let .emptyMessagesFooter(lhsPresentationData, lhsSearchScope, lhsQuery): + if case let .emptyMessagesFooter(rhsPresentationData, rhsSearchScope, rhsQuery) = rhs { + if lhsPresentationData !== rhsPresentationData { + return false + } + if lhsSearchScope != rhsSearchScope { + return false + } + if lhsQuery != rhsQuery { + return false + } + return true + } else { + return false + } case let .addContact(lhsPhoneNumber, lhsTheme, lhsStrings): if case let .addContact(rhsPhoneNumber, rhsTheme, rhsStrings) = rhs { if lhsPhoneNumber != rhsPhoneNumber { @@ -601,7 +620,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return false case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _, _, _): return lhsIndex <= rhsIndex - case .globalPeer, .message, .messagePlaceholder, .addContact: + case .globalPeer, .message, .messagePlaceholder, .emptyMessagesFooter, .addContact: return true } case let .globalPeer(_, _, lhsIndex, _, _, _, _, _, _, _, _): @@ -610,7 +629,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return false case let .globalPeer(_, _, rhsIndex, _, _, _, _, _, _, _, _): return lhsIndex <= rhsIndex - case .message, .messagePlaceholder, .addContact: + case .message, .messagePlaceholder, .emptyMessagesFooter, .addContact: return true } case let .message(_, _, _, _, _, _, _, _, lhsKey, _, _, _, _, _, _): @@ -618,6 +637,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { return lhsKey < rhsKey } else if case .messagePlaceholder = rhs { return true + } else if case .emptyMessagesFooter = rhs { + return true } else if case .addContact = rhs { return true } else { @@ -626,11 +647,19 @@ public enum ChatListSearchEntry: Comparable, Identifiable { case let .messagePlaceholder(lhsIndex, _, _): if case let .messagePlaceholder(rhsIndex, _, _) = rhs { return lhsIndex < rhsIndex + } else if case .emptyMessagesFooter = rhs { + return true } else if case .addContact = rhs { return true } else { return false } + case .emptyMessagesFooter: + if case .addContact = rhs { + return true + } else { + return false + } case .addContact: return false } @@ -658,7 +687,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable { toggleAllPaused: @escaping () -> Void, openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void, openPublicPosts: @escaping () -> Void, - openMessagesFilter: @escaping (ASDisplayNode) -> Void + openMessagesFilter: @escaping (ASDisplayNode) -> Void, + switchMessagesFilter: @escaping (TelegramSearchPeersScope) -> Void ) -> ListViewItem { switch self { case let .topic(peer, threadInfo, _, theme, strings, expandType): @@ -1128,6 +1158,33 @@ public enum ChatListSearchEntry: Comparable, Identifiable { openMessagesFilter(sourceNode) }) return ChatListItem(presentationData: presentationData, context: context, chatListLocation: location, filterData: nil, index: EngineChatList.Item.Index.chatList(ChatListIndex(pinningIndex: nil, messageIndex: MessageIndex(id: MessageId(peerId: PeerId(0), namespace: Namespaces.Message.Cloud, id: 0), timestamp: 0))), content: .loading, editing: false, hasActiveRevealControls: false, selected: false, header: header, enableContextActions: false, hiddenOffset: false, interaction: interaction) + case let .emptyMessagesFooter(presentationData, searchScope, searchQuery): + var actionTitle: String? + let filterTitle: String + switch searchScope { + case .everywhere: + filterTitle = presentationData.strings.ChatList_Search_Messages_AllChats + case .channels: + filterTitle = presentationData.strings.ChatList_Search_Messages_Channels + case .groups: + filterTitle = presentationData.strings.ChatList_Search_Messages_GroupChats + case .privateChats: + filterTitle = presentationData.strings.ChatList_Search_Messages_PrivateChats + } + actionTitle = "\(filterTitle) <" + + let header = ChatListSearchItemHeader(type: .messages(location: nil), theme: presentationData.theme, strings: presentationData.strings, actionTitle: actionTitle, action: { sourceNode in + openMessagesFilter(sourceNode) + }) + return ChatListSearchEmptyFooterItem( + theme: presentationData.theme, + strings: presentationData.strings, + header: header, + searchQuery: searchQuery, + searchAllMessages: searchScope == .everywhere ? nil : { + switchMessagesFilter(.everywhere) + } + ) case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(context: context, theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { interaction.addContact(phoneNumber) @@ -1201,12 +1258,42 @@ private func chatListSearchContainerPreparedRecentTransition( return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates, isEmpty: isEmpty) } -public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void, openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void, openPublicPosts: @escaping () -> Void, openMessagesFilter: @escaping (ASDisplayNode) -> Void) -> ChatListSearchContainerTransition { +public func chatListSearchContainerPreparedTransition( + from fromEntries: [ChatListSearchEntry], + to toEntries: [ChatListSearchEntry], + displayingResults: Bool, + isEmpty: Bool, + isLoading: Bool, + animated: Bool, + context: AccountContext, + presentationData: PresentationData, + enableHeaders: Bool, + filter: ChatListNodePeersFilter, + requestPeerType: [ReplyMarkupButtonRequestPeerType]?, + location: ChatListControllerLocation, + key: ChatListSearchPaneKey, + tagMask: EngineMessage.Tags?, + interaction: ChatListNodeInteraction, + listInteraction: ListMessageItemInteraction, + peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, + toggleExpandLocalResults: @escaping () -> Void, + toggleExpandGlobalResults: @escaping () -> Void, + searchPeer: @escaping (EnginePeer) -> Void, + searchQuery: String?, + searchOptions: ChatListSearchOptions?, + messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int64, isFirstInList: Bool)?) -> Void)?, + openClearRecentlyDownloaded: @escaping () -> Void, + toggleAllPaused: @escaping () -> Void, + openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void, + openPublicPosts: @escaping () -> Void, + openMessagesFilter: @escaping (ASDisplayNode) -> Void, + switchMessagesFilter: @escaping (TelegramSearchPeersScope) -> Void +) -> ChatListSearchContainerTransition { 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, enableHeaders: enableHeaders, filter: filter, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused, openStories: openStories, openPublicPosts: openPublicPosts, openMessagesFilter: openMessagesFilter), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused, openStories: openStories, openPublicPosts: openPublicPosts, openMessagesFilter: openMessagesFilter), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused, openStories: openStories, openPublicPosts: openPublicPosts, openMessagesFilter: openMessagesFilter, switchMessagesFilter: switchMessagesFilter), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, requestPeerType: requestPeerType, location: location, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused, openStories: openStories, openPublicPosts: openPublicPosts, openMessagesFilter: openMessagesFilter, switchMessagesFilter: switchMessagesFilter), directionHint: nil) } return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated) } @@ -2912,6 +2999,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { index += 1 } } else { + var hasAnyMessages = false for foundRemoteMessageSet in foundRemoteMessages.0 { for message in foundRemoteMessageSet.messages { if existingMessageIds.contains(message.id) { @@ -2936,10 +3024,22 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } //TODO:requiresPremiumForMessaging + hasAnyMessages = true entries.append(.message(message, peer, foundRemoteMessageSet.readCounters[message.id.peerId], foundRemoteMessageSet.threadsData[message.id]?.info, presentationData, foundRemoteMessageSet.totalCount, selectionState?.contains(message.id), headerId == firstHeaderId, .index(message.index), nil, .generic, false, nil, false, searchScope)) index += 1 } } + if !hasAnyMessages { + switch searchScope { + case .everywhere: + break + default: + if let data = context.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_empty_search_footer"] != nil { + } else { + entries.append(.emptyMessagesFooter(presentationData, searchScope, query)) + } + } + } } } @@ -3444,6 +3544,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { strongSelf.interaction.switchToFilter(.publicPosts) }, openMessagesFilter: { sourceNode in strongSelf.openMessagesFilter(sourceNode: sourceNode) + }, switchMessagesFilter: { filter in + strongSelf.searchScopePromise.set(.everywhere) }) strongSelf.currentEntries = newEntries if strongSelf.key == .downloads { diff --git a/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift b/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift index 622e292c90..4434f59e2f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListHoleItem.swift @@ -4,6 +4,8 @@ import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData +import ComponentFlow +import LottieComponent class ChatListHoleItem: ListViewItem { let theme: PresentationTheme @@ -78,3 +80,252 @@ class ChatListHoleItemNode: ListViewItemNode { } } } + +class ChatListSearchEmptyFooterItem: ListViewItem { + let theme: PresentationTheme + let strings: PresentationStrings + let searchQuery: String? + let searchAllMessages: (() -> Void)? + + let header: ListViewItemHeader? + let selectable: Bool = false + + init(theme: PresentationTheme, strings: PresentationStrings, header: ListViewItemHeader?, searchQuery: String?, searchAllMessages: (() -> Void)?) { + self.theme = theme + self.strings = strings + self.header = header + self.searchQuery = searchQuery + self.searchAllMessages = searchAllMessages + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = ChatListSearchEmptyFooterItemNode() + let (layout, apply) = node.asyncLayout()(self, params) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + assert(node() is ChatListSearchEmptyFooterItemNode) + if let nodeValue = node() as? ChatListSearchEmptyFooterItemNode { + + let layout = nodeValue.asyncLayout() + async { + let (nodeLayout, apply) = layout(self, params) + Queue.mainQueue().async { + completion(nodeLayout, { _ in + apply() + }) + } + } + } + } + } +} + +class ChatListSearchEmptyFooterItemNode: ListViewItemNode { + private let contentNode: ASDisplayNode + private let titleNode: TextNode + private let textNode: TextNode + private let searchAllMessagesButton: HighlightableButtonNode + private let searchAllMessagesTitle: TextNode + + private let icon = ComponentView() + + private var item: ChatListSearchEmptyFooterItem? + + required init() { + self.contentNode = ASDisplayNode() + self.titleNode = TextNode() + self.textNode = TextNode() + + self.searchAllMessagesButton = HighlightableButtonNode() + self.searchAllMessagesTitle = TextNode() + self.searchAllMessagesTitle.isUserInteractionEnabled = false + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.contentNode) + self.contentNode.addSubnode(self.titleNode) + self.contentNode.addSubnode(self.textNode) + + self.contentNode.addSubnode(self.searchAllMessagesButton) + self.searchAllMessagesButton.addSubnode(self.searchAllMessagesTitle) + + self.searchAllMessagesButton.addTarget(self, action: #selector(self.searchAllMessagesButtonPressed), forControlEvents: .touchUpInside) + + self.wantsTrailingItemSpaceUpdates = true + } + + @objc private func searchAllMessagesButtonPressed() { + self.item?.searchAllMessages?() + } + + override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + let layout = self.asyncLayout() + let (_, apply) = layout(item as! ChatListSearchEmptyFooterItem, params) + apply() + } + + override func headers() -> [ListViewItemHeader]? { + if let item = self.item { + return item.header.flatMap { [$0] } + } else { + return nil + } + } + + override func updateTrailingItemSpace(_ trailingItemSpace: CGFloat, transition: ContainedViewLayoutTransition) { + var contentFrame = self.contentNode.frame + contentFrame.origin.y = max(0.0, floor(trailingItemSpace * 0.5)) + self.contentNode.frame = contentFrame + } + + func asyncLayout() -> (_ item: ChatListSearchEmptyFooterItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, () -> Void) { + let makeTitleNodeLayout = TextNode.asyncLayout(self.titleNode) + let makeTextNodeLayout = TextNode.asyncLayout(self.textNode) + let makeSearchAllMessagesTitleLayout = TextNode.asyncLayout(self.searchAllMessagesTitle) + + return { [weak self] item, params in + let titleLayout = makeTitleNodeLayout(TextNodeLayoutArguments( + attributedString: NSAttributedString(string: item.strings.ChatList_Search_NoResults, font: Font.semibold(17.0), textColor: item.theme.list.freeTextColor), + maximumNumberOfLines: 1, + truncationType: .end, + constrainedSize: CGSize(width: params.width - params.leftInset * 2.0 - 12.0 * 2.0, height: 1000.0) + )) + + let textValue: String + if let searchQuery = item.searchQuery { + textValue = item.strings.ChatList_Search_NoResultsQueryDescription(searchQuery).string + } else { + textValue = item.strings.ChatList_Search_NoResults + } + + let textLayout = makeTextNodeLayout(TextNodeLayoutArguments( + attributedString: NSAttributedString(string: textValue, font: Font.regular(16.0), textColor: item.theme.list.freeTextColor), + maximumNumberOfLines: 0, + truncationType: .end, + constrainedSize: CGSize(width: params.width - params.leftInset * 2.0 - 12.0 * 2.0, height: 1000.0), + alignment: .center, + lineSpacing: 0.1 + )) + + let searchAllMessagesTitleLayout = makeSearchAllMessagesTitleLayout(TextNodeLayoutArguments( + attributedString: NSAttributedString(string: item.strings.ChatList_EmptyResult_SearchInAll, font: Font.regular(17.0), textColor: item.theme.list.itemAccentColor), + maximumNumberOfLines: 1, + truncationType: .end, + constrainedSize: CGSize(width: params.width - params.leftInset * 2.0 - 12.0 * 2.0, height: 1000.0) + )) + + var contentHeight: CGFloat = 0.0 + + let topInset: CGFloat = 40.0 + let bottomInset: CGFloat = 10.0 + let iconSpacing: CGFloat = 20.0 + let titleSpacing: CGFloat = 6.0 + + let buttonSpacing: CGFloat = 14.0 + let buttonInset: CGFloat = 11.0 + + let iconSize = CGSize(width: 128.0, height: 128.0) + + contentHeight += topInset + contentHeight += iconSize.height + contentHeight += iconSpacing + contentHeight += titleLayout.0.size.height + contentHeight += titleSpacing + contentHeight += textLayout.0.size.height + + if item.searchAllMessages != nil { + contentHeight += buttonSpacing + contentHeight += buttonInset + contentHeight += searchAllMessagesTitleLayout.0.size.height + contentHeight += buttonInset + } + + contentHeight += bottomInset + + let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: contentHeight), insets: UIEdgeInsets()) + + return (layout, { [weak self] in + guard let self else { + return + } + + self.item = item + self.contentSize = layout.contentSize + self.insets = layout.insets + + let _ = titleLayout.1() + let _ = textLayout.1() + let _ = searchAllMessagesTitleLayout.1() + + var contentY: CGFloat = 0.0 + contentY += topInset + + let _ = self.icon.update( + transition: .immediate, + component: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent( + name: "ChatListNoResults" + ), + color: nil, + placeholderColor: nil, + startingPosition: .begin, + size: iconSize, + renderingScale: nil, + loop: false, + playOnce: nil + )), + environment: {}, containerSize: iconSize + ) + let iconFrame = CGRect(origin: CGPoint(x: floor((params.width - iconSize.width) * 0.5), y: contentY), size: iconSize) + if let iconView = self.icon.view { + if iconView.superview == nil { + self.contentNode.view.addSubview(iconView) + } + iconView.frame = iconFrame + } + + contentY += iconSize.height + contentY += iconSpacing + + let titleFrame = CGRect(origin: CGPoint(x: floor((params.width - titleLayout.0.size.width) * 0.5), y: contentY), size: titleLayout.0.size) + self.titleNode.frame = titleFrame + contentY += titleLayout.0.size.height + contentY += titleSpacing + + let textFrame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.0.size.width) * 0.5), y: contentY), size: textLayout.0.size) + self.textNode.frame = textFrame + contentY += textLayout.0.size.height + + if item.searchAllMessages != nil { + contentY += buttonSpacing + let searchAllMessagesButtonFrame = CGRect(origin: CGPoint(x: floor((params.width - searchAllMessagesTitleLayout.0.size.width) * 0.5), y: contentY), size: CGSize(width: searchAllMessagesTitleLayout.0.size.width, height: searchAllMessagesTitleLayout.0.size.height + buttonInset * 2.0)) + contentY += searchAllMessagesTitleLayout.0.size.height + buttonInset * 2.0 + + self.searchAllMessagesButton.frame = searchAllMessagesButtonFrame + self.searchAllMessagesTitle.frame = CGRect(origin: CGPoint(x: 0.0, y: buttonInset), size: searchAllMessagesTitleLayout.0.size) + contentY += buttonInset + contentY += searchAllMessagesTitleLayout.0.size.height + contentY += buttonInset + } + + contentY += bottomInset + + let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: self.contentNode.frame.minY), size: CGSize(width: params.width, height: contentHeight)) + self.contentNode.frame = contentFrame + }) + } + } +} diff --git a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift index 617d4df551..c3eea4b4eb 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatParticipantVideoComponent.swift @@ -419,7 +419,12 @@ final class VideoChatParticipantVideoComponent: Component { alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha) } - let videoDescription: GroupCallParticipantsContext.Participant.VideoDescription? = component.maxVideoQuality == 0 ? nil : (component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription) + let videoDescription: GroupCallParticipantsContext.Participant.VideoDescription? + if component.isMyPeer && component.isPresentation { + videoDescription = nil + } else { + videoDescription = component.maxVideoQuality == 0 ? nil : (component.isPresentation ? component.participant.presentationDescription : component.participant.videoDescription) + } var isEffectivelyPaused = false if let videoDescription, videoDescription.isPaused { diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift index 1e05a29420..4bbd537ed0 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenMoreMenu.swift @@ -152,16 +152,15 @@ extension VideoChatScreenComponent.View { } } - //TODO:localize let qualityList: [(Int, String)] = [ - (0, "Audio Only"), + (0, environment.strings.VideoChat_IncomingVideoQuality_AudioOnly), (180, "180p"), (360, "360p"), (Int.max, "720p") ] let videoQualityTitle = qualityList.first(where: { $0.0 == self.maxVideoQuality })?.1 ?? "" - items.append(.action(ContextMenuActionItem(text: "Receive Video Quality", textColor: .primary, textLayout: .secondLineWithValue(videoQualityTitle), icon: { _ in + items.append(.action(ContextMenuActionItem(text: environment.strings.VideoChat_IncomingVideoQuality_Title, textColor: .primary, textLayout: .secondLineWithValue(videoQualityTitle), icon: { _ in return nil }, action: { [weak self] c, _ in guard let self else {