mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Add empty search result chat list footer
This commit is contained in:
parent
a5579103f4
commit
dea8f6b48c
@ -593,8 +593,10 @@ private struct NotificationContent: CustomStringConvertible {
|
|||||||
if !self.userInfo.isEmpty {
|
if !self.userInfo.isEmpty {
|
||||||
content.userInfo = self.userInfo
|
content.userInfo = self.userInfo
|
||||||
}
|
}
|
||||||
if !self.attachments.isEmpty {
|
if self.isLockedMessage == nil {
|
||||||
content.attachments = self.attachments
|
if !self.attachments.isEmpty {
|
||||||
|
content.attachments = self.attachments
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
|
@ -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.";
|
"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.";
|
"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";
|
||||||
|
@ -111,6 +111,7 @@ swift_library(
|
|||||||
"//submodules/ComposePollUI",
|
"//submodules/ComposePollUI",
|
||||||
"//submodules/ChatPresentationInterfaceState",
|
"//submodules/ChatPresentationInterfaceState",
|
||||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -380,6 +380,7 @@ public enum ChatListSearchEntryStableId: Hashable {
|
|||||||
case globalPeerId(EnginePeer.Id)
|
case globalPeerId(EnginePeer.Id)
|
||||||
case messageId(EngineMessage.Id, ChatListSearchEntry.MessageSection)
|
case messageId(EngineMessage.Id, ChatListSearchEntry.MessageSection)
|
||||||
case messagePlaceholder(Int32)
|
case messagePlaceholder(Int32)
|
||||||
|
case emptyMessagesFooter
|
||||||
case addContact
|
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 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 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 messagePlaceholder(Int32, ChatListPresentationData, TelegramSearchPeersScope)
|
||||||
|
case emptyMessagesFooter(ChatListPresentationData, TelegramSearchPeersScope, String?)
|
||||||
case addContact(String, PresentationTheme, PresentationStrings)
|
case addContact(String, PresentationTheme, PresentationStrings)
|
||||||
|
|
||||||
public var stableId: ChatListSearchEntryStableId {
|
public var stableId: ChatListSearchEntryStableId {
|
||||||
@ -458,6 +460,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
return .messageId(message.id, section)
|
return .messageId(message.id, section)
|
||||||
case let .messagePlaceholder(index, _, _):
|
case let .messagePlaceholder(index, _, _):
|
||||||
return .messagePlaceholder(index)
|
return .messagePlaceholder(index)
|
||||||
|
case .emptyMessagesFooter:
|
||||||
|
return .emptyMessagesFooter
|
||||||
case .addContact:
|
case .addContact:
|
||||||
return .addContact
|
return .addContact
|
||||||
}
|
}
|
||||||
@ -561,6 +565,21 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
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):
|
case let .addContact(lhsPhoneNumber, lhsTheme, lhsStrings):
|
||||||
if case let .addContact(rhsPhoneNumber, rhsTheme, rhsStrings) = rhs {
|
if case let .addContact(rhsPhoneNumber, rhsTheme, rhsStrings) = rhs {
|
||||||
if lhsPhoneNumber != rhsPhoneNumber {
|
if lhsPhoneNumber != rhsPhoneNumber {
|
||||||
@ -601,7 +620,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
return false
|
return false
|
||||||
case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _, _, _):
|
case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _, _, _):
|
||||||
return lhsIndex <= rhsIndex
|
return lhsIndex <= rhsIndex
|
||||||
case .globalPeer, .message, .messagePlaceholder, .addContact:
|
case .globalPeer, .message, .messagePlaceholder, .emptyMessagesFooter, .addContact:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _, _, _, _):
|
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _, _, _, _):
|
||||||
@ -610,7 +629,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
return false
|
return false
|
||||||
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _, _, _, _):
|
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _, _, _, _):
|
||||||
return lhsIndex <= rhsIndex
|
return lhsIndex <= rhsIndex
|
||||||
case .message, .messagePlaceholder, .addContact:
|
case .message, .messagePlaceholder, .emptyMessagesFooter, .addContact:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case let .message(_, _, _, _, _, _, _, _, lhsKey, _, _, _, _, _, _):
|
case let .message(_, _, _, _, _, _, _, _, lhsKey, _, _, _, _, _, _):
|
||||||
@ -618,6 +637,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
return lhsKey < rhsKey
|
return lhsKey < rhsKey
|
||||||
} else if case .messagePlaceholder = rhs {
|
} else if case .messagePlaceholder = rhs {
|
||||||
return true
|
return true
|
||||||
|
} else if case .emptyMessagesFooter = rhs {
|
||||||
|
return true
|
||||||
} else if case .addContact = rhs {
|
} else if case .addContact = rhs {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
@ -626,11 +647,19 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
case let .messagePlaceholder(lhsIndex, _, _):
|
case let .messagePlaceholder(lhsIndex, _, _):
|
||||||
if case let .messagePlaceholder(rhsIndex, _, _) = rhs {
|
if case let .messagePlaceholder(rhsIndex, _, _) = rhs {
|
||||||
return lhsIndex < rhsIndex
|
return lhsIndex < rhsIndex
|
||||||
|
} else if case .emptyMessagesFooter = rhs {
|
||||||
|
return true
|
||||||
} else if case .addContact = rhs {
|
} else if case .addContact = rhs {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case .emptyMessagesFooter:
|
||||||
|
if case .addContact = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
case .addContact:
|
case .addContact:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -658,7 +687,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
toggleAllPaused: @escaping () -> Void,
|
toggleAllPaused: @escaping () -> Void,
|
||||||
openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void,
|
openStories: @escaping (EnginePeer.Id, AvatarNode) -> Void,
|
||||||
openPublicPosts: @escaping () -> Void,
|
openPublicPosts: @escaping () -> Void,
|
||||||
openMessagesFilter: @escaping (ASDisplayNode) -> Void
|
openMessagesFilter: @escaping (ASDisplayNode) -> Void,
|
||||||
|
switchMessagesFilter: @escaping (TelegramSearchPeersScope) -> Void
|
||||||
) -> ListViewItem {
|
) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
case let .topic(peer, threadInfo, _, theme, strings, expandType):
|
case let .topic(peer, threadInfo, _, theme, strings, expandType):
|
||||||
@ -1128,6 +1158,33 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
openMessagesFilter(sourceNode)
|
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)
|
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):
|
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: {
|
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)
|
interaction.addContact(phoneNumber)
|
||||||
@ -1201,12 +1258,42 @@ private func chatListSearchContainerPreparedRecentTransition(
|
|||||||
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates, isEmpty: isEmpty)
|
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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
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 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), 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)
|
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
|
index += 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
var hasAnyMessages = false
|
||||||
for foundRemoteMessageSet in foundRemoteMessages.0 {
|
for foundRemoteMessageSet in foundRemoteMessages.0 {
|
||||||
for message in foundRemoteMessageSet.messages {
|
for message in foundRemoteMessageSet.messages {
|
||||||
if existingMessageIds.contains(message.id) {
|
if existingMessageIds.contains(message.id) {
|
||||||
@ -2936,10 +3024,22 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO:requiresPremiumForMessaging
|
//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))
|
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
|
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)
|
strongSelf.interaction.switchToFilter(.publicPosts)
|
||||||
}, openMessagesFilter: { sourceNode in
|
}, openMessagesFilter: { sourceNode in
|
||||||
strongSelf.openMessagesFilter(sourceNode: sourceNode)
|
strongSelf.openMessagesFilter(sourceNode: sourceNode)
|
||||||
|
}, switchMessagesFilter: { filter in
|
||||||
|
strongSelf.searchScopePromise.set(.everywhere)
|
||||||
})
|
})
|
||||||
strongSelf.currentEntries = newEntries
|
strongSelf.currentEntries = newEntries
|
||||||
if strongSelf.key == .downloads {
|
if strongSelf.key == .downloads {
|
||||||
|
@ -4,6 +4,8 @@ import AsyncDisplayKit
|
|||||||
import Display
|
import Display
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import ComponentFlow
|
||||||
|
import LottieComponent
|
||||||
|
|
||||||
class ChatListHoleItem: ListViewItem {
|
class ChatListHoleItem: ListViewItem {
|
||||||
let theme: PresentationTheme
|
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<Void, NoError>?, (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<Empty>()
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -419,7 +419,12 @@ final class VideoChatParticipantVideoComponent: Component {
|
|||||||
alphaTransition.setAlpha(view: titleView, alpha: controlsAlpha)
|
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
|
var isEffectivelyPaused = false
|
||||||
if let videoDescription, videoDescription.isPaused {
|
if let videoDescription, videoDescription.isPaused {
|
||||||
|
@ -152,16 +152,15 @@ extension VideoChatScreenComponent.View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:localize
|
|
||||||
let qualityList: [(Int, String)] = [
|
let qualityList: [(Int, String)] = [
|
||||||
(0, "Audio Only"),
|
(0, environment.strings.VideoChat_IncomingVideoQuality_AudioOnly),
|
||||||
(180, "180p"),
|
(180, "180p"),
|
||||||
(360, "360p"),
|
(360, "360p"),
|
||||||
(Int.max, "720p")
|
(Int.max, "720p")
|
||||||
]
|
]
|
||||||
|
|
||||||
let videoQualityTitle = qualityList.first(where: { $0.0 == self.maxVideoQuality })?.1 ?? ""
|
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
|
return nil
|
||||||
}, action: { [weak self] c, _ in
|
}, action: { [weak self] c, _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user