Support expand/collapse in chat search results

This commit is contained in:
Ali 2019-12-03 12:08:35 +04:00
parent 264189bf48
commit 1d1e976328
11 changed files with 3607 additions and 3414 deletions

View File

@ -5158,3 +5158,6 @@ Any member of this group will be able to see messages in the channel.";
"Map.HomeAndWorkTitle" = "Home & Work Addresses";
"Map.HomeAndWorkInfo" = "Telegram uses the Home and Work addresses from your Contact Card.\n\nKeep your Contact Card up to date for quick access to sending Home and Work addresses.";
"Map.SearchNoResultsDescription" = "There were no results for \"%@\".\nTry a new search.";
"ChatList.Search.ShowMore" = "Show more";
"ChatList.Search.ShowLess" = "Show less";

View File

@ -46,16 +46,18 @@ public final class ChatListSearchItemHeader: ListViewItemHeader {
}
public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) {
(node as? ChatListSearchItemHeaderNode)?.update(type: self.type, actionTitle: self.actionTitle, action: self.action)
}
}
public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
private let type: ChatListSearchItemHeaderType
private var type: ChatListSearchItemHeaderType
private var theme: PresentationTheme
private var strings: PresentationStrings
private let actionTitle: String?
private let action: (() -> Void)?
private var actionTitle: String?
private var action: (() -> Void)?
private var validLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)?
private let sectionHeaderNode: ListSectionHeaderNode
@ -71,34 +73,34 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
super.init()
switch type {
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
}
self.sectionHeaderNode.action = actionTitle
@ -112,7 +114,51 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
self.sectionHeaderNode.updateTheme(theme: theme)
}
public func update(type: ChatListSearchItemHeaderType, actionTitle: String?, action: (() -> Void)?) {
self.actionTitle = actionTitle
self.action = action
switch type {
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
}
self.sectionHeaderNode.action = actionTitle
self.sectionHeaderNode.activateAction = action
if let (size, leftInset, rightInset) = self.validLayout {
self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
}
}
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
self.validLayout = (size, leftInset, rightInset)
self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: size)
self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
}

View File

@ -228,17 +228,23 @@ public enum ChatListSearchEntryStableId: Hashable {
}
}
public enum ChatListSearchSectionExpandType {
case none
case expand
case collapse
}
public enum ChatListSearchEntry: Comparable, Identifiable {
case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)
case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData)
case addContact(String, PresentationTheme, PresentationStrings)
public var stableId: ChatListSearchEntryStableId {
switch self {
case let .localPeer(peer, _, _, _, _, _, _, _):
case let .localPeer(peer, _, _, _, _, _, _, _, _):
return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _):
case let .globalPeer(peer, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id)
case let .message(message, _, _, _):
return .messageId(message.id)
@ -249,14 +255,14 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
public static func ==(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool {
switch lhs {
case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder):
if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer.isEqual(rhsPeer) && arePeersEqual(lhsAssociatedPeer, rhsAssociatedPeer) && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 {
case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer.isEqual(rhsPeer) && arePeersEqual(lhsAssociatedPeer, rhsAssociatedPeer) && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
return false
}
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 {
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
return false
@ -302,17 +308,17 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
public static func <(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool {
switch lhs {
case let .localPeer(_, _, _, lhsIndex, _, _, _, _):
if case let .localPeer(_, _, _, rhsIndex, _, _, _, _) = rhs {
case let .localPeer(_, _, _, lhsIndex, _, _, _, _, _):
if case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else {
return true
}
case let .globalPeer(_, _, lhsIndex, _, _, _, _):
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _):
switch rhs {
case .localPeer:
return false
case let .globalPeer(_, _, rhsIndex, _, _, _, _):
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .message, .addContact:
return true
@ -330,9 +336,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
}
}
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem {
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void) -> ListViewItem {
switch self {
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder):
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: Peer
var chatPeer: Peer?
if let associatedPeer = associatedPeer {
@ -377,11 +383,22 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
badge = ContactsPeerItemBadge(count: unreadBadge.0, type: unreadBadge.1 ? .inactive : .active)
}
let header:ChatListSearchItemHeader?
let header: ChatListSearchItemHeader?
if filter.contains(.removeSearchHeader) {
header = nil
} else {
header = ChatListSearchItemHeader(type: .localPeers, theme: theme, strings: strings, actionTitle: nil, action: nil)
let actionTitle: String?
switch expandType {
case .none:
actionTitle = nil
case .expand:
actionTitle = strings.ChatList_Search_ShowMore
case .collapse:
actionTitle = strings.ChatList_Search_ShowLess
}
header = ChatListSearchItemHeader(type: .localPeers, theme: theme, strings: strings, actionTitle: actionTitle, action: actionTitle == nil ? nil : {
toggleExpandLocalResults()
})
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: context.account, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
@ -395,7 +412,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
}
}
})
case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder):
case let .globalPeer(peer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
var enabled = true
if filter.contains(.onlyWriteable) {
enabled = canSendMessagesToPeer(peer.peer)
@ -429,11 +446,22 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
badge = ContactsPeerItemBadge(count: unreadBadge.0, type: unreadBadge.1 ? .inactive : .active)
}
let header:ChatListSearchItemHeader?
let header: ChatListSearchItemHeader?
if filter.contains(.removeSearchHeader) {
header = nil
} else {
header = ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil)
let actionTitle: String?
switch expandType {
case .none:
actionTitle = nil
case .expand:
actionTitle = strings.ChatList_Search_ShowMore
case .collapse:
actionTitle = strings.ChatList_Search_ShowLess
}
header = ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: actionTitle, action: actionTitle == nil ? nil : {
toggleExpandGlobalResults()
})
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: context.account, peerMode: .generalSearch, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
@ -483,12 +511,12 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
}
public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?) -> ChatListSearchContainerTransition {
public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> 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, interaction: interaction, peerContextAction: peerContextAction), 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, interaction: interaction, peerContextAction: peerContextAction), 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, interaction: interaction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults), 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, interaction: interaction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults), directionHint: nil) }
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults)
}
@ -512,6 +540,11 @@ private struct ChatListSearchContainerNodeState: Equatable {
}
}
private struct ChatListSearchContainerNodeSearchState: Equatable {
var expandLocalSearch: Bool = false
var expandGlobalSearch: Bool = false
}
private func doesPeerMatchFilter(peer: Peer, filter: ChatListNodePeersFilter) -> Bool {
var enabled = true
if filter.contains(.onlyWriteable), !canSendMessagesToPeer(peer) {
@ -572,6 +605,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let presentationDataPromise: Promise<ChatListPresentationData>
private var stateValue = ChatListSearchContainerNodeState()
private let statePromise: ValuePromise<ChatListSearchContainerNodeState>
private var searchStateValue = ChatListSearchContainerNodeSearchState()
private let searchStatePromise: ValuePromise<ChatListSearchContainerNodeSearchState>
private let _isSearching = ValuePromise<Bool>(false, ignoreRepeated: true)
override public var isSearching: Signal<Bool, NoError> {
@ -610,6 +645,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
self.statePromise = ValuePromise(self.stateValue, ignoreRepeated: true)
self.searchStatePromise = ValuePromise(self.searchStateValue, ignoreRepeated: true)
super.init()
@ -662,6 +698,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer])?>(value: nil)
let presentationDataPromise = self.presentationDataPromise
let searchStatePromise = self.searchStatePromise
let foundItems = self.searchQuery.get()
|> mapToSignal { query -> Signal<([ChatListSearchEntry], Bool)?, NoError> in
guard let query = query, !query.isEmpty else {
@ -767,8 +804,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
)
}
return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationDataPromise.get())
|> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationData -> ([ChatListSearchEntry], Bool)? in
return combineLatest(accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationDataPromise.get(), searchStatePromise.get())
|> map { accountPeer, foundLocalPeers, foundRemotePeers, foundRemoteMessages, presentationData, searchState -> ([ChatListSearchEntry], Bool)? in
var entries: [ChatListSearchEntry] = []
let isSearching = foundRemotePeers.2 || foundRemoteMessages.1
var index = 0
@ -805,16 +842,59 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
var existingPeerIds = Set<PeerId>()
var totalNumberOfLocalPeers = 0
for renderedPeer in foundLocalPeers.peers {
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, accountPeer) {
if !existingPeerIds.contains(peer.id) {
existingPeerIds.insert(peer.id)
totalNumberOfLocalPeers += 1
}
}
}
for peer in foundRemotePeers.0 {
if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, accountPeer) {
existingPeerIds.insert(peer.peer.id)
totalNumberOfLocalPeers += 1
}
}
var totalNumberOfGlobalPeers = 0
for peer in foundRemotePeers.1 {
if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, accountPeer) {
totalNumberOfGlobalPeers += 1
}
}
existingPeerIds.removeAll()
let localExpandType: ChatListSearchSectionExpandType
let globalExpandType: ChatListSearchSectionExpandType
if totalNumberOfLocalPeers > 5 {
localExpandType = searchState.expandLocalSearch ? .collapse : .expand
} else {
localExpandType = .none
}
if totalNumberOfGlobalPeers > 5 {
globalExpandType = searchState.expandGlobalSearch ? .collapse : .expand
} else {
globalExpandType = .none
}
let lowercasedQuery = query.lowercased()
if presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) {
if !existingPeerIds.contains(accountPeer.id), filteredPeer(accountPeer, accountPeer) {
existingPeerIds.insert(accountPeer.id)
entries.append(.localPeer(accountPeer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder))
entries.append(.localPeer(accountPeer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
index += 1
}
}
var numberOfLocalPeers = 0
for renderedPeer in foundLocalPeers.peers {
if case .expand = localExpandType, numberOfLocalPeers >= 5 {
break
}
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, accountPeer) {
if !existingPeerIds.contains(peer.id) {
existingPeerIds.insert(peer.id)
@ -822,26 +902,38 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
if let associatedPeerId = peer.associatedPeerId {
associatedPeer = renderedPeer.peers[associatedPeerId]
}
entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder))
entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
index += 1
numberOfLocalPeers += 1
}
}
}
for peer in foundRemotePeers.0 {
if case .expand = localExpandType, numberOfLocalPeers >= 5 {
break
}
if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, accountPeer) {
existingPeerIds.insert(peer.peer.id)
entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder))
entries.append(.localPeer(peer.peer, nil, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
index += 1
numberOfLocalPeers += 1
}
}
var numberOfGlobalPeers = 0
index = 0
for peer in foundRemotePeers.1 {
if case .expand = globalExpandType, numberOfGlobalPeers >= 5 {
break
}
if !existingPeerIds.contains(peer.peer.id), filteredPeer(peer.peer, accountPeer) {
existingPeerIds.insert(peer.peer.id)
entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder))
entries.append(.globalPeer(peer, nil, index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalExpandType))
index += 1
numberOfGlobalPeers += 1
}
}
@ -1026,7 +1118,26 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let previousEntries = previousSearchItems.swap(entriesAndFlags?.0)
let firstTime = previousEntries == nil
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entriesAndFlags?.0 ?? [], displayingResults: entriesAndFlags?.0 != nil, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: filter, interaction: interaction, peerContextAction: peerContextAction)
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entriesAndFlags?.0 ?? [], displayingResults: entriesAndFlags?.0 != nil, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: filter, interaction: interaction, peerContextAction: peerContextAction,
toggleExpandLocalResults: {
guard let strongSelf = self else {
return
}
strongSelf.updateSearchState { state in
var state = state
state.expandLocalSearch = !state.expandLocalSearch
return state
}
}, toggleExpandGlobalResults: {
guard let strongSelf = self else {
return
}
strongSelf.updateSearchState { state in
var state = state
state.expandGlobalSearch = !state.expandGlobalSearch
return state
}
})
strongSelf.enqueueTransition(transition, firstTime: firstTime)
}
}))
@ -1087,6 +1198,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
}
private func updateSearchState(_ f: (ChatListSearchContainerNodeSearchState) -> ChatListSearchContainerNodeSearchState) {
let state = f(self.searchStateValue)
if state != self.searchStateValue {
self.searchStateValue = state
self.searchStatePromise.set(state)
}
}
override public func searchTextUpdated(text: String) {
let searchQuery: String? = !text.isEmpty ? text : nil
self.interaction?.searchTextHighightState = searchQuery

View File

@ -3041,6 +3041,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
}
if headerNode.item !== item {
item.updateNode(headerNode, previous: nil, next: nil)
headerNode.item = item
}
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)
headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: true)
headerNode.internalStickLocationDistance = stickLocationDistance
@ -3058,6 +3062,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: transition.0)
} else {
let headerNode = item.node()
if headerNode.item !== item {
item.updateNode(headerNode, previous: nil, next: nil)
headerNode.item = item
}
headerNode.updateFlashingOnScrolling(flashing, animated: false)
headerNode.frame = headerFrame
headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)

View File

@ -26,6 +26,8 @@ open class ListViewItemHeaderNode: ASDisplayNode {
final var internalStickLocationDistance: CGFloat = 0.0
private var isFlashingOnScrolling = false
var item: ListViewItemHeader?
func updateInternalStickLocationDistanceFactor(_ factor: CGFloat, animated: Bool) {
self.internalStickLocationDistanceFactor = factor
}

View File

@ -79,7 +79,9 @@ public final class HashtagSearchController: TelegramBaseController {
let previousEntries = previousSearchItems.swap(entries)
let firstTime = previousEntries == nil
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, peerContextAction: nil)
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, peerContextAction: nil, toggleExpandLocalResults: {
}, toggleExpandGlobalResults: {
})
strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime)
}
})

View File

@ -9,6 +9,7 @@ private let actionFont = Font.medium(13.0)
public final class ListSectionHeaderNode: ASDisplayNode {
private let label: ImmediateTextNode
private var actionButtonLabel: ImmediateTextNode?
private var actionButton: HighlightableButtonNode?
private var theme: PresentationTheme
@ -28,17 +29,26 @@ public final class ListSectionHeaderNode: ASDisplayNode {
didSet {
if (self.action != nil) != (self.actionButton != nil) {
if let _ = self.action {
let actionButtonLabel = ImmediateTextNode()
self.addSubnode(actionButtonLabel)
self.actionButtonLabel = actionButtonLabel
let actionButton = HighlightableButtonNode()
self.addSubnode(actionButton)
self.actionButton = actionButton
actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
} else if let actionButton = self.actionButton {
self.actionButton = nil
actionButton.removeFromSupernode()
} else {
if let actionButtonLabel = self.actionButtonLabel {
self.actionButtonLabel = nil
actionButtonLabel.removeFromSupernode()
}
if let actionButton = self.actionButton {
self.actionButton = nil
actionButton.removeFromSupernode()
}
}
}
if let action = self.action {
self.actionButton?.setAttributedTitle(NSAttributedString(string: action, font: actionFont, textColor: self.theme.chatList.sectionHeaderTextColor), for: [])
self.actionButtonLabel?.attributedText = NSAttributedString(string: action, font: actionFont, textColor: self.theme.chatList.sectionHeaderTextColor)
}
if let (size, leftInset, rightInset) = self.validLayout {
@ -70,7 +80,7 @@ public final class ListSectionHeaderNode: ASDisplayNode {
self.backgroundColor = theme.chatList.sectionHeaderFillColor
if let action = self.action {
self.actionButton?.setAttributedTitle(NSAttributedString(string: action, font: actionFont, textColor: self.theme.chatList.sectionHeaderTextColor), for: [])
self.actionButtonLabel?.attributedText = NSAttributedString(string: action, font: actionFont, textColor: self.theme.chatList.sectionHeaderTextColor)
}
if let (size, leftInset, rightInset) = self.validLayout {
@ -84,8 +94,9 @@ public final class ListSectionHeaderNode: ASDisplayNode {
let labelSize = self.label.updateLayout(CGSize(width: max(0.0, size.width - leftInset - rightInset - 18.0), height: size.height))
self.label.frame = CGRect(origin: CGPoint(x: leftInset + 16.0, y: 6.0 + UIScreenPixel), size: labelSize)
if let actionButton = self.actionButton {
let buttonSize = actionButton.measure(CGSize(width: size.width, height: size.height))
if let actionButton = self.actionButton, let actionButtonLabel = self.actionButtonLabel {
let buttonSize = actionButtonLabel.updateLayout(CGSize(width: size.width, height: size.height))
actionButtonLabel.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize)
actionButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize)
}
}

View File

@ -448,12 +448,12 @@ public final class WalletStrings: Equatable {
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
}
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)