Various improvements

This commit is contained in:
Isaac 2026-03-31 20:24:24 +08:00
parent 18abe71dca
commit 34477605b1
35 changed files with 600 additions and 268 deletions

View file

@ -815,7 +815,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
if let searchTerm = searchTerm { if let searchTerm = searchTerm {
if !searchTerm.isEmpty { if !searchTerm.isEmpty {
for renderedPeer in transaction.searchPeers(query: searchTerm) { for renderedPeer in transaction.searchPeers(query: searchTerm, predicate: nil) {
if let peer = renderedPeer.peer, !(peer is TelegramSecretChat), !peer.isDeleted { if let peer = renderedPeer.peer, !(peer is TelegramSecretChat), !peer.isDeleted {
peers.append(peer) peers.append(peer)
} }
@ -988,7 +988,7 @@ private final class WidgetIntentHandler {
if let searchTerm = searchTerm { if let searchTerm = searchTerm {
if !searchTerm.isEmpty { if !searchTerm.isEmpty {
for renderedPeer in transaction.searchPeers(query: searchTerm) { for renderedPeer in transaction.searchPeers(query: searchTerm, predicate: nil) {
if let peer = renderedPeer.peer, !(peer is TelegramSecretChat), !peer.isDeleted { if let peer = renderedPeer.peer, !(peer is TelegramSecretChat), !peer.isDeleted {
peers.append(peer) peers.append(peer)
} }

View file

@ -34,7 +34,7 @@ private enum InnerState: Equatable {
public final class AuthorizationSequenceController: NavigationController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { public final class AuthorizationSequenceController: NavigationController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
static func navigationBarTheme(_ theme: PresentationTheme) -> NavigationBarTheme { static func navigationBarTheme(_ theme: PresentationTheme) -> NavigationBarTheme {
return NavigationBarTheme(overallDarkAppearance: theme.overallDarkAppearance, buttonColor: theme.chat.inputPanel.panelControlColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, opaqueBackgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor, edgeEffectColor: .clear, style: .glass) return NavigationBarTheme(overallDarkAppearance: theme.overallDarkAppearance, buttonColor: theme.chat.inputPanel.panelControlColor, disabledButtonColor: theme.intro.disabledTextColor, primaryTextColor: theme.intro.primaryTextColor, backgroundColor: .clear, opaqueBackgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: theme.rootController.navigationBar.badgeBackgroundColor, badgeStrokeColor: theme.rootController.navigationBar.badgeStrokeColor, badgeTextColor: theme.rootController.navigationBar.badgeTextColor, edgeEffectColor: .clear, accentButtonColor: theme.list.itemCheckColors.fillColor, accentForegroundColor: theme.list.itemCheckColors.foregroundColor, style: .glass)
} }
private let sharedContext: SharedAccountContext private let sharedContext: SharedAccountContext

View file

@ -1949,7 +1949,21 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
//filter.insert(.excludeRecent) //filter.insert(.excludeRecent)
} }
let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, requestPeerType: nil, location: effectiveLocation, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in var folder: (Int32, String)?
if let folders = self.controller?.tabContainerData?.0 {
switch self.effectiveContainerNode.currentItemFilter {
case .all:
break
case let .filter(id):
if let value = folders.first(where: { $0.id == .filter(id) }) {
if case let .filter(_, text, _) = value {
folder = (id, text.text)
}
}
}
}
let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, requestPeerType: nil, location: effectiveLocation, folder: folder, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in
self?.requestOpenPeerFromSearch?(peer, threadId, dismissSearch) self?.requestOpenPeerFromSearch?(peer, threadId, dismissSearch)
}, openDisabledPeer: { _, _, _ in }, openDisabledPeer: { _, _, _ in
}, openRecentPeerOptions: { [weak self] peer in }, openRecentPeerOptions: { [weak self] peer in

View file

@ -43,6 +43,7 @@ import ComponentDisplayAdapters
private enum ChatListTokenId: Int32 { private enum ChatListTokenId: Int32 {
case archive case archive
case folder
case forum case forum
case filter case filter
case peer case peer
@ -102,6 +103,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let peersFilter: ChatListNodePeersFilter private let peersFilter: ChatListNodePeersFilter
private let requestPeerType: [ReplyMarkupButtonRequestPeerType]? private let requestPeerType: [ReplyMarkupButtonRequestPeerType]?
private var location: ChatListControllerLocation private var location: ChatListControllerLocation
private var folder: (Int32, String)?
private let displaySearchFilters: Bool private let displaySearchFilters: Bool
private let hasDownloads: Bool private let hasDownloads: Bool
private var interaction: ChatListSearchInteraction? private var interaction: ChatListSearchInteraction?
@ -165,7 +167,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var recentAppsDisposable: Disposable? private var recentAppsDisposable: Disposable?
private var refreshedGlobalPostSearchStateDisposable: Disposable? private var refreshedGlobalPostSearchStateDisposable: Disposable?
public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, parentController: @escaping () -> ViewController?) { public init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filter: ChatListNodePeersFilter, requestPeerType: [ReplyMarkupButtonRequestPeerType]?, location: ChatListControllerLocation, folder: (Int32, String)?, displaySearchFilters: Bool, hasDownloads: Bool, initialFilter: ChatListSearchFilter = .chats, openPeer originalOpenPeer: @escaping (EnginePeer, EnginePeer?, Int64?, Bool) -> Void, openDisabledPeer: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void, openRecentPeerOptions: @escaping (EnginePeer) -> Void, openMessage originalOpenMessage: @escaping (EnginePeer, Int64?, EngineMessage.Id, Bool) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, parentController: @escaping () -> ViewController?) {
var initialFilter = initialFilter var initialFilter = initialFilter
if case .chats = initialFilter, case .forum = location { if case .chats = initialFilter, case .forum = location {
initialFilter = .topics initialFilter = .topics
@ -175,6 +177,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.peersFilter = filter self.peersFilter = filter
self.requestPeerType = requestPeerType self.requestPeerType = requestPeerType
self.location = location self.location = location
self.folder = folder
self.displaySearchFilters = displaySearchFilters self.displaySearchFilters = displaySearchFilters
self.hasDownloads = hasDownloads self.hasDownloads = hasDownloads
self.navigationController = navigationController self.navigationController = navigationController
@ -194,6 +199,11 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.paneContainerNode.clipsToBounds = true self.paneContainerNode.clipsToBounds = true
super.init() super.init()
if let folder {
self.searchOptionsValue = self.currentSearchOptions.withUpdatedFolder(folder)
self.searchOptions.set(.single(self.currentSearchOptions))
}
self.backgroundColor = filter.contains(.excludeRecent) ? nil : self.presentationData.theme.chatList.backgroundColor self.backgroundColor = filter.contains(.excludeRecent) ? nil : self.presentationData.theme.chatList.backgroundColor
@ -588,7 +598,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
private var currentSearchOptions: ChatListSearchOptions { private var currentSearchOptions: ChatListSearchOptions {
return self.searchOptionsValue ?? ChatListSearchOptions(peer: nil, date: nil) return self.searchOptionsValue ?? ChatListSearchOptions(peer: nil, date: nil, folder: nil)
} }
public override func searchTokensUpdated(tokens: [SearchBarToken]) { public override func searchTokensUpdated(tokens: [SearchBarToken]) {
@ -609,12 +619,19 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
if !tokensIdSet.contains(ChatListTokenId.peer.rawValue) && updatedOptions?.peer != nil { if !tokensIdSet.contains(ChatListTokenId.peer.rawValue) && updatedOptions?.peer != nil {
updatedOptions = updatedOptions?.withUpdatedPeer(nil) updatedOptions = updatedOptions?.withUpdatedPeer(nil)
} }
if !tokensIdSet.contains(ChatListTokenId.folder.rawValue) && updatedOptions?.folder != nil {
updatedOptions = updatedOptions?.withUpdatedFolder(nil)
self.folder = nil
}
self.updateSearchOptions(updatedOptions) self.updateSearchOptions(updatedOptions)
} }
private func updateSearchOptions(_ options: ChatListSearchOptions?, clearQuery: Bool = false) { private func updateSearchOptions(_ options: ChatListSearchOptions?, clearQuery: Bool = false) {
var options = options var options = options
var tokens: [SearchBarToken] = [] var tokens: [SearchBarToken] = []
if let folder = self.folder {
tokens.append(SearchBarToken(id: ChatListTokenId.folder.rawValue, icon: nil, iconOffset: 0.0, peer: nil, title: folder.1, permanent: false))
}
if case .chatList(.archive) = self.location { if case .chatList(.archive) = self.location {
tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: false)) tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: false))
} else if case .forum = self.location, let forumPeer = self.forumPeer { } else if case .forum = self.location, let forumPeer = self.forumPeer {
@ -737,10 +754,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
key = .chats key = .chats
} }
self.paneContainerNode.requestSelectPane(key) self.paneContainerNode.requestSelectPane(key)
self.updateSearchOptions(nil) self.updateSearchOptions(self.currentSearchOptions)
self.searchTextUpdated(text: query ?? "") self.searchTextUpdated(text: query ?? "")
var tokens: [SearchBarToken] = [] var tokens: [SearchBarToken] = []
if let folder = self.folder {
tokens.append(SearchBarToken(id: ChatListTokenId.folder.rawValue, icon: nil, iconOffset: 0.0, peer: nil, title: folder.1, permanent: false))
}
if case .chatList(.archive) = self.location { if case .chatList(.archive) = self.location {
tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: false)) tokens.append(SearchBarToken(id: ChatListTokenId.archive.rawValue, icon: UIImage(bundleImageName: "Chat List/Search/Archive"), iconOffset: -1.0, title: self.presentationData.strings.ChatList_Archive, permanent: false))
} else if case .forum = self.location, let forumPeer = self.forumPeer { } else if case .forum = self.location, let forumPeer = self.forumPeer {

View file

@ -1448,17 +1448,22 @@ public enum ChatListSearchContextActionSource {
public struct ChatListSearchOptions { public struct ChatListSearchOptions {
let peer: (EnginePeer.Id, Bool, String)? let peer: (EnginePeer.Id, Bool, String)?
let date: (Int32?, Int32, String)? let date: (Int32?, Int32, String)?
let folder: (Int32, String)?
var isEmpty: Bool { var isEmpty: Bool {
return self.peer == nil && self.date == nil return self.peer == nil && self.date == nil && self.folder == nil
} }
func withUpdatedPeer(_ peerIdIsGroupAndName: (EnginePeer.Id, Bool, String)?) -> ChatListSearchOptions { func withUpdatedPeer(_ peerIdIsGroupAndName: (EnginePeer.Id, Bool, String)?) -> ChatListSearchOptions {
return ChatListSearchOptions(peer: peerIdIsGroupAndName, date: self.date) return ChatListSearchOptions(peer: peerIdIsGroupAndName, date: self.date, folder: self.folder)
} }
func withUpdatedDate(_ minDateMaxDateAndTitle: (Int32?, Int32, String)?) -> ChatListSearchOptions { func withUpdatedDate(_ minDateMaxDateAndTitle: (Int32?, Int32, String)?) -> ChatListSearchOptions {
return ChatListSearchOptions(peer: self.peer, date: minDateMaxDateAndTitle) return ChatListSearchOptions(peer: self.peer, date: minDateMaxDateAndTitle, folder: self.folder)
}
func withUpdatedFolder(_ folder: (Int32, String)?) -> ChatListSearchOptions {
return ChatListSearchOptions(peer: self.peer, date: self.date, folder: folder)
} }
} }
@ -2065,7 +2070,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var defaultFoundRemoteMessagesSignal: Signal<([FoundRemoteMessages], Bool), NoError> = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], false)) var defaultFoundRemoteMessagesSignal: Signal<([FoundRemoteMessages], Bool), NoError> = .single(([FoundRemoteMessages(messages: [], readCounters: [:], threadsData: [:], totalCount: 0)], false))
if key == .globalPosts, let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_load_empty_global_posts"] as? Double, value != 0.0 { if key == .globalPosts, let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_load_empty_global_posts"] as? Double, value != 0.0 {
let searchSignal = context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: nil), tags: nil, minDate: nil, maxDate: nil), query: "", state: nil, limit: 50) let searchSignal = context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: nil), tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil, limit: 50)
|> map { resultData -> ChatListSearchMessagesResult in |> map { resultData -> ChatListSearchMessagesResult in
let (result, updatedState) = resultData let (result, updatedState) = resultData
@ -2089,7 +2094,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let foundItems: Signal<([ChatListSearchEntry], Bool, String?)?, NoError> = combineLatest(queue: .mainQueue(), searchQuery, self.approvedGlobalPostQueryState.get(), searchOptions, self.searchScopePromise.get(), downloadItems, globalPostSearchStateType, isPremium) let foundItems: Signal<([ChatListSearchEntry], Bool, String?)?, NoError> = combineLatest(queue: .mainQueue(), searchQuery, self.approvedGlobalPostQueryState.get(), searchOptions, self.searchScopePromise.get(), downloadItems, globalPostSearchStateType, isPremium)
|> debounceOnMainThread |> debounceOnMainThread
|> mapToSignal { [weak self] query, approvedGlobalPostQueryState, options, searchScope, downloadItems, _, _ -> Signal<([ChatListSearchEntry], Bool, String?)?, NoError> in |> mapToSignal { [weak self] query, approvedGlobalPostQueryState, options, searchScope, downloadItems, _, _ -> Signal<([ChatListSearchEntry], Bool, String?)?, NoError> in
if query == nil && options == nil && [.chats, .topics, .channels, .apps].contains(key) { if query == nil && (options == nil || options?.withUpdatedFolder(nil).isEmpty == true) && [.chats, .topics, .channels, .apps].contains(key) {
let _ = currentRemotePeers.swap(nil) let _ = currentRemotePeers.swap(nil)
return .single(nil) return .single(nil)
} }
@ -2244,7 +2249,24 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return result return result
} }
let updatedLocalPeers = context.engine.contacts.searchLocalPeers(query: query.lowercased()) var predicate: Signal<ChatListFilterPredicate?, NoError> = .single(nil)
if let folderId = options?.folder?.0 {
predicate = context.engine.peers.currentChatListFilters()
|> take(1)
|> map { filters -> ChatListFilterPredicate? in
guard let filter = filters.first(where: { $0.id == folderId }) else {
return nil
}
guard case let .filter(_, _, _, data) = filter else {
return nil
}
return chatListFilterPredicate(filter: data, accountPeerId: context.account.peerId)
}
}
let updatedLocalPeers = predicate |> mapToSignal { predicate in
return context.engine.contacts.searchLocalPeers(query: query.lowercased(), predicate: predicate)
}
|> mapToSignal { peers -> Signal<[EngineRenderedPeer], NoError> in |> mapToSignal { peers -> Signal<[EngineRenderedPeer], NoError> in
return context.engine.data.subscribe( return context.engine.data.subscribe(
EngineDataMap(peers.map { peer in EngineDataMap(peers.map { peer in
@ -2552,7 +2574,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if case .savedMessagesChats = location { if case .savedMessagesChats = location {
foundRemotePeers = .single(([], [], [], false)) foundRemotePeers = .single(([], [], [], false))
} else if let query = query, case .chats = key { } else if let query = query, case .chats = key {
if query.hasPrefix("#") { if query.hasPrefix("#") || options?.folder != nil {
foundRemotePeers = .single(([], [], [], false)) foundRemotePeers = .single(([], [], [], false))
} else { } else {
foundRemotePeers = ( foundRemotePeers = (
@ -2584,28 +2606,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
let searchLocations: [SearchMessagesLocation] let searchLocations: [SearchMessagesLocation]
if key == .globalPosts { if key == .globalPosts {
searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil)] searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil, folderId: nil)]
} else if let options = options { } else if let options {
if case let .forum(peerId) = location { if case let .forum(peerId) = location {
searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(scope: .everywhere, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)] searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(scope: .everywhere, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: nil)]
} else if let (peerId, _, _) = options.peer { } else if let (peerId, _, _) = options.peer {
searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1)] searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1)]
} else { } else {
if case let .chatList(groupId) = location, case .archive = groupId { if case let .chatList(groupId) = location, case .archive = groupId {
searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)] searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)]
} else { } else {
searchLocations = [.general(scope: searchScope, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)] searchLocations = [.general(scope: searchScope, tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1, folderId: options.folder?.0)]
} }
} }
} else { } else {
if case .channels = key { if case .channels = key {
searchLocations = [.general(scope: .channels, tags: tagMask, minDate: nil, maxDate: nil)] searchLocations = [.general(scope: .channels, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)]
} else if case let .forum(peerId) = location { } else if case let .forum(peerId) = location {
searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: nil, maxDate: nil), .general(scope: .everywhere, tags: tagMask, minDate: nil, maxDate: nil)] searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: nil, maxDate: nil), .general(scope: .everywhere, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)]
} else if case let .chatList(groupId) = location, case .archive = groupId { } else if case let .chatList(groupId) = location, case .archive = groupId {
searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)] searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)]
} else { } else {
searchLocations = [.general(scope: searchScope, tags: tagMask, minDate: nil, maxDate: nil)] searchLocations = [.general(scope: searchScope, tags: tagMask, minDate: nil, maxDate: nil, folderId: nil)]
} }
} }
@ -2704,7 +2726,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let searchSignals: [Signal<(SearchMessagesResult, SearchMessagesState), NoError>] let searchSignals: [Signal<(SearchMessagesResult, SearchMessagesState), NoError>]
if key == .globalPosts { if key == .globalPosts {
searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil), query: finalQuery, state: nil, limit: 50)] searchSignals = [context.engine.messages.searchMessages(location: .general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: finalQuery, state: nil, limit: 50)]
} else { } else {
searchSignals = searchLocations.map { searchLocation in searchSignals = searchLocations.map { searchLocation in
return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50) return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50)

View file

@ -503,6 +503,20 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
self.component?.contentIdUpdated(id) self.component?.contentIdUpdated(id)
} }
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.alpha.isZero {
return nil
}
for view in self.subviews.reversed() {
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
return result
}
}
let result = super.hitTest(point, with: event)
return result
}
func update(component: PagerComponent<ChildEnvironmentType, TopPanelEnvironment>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize { func update(component: PagerComponent<ChildEnvironmentType, TopPanelEnvironment>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
let previousPanelHideBehavior = self.component?.panelHideBehavior let previousPanelHideBehavior = self.component?.panelHideBehavior

View file

@ -28,10 +28,12 @@ public final class NavigationBarTheme {
public let badgeStrokeColor: UIColor public let badgeStrokeColor: UIColor
public let badgeTextColor: UIColor public let badgeTextColor: UIColor
public let edgeEffectColor: UIColor? public let edgeEffectColor: UIColor?
public let accentButtonColor: UIColor
public let accentForegroundColor: UIColor
public let style: NavigationBar.Style public let style: NavigationBar.Style
public let glassStyle: NavigationBar.GlassStyle public let glassStyle: NavigationBar.GlassStyle
public init(overallDarkAppearance: Bool, buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, opaqueBackgroundColor: UIColor? = nil, enableBackgroundBlur: Bool, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor, edgeEffectColor: UIColor? = nil, style: NavigationBar.Style = .legacy, glassStyle: NavigationBar.GlassStyle = .default) { public init(overallDarkAppearance: Bool, buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, opaqueBackgroundColor: UIColor? = nil, enableBackgroundBlur: Bool, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor, edgeEffectColor: UIColor? = nil, accentButtonColor: UIColor, accentForegroundColor: UIColor, style: NavigationBar.Style = .legacy, glassStyle: NavigationBar.GlassStyle = .default) {
self.overallDarkAppearance = overallDarkAppearance self.overallDarkAppearance = overallDarkAppearance
self.buttonColor = buttonColor self.buttonColor = buttonColor
self.disabledButtonColor = disabledButtonColor self.disabledButtonColor = disabledButtonColor
@ -44,16 +46,18 @@ public final class NavigationBarTheme {
self.badgeStrokeColor = badgeStrokeColor self.badgeStrokeColor = badgeStrokeColor
self.badgeTextColor = badgeTextColor self.badgeTextColor = badgeTextColor
self.edgeEffectColor = edgeEffectColor self.edgeEffectColor = edgeEffectColor
self.accentButtonColor = accentButtonColor
self.accentForegroundColor = accentForegroundColor
self.style = style self.style = style
self.glassStyle = glassStyle self.glassStyle = glassStyle
} }
public func withUpdatedBackgroundColor(_ color: UIColor) -> NavigationBarTheme { public func withUpdatedBackgroundColor(_ color: UIColor) -> NavigationBarTheme {
return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: color, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: false, separatorColor: self.separatorColor, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, style: self.style, glassStyle: self.glassStyle) return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: color, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: false, separatorColor: self.separatorColor, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, accentButtonColor: self.accentButtonColor, accentForegroundColor: self.accentForegroundColor, style: self.style, glassStyle: self.glassStyle)
} }
public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme { public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme {
return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: self.enableBackgroundBlur, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, style: self.style, glassStyle: self.glassStyle) return NavigationBarTheme(overallDarkAppearance: self.overallDarkAppearance, buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, opaqueBackgroundColor: self.opaqueBackgroundColor, enableBackgroundBlur: self.enableBackgroundBlur, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor, edgeEffectColor: self.edgeEffectColor, accentButtonColor: self.accentButtonColor, accentForegroundColor: self.accentForegroundColor, style: self.style, glassStyle: self.glassStyle)
} }
} }

View file

@ -681,7 +681,7 @@ private func galleryEntriesForMessageHistoryEntries(_ entries: [MessageHistoryEn
} }
public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder, GalleryControllerProtocol { public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder, GalleryControllerProtocol {
public static let darkNavigationTheme = NavigationBarTheme(overallDarkAppearance: true, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, edgeEffectColor: .clear, style: .glass) public static let darkNavigationTheme = NavigationBarTheme(overallDarkAppearance: true, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, edgeEffectColor: .clear, accentButtonColor: .white, accentForegroundColor: .black, style: .glass)
private var galleryNode: GalleryControllerNode { private var galleryNode: GalleryControllerNode {
return self.displayNode as! GalleryControllerNode return self.displayNode as! GalleryControllerNode

View file

@ -54,7 +54,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol {
if self.publicPosts { if self.publicPosts {
search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: nil) search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: nil)
} else { } else {
search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil), query: self.query, state: nil) search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: nil)
} }
self.isSearchingPromise.set(true) self.isSearchingPromise.set(true)
@ -102,7 +102,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol {
if self.publicPosts { if self.publicPosts {
search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: self.currentSearchState) search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: self.currentSearchState)
} else { } else {
search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil), query: self.query, state: currentSearchState) search = self.context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: nil, minDate: nil, maxDate: nil, folderId: nil), query: self.query, state: currentSearchState)
} }
self.historyViewDisposable?.dispose() self.historyViewDisposable?.dispose()

View file

@ -39,7 +39,7 @@ public class SetupTwoStepVerificationController: ViewController {
self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: self.presentationData.theme.list.itemCheckColors.fillColor, accentForegroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style

View file

@ -71,7 +71,7 @@ public final class TwoFactorDataInputScreen: ViewController {
self.presentationData = self.sharedContext.currentPresentationData.with { $0 } self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme) let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor) let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor, accentButtonColor: defaultTheme.accentButtonColor, accentForegroundColor: defaultTheme.accentForegroundColor)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))

View file

@ -57,7 +57,7 @@ public final class TwoFactorAuthSplashScreen: ViewController {
self.presentationData = self.sharedContext.currentPresentationData.with { $0 } self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme) let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor) let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: defaultTheme.overallDarkAppearance, buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor, accentButtonColor: defaultTheme.accentButtonColor, accentForegroundColor: defaultTheme.accentForegroundColor)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))

View file

@ -1213,9 +1213,9 @@ public final class Transaction {
self.postbox?.reindexUnreadCounters(currentTransaction: self) self.postbox?.reindexUnreadCounters(currentTransaction: self)
} }
public func searchPeers(query: String) -> [RenderedPeer] { public func searchPeers(query: String, predicate: ChatListFilterPredicate?) -> [RenderedPeer] {
assert(!self.disposed) assert(!self.disposed)
return self.postbox?.searchPeers(query: query) ?? [] return self.postbox?.searchPeers(transaction: self, query: query, predicate: predicate) ?? []
} }
public func clearTimestampBasedAttribute(id: MessageId, tag: UInt16) { public func clearTimestampBasedAttribute(id: MessageId, tag: UInt16) {
@ -3800,13 +3800,13 @@ final class PostboxImpl {
} |> switchToLatest } |> switchToLatest
} }
public func searchPeers(query: String) -> Signal<[RenderedPeer], NoError> { public func searchPeers(query: String, predicate: ChatListFilterPredicate?) -> Signal<[RenderedPeer], NoError> {
return self.transaction { transaction -> Signal<[RenderedPeer], NoError> in return self.transaction { transaction -> Signal<[RenderedPeer], NoError> in
return .single(transaction.searchPeers(query: query)) return .single(transaction.searchPeers(query: query, predicate: predicate))
} |> switchToLatest } |> switchToLatest
} }
fileprivate func searchPeers(query: String) -> [RenderedPeer] { fileprivate func searchPeers(transaction: Transaction, query: String, predicate: ChatListFilterPredicate?) -> [RenderedPeer] {
var peerIds = Set<PeerId>() var peerIds = Set<PeerId>()
var chatPeers: [RenderedPeer] = [] var chatPeers: [RenderedPeer] = []
@ -3823,6 +3823,30 @@ final class PostboxImpl {
} }
chatPeerIds.append(contentsOf: additionalChatPeerIds) chatPeerIds.append(contentsOf: additionalChatPeerIds)
if let predicate {
let globalNotificationSettings = self.getGlobalNotificationSettings(transaction: transaction)
let filterImpl: (PeerId) -> Bool = { peerId in
guard let peer = self.peerTable.get(peerId) else {
return false
}
let inclusion = self.chatListIndexTable.get(peerId: peerId)
let isUnread = self.readStateTable.getCombinedState(peerId)?.isUnread ?? false
let notificationsPeerId = peer.notificationSettingsPeerId ?? peerId
let isContact = self.contactsTable.isContact(peerId: notificationsPeerId)
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: self.peerNotificationSettingsTable.getEffective(notificationsPeerId))
let messageTagSummaryResult = resolveChatListMessageTagSummaryResultCalculation(postbox: self, peerId: peer.id, threadId: nil, calculation: predicate.messageTagSummary)
if predicate.includes(peer: peer, groupId: inclusion.inclusion.groupId ?? .root, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: messageTagSummaryResult) {
return true
} else {
return false
}
}
chatPeerIds = chatPeerIds.filter(filterImpl)
contactPeerIds = contactPeerIds.filter(filterImpl)
}
for peerId in chatPeerIds { for peerId in chatPeerIds {
if let peer = self.peerTable.get(peerId) { if let peer = self.peerTable.get(peerId) {
var peers = SimpleDictionary<PeerId, Peer>() var peers = SimpleDictionary<PeerId, Peer>()
@ -4929,12 +4953,12 @@ public class Postbox {
} }
} }
public func searchPeers(query: String) -> Signal<[RenderedPeer], NoError> { public func searchPeers(query: String, predicate: ChatListFilterPredicate? = nil) -> Signal<[RenderedPeer], NoError> {
return Signal { subscriber in return Signal { subscriber in
let disposable = MetaDisposable() let disposable = MetaDisposable()
self.impl.with { impl in self.impl.with { impl in
disposable.set(impl.searchPeers(query: query).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)) disposable.set(impl.searchPeers(query: query, predicate: predicate).start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion))
} }
return disposable return disposable

View file

@ -77,7 +77,7 @@ public final class QrCodeScanScreen: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) let navigationBarTheme = NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: .white, accentForegroundColor: .black)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close))) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))

View file

@ -928,6 +928,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
} set { } set {
self.textField.tokens = newValue self.textField.tokens = newValue
self.updateIsEmpty(animated: true) self.updateIsEmpty(animated: true)
if let (boundingSize, leftInset, rightInset) = self.validLayout {
self.updateLayout(boundingSize: boundingSize, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
}
} }
} }

View file

@ -58,8 +58,8 @@ public extension TelegramEngine {
return _internal_searchPeers(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, query: query, scope: scope) return _internal_searchPeers(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, query: query, scope: scope)
} }
public func searchLocalPeers(query: String, scope: TelegramSearchPeersScope = .everywhere) -> Signal<[EngineRenderedPeer], NoError> { public func searchLocalPeers(query: String, scope: TelegramSearchPeersScope = .everywhere, predicate: ChatListFilterPredicate? = nil) -> Signal<[EngineRenderedPeer], NoError> {
return self.account.postbox.searchPeers(query: query) return self.account.postbox.searchPeers(query: query, predicate: predicate)
|> map { peers in |> map { peers in
switch scope { switch scope {
case .everywhere: case .everywhere:

View file

@ -6,7 +6,7 @@ import MtProtoKit
public enum SearchMessagesLocation: Equatable { public enum SearchMessagesLocation: Equatable {
case general(scope: TelegramSearchPeersScope, tags: MessageTags?, minDate: Int32?, maxDate: Int32?) case general(scope: TelegramSearchPeersScope, tags: MessageTags?, minDate: Int32?, maxDate: Int32?, folderId: Int32?)
case group(groupId: PeerGroupId, tags: MessageTags?, minDate: Int32?, maxDate: Int32?) case group(groupId: PeerGroupId, tags: MessageTags?, minDate: Int32?, maxDate: Int32?)
case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, reactions: [MessageReaction.Reaction]?, threadId: Int64?, minDate: Int32?, maxDate: Int32?) case peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, reactions: [MessageReaction.Reaction]?, threadId: Int64?, minDate: Int32?, maxDate: Int32?)
case sentMedia(tags: MessageTags?) case sentMedia(tags: MessageTags?)
@ -471,7 +471,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
} }
return combineLatest(peerMessages, additionalPeerMessages) return combineLatest(peerMessages, additionalPeerMessages)
} }
case let .general(_, tags, minDate, maxDate), let .group(_, tags, minDate, maxDate): case let .general(_, tags, minDate, maxDate, _), let .group(_, tags, minDate, maxDate):
var flags: Int32 = 0 var flags: Int32 = 0
let folderId: Int32? let folderId: Int32?
if case let .group(groupId, _, _, _) = location { if case let .group(groupId, _, _, _) = location {
@ -481,7 +481,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
folderId = nil folderId = nil
} }
if case let .general(scope, _, _, _) = location, case let .globalPosts(allowPaidStars) = scope { if case let .general(scope, _, _, _, _) = location, case let .globalPosts(allowPaidStars) = scope {
remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in
var lowerBound: MessageIndex? var lowerBound: MessageIndex?
if let state = state, let message = state.main.messages.last { if let state = state, let message = state.main.messages.last {
@ -508,7 +508,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
} }
} }
} else { } else {
if case let .general(scope, _, _, _) = location { if case let .general(scope, _, _, _, _) = location {
switch scope { switch scope {
case .everywhere: case .everywhere:
break break
@ -567,9 +567,9 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
if state?.additional == nil { if state?.additional == nil {
switch location { switch location {
case let .general(_, tags, minDate, maxDate), let .group(_, tags, minDate, maxDate): case let .general(_, tags, minDate, maxDate, _), let .group(_, tags, minDate, maxDate):
let secretMessages: [Message] let secretMessages: [Message]
if case let .general(scope, _, _, _) = location, case .channels = scope { if case let .general(scope, _, _, _, _) = location, case .channels = scope {
secretMessages = [] secretMessages = []
} else { } else {
secretMessages = transaction.searchMessages(peerId: nil, query: query, tags: tags) secretMessages = transaction.searchMessages(peerId: nil, query: query, tags: tags)

View file

@ -208,6 +208,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case giftCraftingTips = 85 case giftCraftingTips = 85
case copyProtectionTips = 86 case copyProtectionTips = 86
case aiTextProcessingStyleSelectionTips = 87 case aiTextProcessingStyleSelectionTips = 87
case savedMessagesChatListView = 88
var key: ValueBoxKey { var key: ValueBoxKey {
let v = ValueBoxKey(length: 4) let v = ValueBoxKey(length: 4)
@ -589,6 +590,10 @@ private struct ApplicationSpecificNoticeKeys {
static func aiTextProcessingStyleSelectionTips() -> NoticeEntryKey { static func aiTextProcessingStyleSelectionTips() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.aiTextProcessingStyleSelectionTips.key) return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.aiTextProcessingStyleSelectionTips.key)
} }
static func savedMessagesChatListView() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessagesChatListView.key)
}
} }
public struct ApplicationSpecificNotice { public struct ApplicationSpecificNotice {
@ -2610,4 +2615,31 @@ public struct ApplicationSpecificNotice {
return Int(previousValue) return Int(previousValue)
} }
} }
public static func getSavedMessagesChatListView(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.savedMessagesChatListView())?.get(ApplicationSpecificCounterNotice.self) {
return value.value
} else {
return 0
}
}
}
public static func incrementSavedMessagesChatListView(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
return accountManager.transaction { transaction -> Int in
var currentValue: Int32 = 0
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.savedMessagesChatListView())?.get(ApplicationSpecificCounterNotice.self) {
currentValue = value.value
}
let previousValue = currentValue
currentValue += Int32(count)
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
transaction.setNotice(ApplicationSpecificNoticeKeys.savedMessagesChatListView(), entry)
}
return Int(previousValue)
}
}
} }

View file

@ -69,7 +69,7 @@ public extension NavigationBarTheme {
badgeTextColor = theme.badgeTextColor badgeTextColor = theme.badgeTextColor
} }
self.init(overallDarkAppearance: rootControllerTheme.overallDarkAppearance, buttonColor: buttonColor, disabledButtonColor: disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.blurredBackgroundColor, opaqueBackgroundColor: hideBackground ? .clear : theme.opaqueBackgroundColor, enableBackgroundBlur: enableBackgroundBlur, separatorColor: hideBackground || hideSeparator ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : badgeBackgroundColor, badgeStrokeColor: .clear, badgeTextColor: hideBadge ? .clear : badgeTextColor, edgeEffectColor: edgeEffectColor, style: style, glassStyle: glassStyle) self.init(overallDarkAppearance: rootControllerTheme.overallDarkAppearance, buttonColor: buttonColor, disabledButtonColor: disabledButtonColor, primaryTextColor: theme.primaryTextColor, backgroundColor: hideBackground ? .clear : theme.blurredBackgroundColor, opaqueBackgroundColor: hideBackground ? .clear : theme.opaqueBackgroundColor, enableBackgroundBlur: enableBackgroundBlur, separatorColor: hideBackground || hideSeparator ? .clear : theme.separatorColor, badgeBackgroundColor: hideBadge ? .clear : badgeBackgroundColor, badgeStrokeColor: .clear, badgeTextColor: hideBadge ? .clear : badgeTextColor, edgeEffectColor: edgeEffectColor, accentButtonColor: rootControllerTheme.list.itemCheckColors.fillColor, accentForegroundColor: rootControllerTheme.list.itemCheckColors.foregroundColor, style: style, glassStyle: glassStyle)
} }
} }

View file

@ -756,7 +756,7 @@ public func makeAttachmentFileControllerImpl(
case .audio: case .audio:
recentDocuments = .single(nil) recentDocuments = .single(nil)
|> then( |> then(
context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil), query: "", state: nil) context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: "", state: nil)
|> map { result -> [Message]? in |> map { result -> [Message]? in
return result.0.messages return result.0.messages
} }

View file

@ -530,7 +530,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
case .audio: case .audio:
shared = .single(nil) shared = .single(nil)
|> then( |> then(
context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil), query: query, state: nil) context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil, folderId: nil), query: query, state: nil)
|> delay(0.6, queue: Queue.mainQueue()) |> delay(0.6, queue: Queue.mainQueue())
|> map { result -> [Message]? in |> map { result -> [Message]? in
return result.0.messages.filter { !$0.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) } return result.0.messages.filter { !$0.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) }

View file

@ -361,7 +361,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
private let context: AccountContext private let context: AccountContext
private let stateContext: StateContext? private let stateContext: StateContext?
private let entityKeyboardView: ComponentHostView<Empty> private let entityKeyboardView: ComponentView<Empty>
private let defaultToEmojiTab: Bool private let defaultToEmojiTab: Bool
private var stableReorderableGroupOrder: [EntityKeyboardComponent.ReorderCategory: [ItemCollectionId]] = [:] private var stableReorderableGroupOrder: [EntityKeyboardComponent.ReorderCategory: [ItemCollectionId]] = [:]
@ -449,7 +449,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
public var canSwitchToTextInputAutomatically: Bool { public var canSwitchToTextInputAutomatically: Bool {
if let pagerView = self.entityKeyboardView.componentView as? EntityKeyboardComponent.View, let centralId = pagerView.centralId { if let pagerView = self.entityKeyboardView.view as? EntityKeyboardComponent.View, let centralId = pagerView.centralId {
if centralId == AnyHashable("emoji") { if centralId == AnyHashable("emoji") {
return false return false
} }
@ -496,11 +496,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
self.interaction = interaction self.interaction = interaction
self.clippingView = UIView() self.clippingView = SparseContainerView()
self.clippingView.clipsToBounds = true self.clippingView.clipsToBounds = true
self.clippingView.layer.cornerRadius = keyboardCornerRadius self.clippingView.layer.cornerRadius = keyboardCornerRadius
self.entityKeyboardView = ComponentHostView<Empty>() self.entityKeyboardView = ComponentView()
super.init() super.init()
@ -538,7 +538,6 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
) )
} }
self.clippingView.addSubview(self.entityKeyboardView)
self.view.addSubview(self.clippingView) self.view.addSubview(self.clippingView)
if let backgroundChromeView = self.backgroundChromeView { if let backgroundChromeView = self.backgroundChromeView {
@ -692,7 +691,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
for featuredStickerPack in stickerPacks { for featuredStickerPack in stickerPacks {
if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) { if featuredStickerPack.topItems.contains(where: { $0.file.fileId == file.fileId }) {
if let pagerView = self.entityKeyboardView.componentView as? EntityKeyboardComponent.View, let emojiInputInteraction = self.emojiInputInteraction { if let pagerView = self.entityKeyboardView.view as? EntityKeyboardComponent.View, let emojiInputInteraction = self.emojiInputInteraction {
pagerView.openCustomSearch(content: EmojiSearchContent( pagerView.openCustomSearch(content: EmojiSearchContent(
context: self.context, context: self.context,
forceTheme: self.interaction?.forceTheme, forceTheme: self.interaction?.forceTheme,
@ -1326,7 +1325,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
)) ))
}, },
openSearch: { [weak self] in openSearch: { [weak self] in
if let strongSelf = self, let pagerView = strongSelf.entityKeyboardView.componentView as? EntityKeyboardComponent.View { if let strongSelf = self, let pagerView = strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View {
pagerView.openSearch() pagerView.openSearch()
} }
}, },
@ -1630,7 +1629,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
var transition: ComponentTransition = .immediate var transition: ComponentTransition = .immediate
var useAnimation = false var useAnimation = false
if let pagerView = strongSelf.entityKeyboardView.componentView as? EntityKeyboardComponent.View, let centralId = pagerView.centralId { if let pagerView = strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View, let centralId = pagerView.centralId {
if centralId == AnyHashable("emoji") { if centralId == AnyHashable("emoji") {
useAnimation = strongSelf.currentInputData.emoji != inputData.emoji useAnimation = strongSelf.currentInputData.emoji != inputData.emoji
} else if centralId == AnyHashable("stickers"), strongSelf.currentInputData.stickers != nil, inputData.stickers != nil { } else if centralId == AnyHashable("stickers"), strongSelf.currentInputData.stickers != nil, inputData.stickers != nil {
@ -1710,7 +1709,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
gifContext.loadMore(token: token) gifContext.loadMore(token: token)
}, },
openSearch: { [weak self] in openSearch: { [weak self] in
if let strongSelf = self, let pagerView = strongSelf.entityKeyboardView.componentView as? EntityKeyboardComponent.View { if let strongSelf = self, let pagerView = strongSelf.entityKeyboardView.view as? EntityKeyboardComponent.View {
pagerView.openSearch() pagerView.openSearch()
} }
}, },
@ -1798,8 +1797,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = super.hitTest(point, with: event) { if self.alpha.isZero || !self.view.isUserInteractionEnabled {
return result return nil
}
for subview in self.view.subviews.reversed() {
if let result = subview.hitTest(self.view.convert(point, to: subview), with: event), result.isUserInteractionEnabled {
return result
}
} }
if let backgroundView = self.backgroundView, backgroundView.frame.contains(point) { if let backgroundView = self.backgroundView, backgroundView.frame.contains(point) {
@ -2025,7 +2029,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
entityKeyboardSizeFrame.origin.y += self.topBackgroundExtension entityKeyboardSizeFrame.origin.y += self.topBackgroundExtension
} }
transition.updateFrame(view: self.entityKeyboardView, frame: entityKeyboardSizeFrame) if let entityKeyboardComponentView = self.entityKeyboardView.view {
if entityKeyboardComponentView.superview == nil {
self.clippingView.addSubview(entityKeyboardComponentView)
}
transition.updateFrame(view: entityKeyboardComponentView, frame: entityKeyboardSizeFrame)
}
transition.updateFrame(view: self.clippingView, frame: clippingFrame) transition.updateFrame(view: self.clippingView, frame: clippingFrame)
@ -2366,7 +2375,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
} }
public func scrollToGroupEmoji() { public func scrollToGroupEmoji() {
if let pagerView = self.entityKeyboardView.componentView as? EntityKeyboardComponent.View { if let pagerView = self.entityKeyboardView.view as? EntityKeyboardComponent.View {
pagerView.scrollToItemGroup(contentId: "emoji", groupId: "peerSpecific", subgroupId: nil) pagerView.scrollToItemGroup(contentId: "emoji", groupId: "peerSpecific", subgroupId: nil)
} }
} }

View file

@ -157,7 +157,8 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
self.environment = environment self.environment = environment
if self.component == nil { if self.component == nil {
if case .format = component.mode { switch component.mode {
case .format, .search:
self.minDate = Date(timeIntervalSince1970: 0.0) self.minDate = Date(timeIntervalSince1970: 0.0)
self.maxDate = Date(timeIntervalSince1970: Double(Int32.max - 1)) self.maxDate = Date(timeIntervalSince1970: Double(Int32.max - 1))
if let currentTime = component.currentTime { if let currentTime = component.currentTime {
@ -167,7 +168,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
} else { } else {
self.date = Date() self.date = Date()
} }
} else { default:
self.updateMinimumDate(currentTime: component.currentTime, minimalTime: component.minimalTime) self.updateMinimumDate(currentTime: component.currentTime, minimalTime: component.minimalTime)
} }
self.repeatPeriod = component.currentRepeatPeriod self.repeatPeriod = component.currentRepeatPeriod
@ -227,6 +228,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
title = strings.Conversation_FormatDate_Title title = strings.Conversation_FormatDate_Title
case .poll: case .poll:
title = strings.CreatePoll_Deadline_Title title = strings.CreatePoll_Deadline_Title
case .search:
//TODO:localize
title = "Search"
} }
let titleSize = self.title.update( let titleSize = self.title.update(
transition: transition, transition: transition,
@ -386,6 +390,8 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
var repeatValueFrame = CGRect() var repeatValueFrame = CGRect()
if case .format = component.mode { if case .format = component.mode {
contentHeight += 8.0 contentHeight += 8.0
} else if case .search = component.mode {
contentHeight += 8.0
} else if case .poll = component.mode { } else if case .poll = component.mode {
contentHeight += 8.0 contentHeight += 8.0
} else { } else {
@ -497,6 +503,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
buttonTitle = component.currentTime != nil ? strings.Conversation_FormatDate_EditDate : strings.Conversation_FormatDate_AddDate buttonTitle = component.currentTime != nil ? strings.Conversation_FormatDate_EditDate : strings.Conversation_FormatDate_AddDate
case .poll: case .poll:
buttonTitle = strings.CreatePoll_Deadline_SetDeadline buttonTitle = strings.CreatePoll_Deadline_SetDeadline
case .search:
//TODO:localize
buttonTitle = "Done"
} }
let buttonSideInset: CGFloat = 30.0 let buttonSideInset: CGFloat = 30.0
@ -539,7 +548,13 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
} }
contentHeight += buttonSize.height contentHeight += buttonSize.height
if case .format = component.mode, component.currentTime != nil { var isFormatOrSearch = false
if case .format = component.mode {
isFormatOrSearch = true
} else if case .search = component.mode {
isFormatOrSearch = true
}
if isFormatOrSearch && component.currentTime != nil {
contentHeight += 8.0 contentHeight += 8.0
let buttonSize = self.secondaryButton.update( let buttonSize = self.secondaryButton.update(
@ -947,6 +962,7 @@ public class ChatScheduleTimeScreen: ViewControllerComponentContainer {
case reminders case reminders
case format case format
case poll case poll
case search
} }
public struct Result { public struct Result {

View file

@ -272,7 +272,7 @@ public final class EntityKeyboardComponent: Component {
public final class View: UIView { public final class View: UIView {
private let tintContainerView: UIView private let tintContainerView: UIView
private let pagerView: ComponentHostView<EntityKeyboardChildEnvironment> private let pagerView: ComponentView<EntityKeyboardChildEnvironment>
private var component: EntityKeyboardComponent? private var component: EntityKeyboardComponent?
public private(set) weak var state: EmptyComponentState? public private(set) weak var state: EmptyComponentState?
@ -295,20 +295,32 @@ public final class EntityKeyboardComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
self.tintContainerView = UIView() self.tintContainerView = UIView()
self.pagerView = ComponentHostView<EntityKeyboardChildEnvironment>() self.pagerView = ComponentView()
super.init(frame: frame) super.init(frame: frame)
//self.clipsToBounds = true //self.clipsToBounds = true
self.disablesInteractiveTransitionGestureRecognizer = true self.disablesInteractiveTransitionGestureRecognizer = true
self.addSubview(self.pagerView)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.alpha.isZero {
return nil
}
for view in self.subviews.reversed() {
if let result = view.hitTest(self.convert(point, to: view), with: event), result.isUserInteractionEnabled {
return result
}
}
let result = super.hitTest(point, with: event)
return result
}
func update(component: EntityKeyboardComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize { func update(component: EntityKeyboardComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.state = state self.state = state
@ -787,7 +799,12 @@ public final class EntityKeyboardComponent: Component {
forceUpdate: forceUpdate, forceUpdate: forceUpdate,
containerSize: availableSize containerSize: availableSize
) )
transition.setFrame(view: self.pagerView, frame: CGRect(origin: CGPoint(), size: pagerSize)) if let pagerComponentView = self.pagerView.view {
if pagerComponentView.superview == nil {
self.insertSubview(pagerComponentView, at: 0)
}
transition.setFrame(view: pagerComponentView, frame: CGRect(origin: CGPoint(), size: pagerSize))
}
let accountContext = component.emojiContent?.context ?? component.stickerContent?.context let accountContext = component.emojiContent?.context ?? component.stickerContent?.context
if let searchComponent = self.searchComponent, let accountContext = accountContext { if let searchComponent = self.searchComponent, let accountContext = accountContext {

View file

@ -441,9 +441,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
self.leftButtonNodeImpl.view.removeFromSuperview() self.leftButtonNodeImpl.view.removeFromSuperview()
var backTitle: String? var backTitle: String?
if case .glass = self.presentationData.theme.style { if let customBackButtonText = self.customBackButtonText {
backTitle = ""
} else if let customBackButtonText = self.customBackButtonText {
backTitle = customBackButtonText backTitle = customBackButtonText
} else if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { } else if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance {
backTitle = leftBarButtonItem.title backTitle = leftBarButtonItem.title
@ -460,6 +458,12 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
} }
} }
if backTitle != nil {
if case .glass = self.presentationData.theme.style {
backTitle = ""
}
}
if let backTitle { if let backTitle {
self.backButtonNodeImpl.updateManualText(backTitle, isBack: true) self.backButtonNodeImpl.updateManualText(backTitle, isBack: true)
if self.backButtonNodeImpl.supernode == nil { if self.backButtonNodeImpl.supernode == nil {
@ -631,10 +635,6 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
self.leftButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
self.rightButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
self.backButtonArrow.image = presentationData.theme.style == .glass ? generateTintedImage(image: glassBackArrowImage, color: self.presentationData.theme.buttonColor) : navigationBarBackArrowImage(color: self.presentationData.theme.buttonColor) self.backButtonArrow.image = presentationData.theme.style == .glass ? generateTintedImage(image: glassBackArrowImage, color: self.presentationData.theme.buttonColor) : navigationBarBackArrowImage(color: self.presentationData.theme.buttonColor)
if let title = self.title { if let title = self.title {
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBarImpl.titleFont, textColor: self.presentationData.theme.primaryTextColor) self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBarImpl.titleFont, textColor: self.presentationData.theme.primaryTextColor)
@ -753,10 +753,6 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor self.backButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
self.leftButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
self.rightButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
self.backButtonArrow.image = self.presentationData.theme.style == .glass ? generateTintedImage(image: glassBackArrowImage, color: self.presentationData.theme.buttonColor) : navigationBarBackArrowImage(color: self.presentationData.theme.buttonColor) self.backButtonArrow.image = self.presentationData.theme.style == .glass ? generateTintedImage(image: glassBackArrowImage, color: self.presentationData.theme.buttonColor) : navigationBarBackArrowImage(color: self.presentationData.theme.buttonColor)
if let title = self.title { if let title = self.title {
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBarImpl.titleFont, textColor: self.presentationData.theme.primaryTextColor) self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBarImpl.titleFont, textColor: self.presentationData.theme.primaryTextColor)
@ -906,6 +902,15 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
self.badgeNode.alpha = 1.0 self.badgeNode.alpha = 1.0
} }
} else if self.leftButtonNodeImpl.view.superview != nil { } else if self.leftButtonNodeImpl.view.superview != nil {
switch self.leftButtonNodeImpl.commonContentType {
case .accent:
self.leftButtonNodeImpl.color = self.presentationData.theme.accentForegroundColor
self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.accentForegroundColor.withMultipliedAlpha(0.5)
case .generic:
self.leftButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.leftButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
}
let leftButtonSize = self.leftButtonNodeImpl.updateLayout(constrainedSize: CGSize(width: size.width, height: 44.0), isLandscape: isLandscape, isLeftAligned: true) let leftButtonSize = self.leftButtonNodeImpl.updateLayout(constrainedSize: CGSize(width: size.width, height: 44.0), isLandscape: isLandscape, isLeftAligned: true)
leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0 leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0
@ -937,6 +942,15 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
var rightButtonsWidth: CGFloat = 0.0 var rightButtonsWidth: CGFloat = 0.0
if self.rightButtonNodeImpl.view.superview != nil { if self.rightButtonNodeImpl.view.superview != nil {
switch self.rightButtonNodeImpl.commonContentType {
case .accent:
self.rightButtonNodeImpl.color = self.presentationData.theme.accentForegroundColor
self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.accentForegroundColor.withMultipliedAlpha(0.5)
case .generic:
self.rightButtonNodeImpl.color = self.presentationData.theme.buttonColor
self.rightButtonNodeImpl.disabledColor = self.presentationData.theme.disabledButtonColor
}
let rightButtonSize = self.rightButtonNodeImpl.updateLayout(constrainedSize: (CGSize(width: size.width, height: 44.0)), isLandscape: isLandscape, isLeftAligned: false) let rightButtonSize = self.rightButtonNodeImpl.updateLayout(constrainedSize: (CGSize(width: size.width, height: 44.0)), isLandscape: isLandscape, isLeftAligned: false)
if !self.rightButtonNodeImpl.isEmpty { if !self.rightButtonNodeImpl.isEmpty {
rightButtonsWidth += rightButtonSize.width rightButtonsWidth += rightButtonSize.width
@ -975,7 +989,16 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
leftButtonsBackgroundTransition.setBounds(view: leftButtonsBackgroundView.background, bounds: CGRect(origin: CGPoint(), size: leftButtonsBackgroundFrame.size)) leftButtonsBackgroundTransition.setBounds(view: leftButtonsBackgroundView.background, bounds: CGRect(origin: CGPoint(), size: leftButtonsBackgroundFrame.size))
leftButtonsBackgroundTransition.setFrame(view: leftButtonsBackgroundView.container, frame: CGRect(origin: CGPoint(), size: leftButtonsBackgroundFrame.size)) leftButtonsBackgroundTransition.setFrame(view: leftButtonsBackgroundView.container, frame: CGRect(origin: CGPoint(), size: leftButtonsBackgroundFrame.size))
ComponentTransition(transition).setAlpha(view: leftButtonsBackgroundView.background, alpha: leftButtonsWidth == 0.0 ? 0.0 : 1.0) ComponentTransition(transition).setAlpha(view: leftButtonsBackgroundView.background, alpha: leftButtonsWidth == 0.0 ? 0.0 : 1.0)
leftButtonsBackgroundView.background.update(size: leftButtonsBackgroundFrame.size, cornerRadius: leftButtonsBackgroundFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: self.presentationData.theme.glassStyle == .clear ? .clear : .panel), isInteractive: true, isVisible: leftButtonsWidth != 0.0, transition: leftButtonsBackgroundTransition)
var leftButtonsColor: GlassBackgroundView.TintColor = .init(kind: self.presentationData.theme.glassStyle == .clear ? .clear : .panel)
switch self.leftButtonNodeImpl.commonContentType {
case .accent:
leftButtonsColor = .init(kind: .custom(style: self.presentationData.theme.glassStyle == .clear ? .clear : .default, color: self.presentationData.theme.accentButtonColor))
case .generic:
break
}
leftButtonsBackgroundView.background.update(size: leftButtonsBackgroundFrame.size, cornerRadius: leftButtonsBackgroundFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: leftButtonsColor, isInteractive: true, isVisible: leftButtonsWidth != 0.0, transition: leftButtonsBackgroundTransition)
} }
if let rightButtonsBackgroundView = self.rightButtonsBackgroundView { if let rightButtonsBackgroundView = self.rightButtonsBackgroundView {
@ -1001,8 +1024,16 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
}) })
} }
var rightButtonsColor: GlassBackgroundView.TintColor = .init(kind: self.presentationData.theme.glassStyle == .clear ? .clear : .panel)
switch self.rightButtonNodeImpl.commonContentType {
case .accent:
rightButtonsColor = .init(kind: .custom(style: self.presentationData.theme.glassStyle == .clear ? .clear : .default, color: self.presentationData.theme.accentButtonColor))
case .generic:
break
}
rightButtonsBackgroundView.background.isHidden = false rightButtonsBackgroundView.background.isHidden = false
rightButtonsBackgroundView.background.update(size: rightButtonsBackgroundFrame.size, cornerRadius: rightButtonsBackgroundFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: self.presentationData.theme.glassStyle == .clear ? .clear : .panel), isInteractive: true, transition: rightButtonsBackgroundTransition) rightButtonsBackgroundView.background.update(size: rightButtonsBackgroundFrame.size, cornerRadius: rightButtonsBackgroundFrame.height * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: rightButtonsColor, isInteractive: true, transition: rightButtonsBackgroundTransition)
} else { } else {
rightButtonsBackgroundView.background.isHidden = true rightButtonsBackgroundView.background.isHidden = true
} }
@ -1038,7 +1069,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
titleViewTransition = .immediate titleViewTransition = .immediate
} }
let titleSize = titleView.updateLayout(availableSize: CGSize(width: size.width - max(leftTitleInset, rightTitleInset) * 2.0, height: nominalHeight), transition: titleViewTransition) let titleSize = titleView.updateLayout(availableSize: CGSize(width: size.width - leftTitleInset - rightTitleInset, height: nominalHeight), transition: titleViewTransition)
var titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) * 0.5), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) var titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) * 0.5), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
if titleFrame.origin.x + titleFrame.width > size.width - rightTitleInset { if titleFrame.origin.x + titleFrame.width > size.width - rightTitleInset {
@ -1050,8 +1081,14 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
titleViewTransition.updateFrame(view: titleView, frame: titleFrame) titleViewTransition.updateFrame(view: titleView, frame: titleFrame)
} else { } else {
let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) let titleSize = CGSize(width: max(1.0, size.width - leftTitleInset - rightTitleInset), height: nominalHeight)
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) var titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize)
if titleFrame.origin.x + titleFrame.width > size.width - rightTitleInset {
titleFrame.origin.x = size.width - rightTitleInset - titleFrame.width
}
if titleFrame.origin.x < leftTitleInset {
titleFrame.origin.x = leftTitleInset + floorToScreenPixels((size.width - leftTitleInset - rightTitleInset - titleFrame.width) * 0.5)
}
var titleViewTransition = transition var titleViewTransition = transition
if titleView.frame.isEmpty { if titleView.frame.isEmpty {
titleViewTransition = .immediate titleViewTransition = .immediate

View file

@ -261,7 +261,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
} }
} }
private(set) var imageNode: ASImageNode? private(set) var imageView: UIImageView?
private var _image: UIImage? private var _image: UIImage?
public var image: UIImage? { public var image: UIImage? {
@ -271,23 +271,21 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
_image = value _image = value
if let _ = value { if let _ = value {
if self.imageNode == nil { if self.imageView == nil {
let imageNode = ASImageNode() let imageView = UIImageView()
imageNode.displayWithoutProcessing = true self.imageView = imageView
imageNode.displaysAsynchronously = false
self.imageNode = imageNode
self.addSubnode(imageNode) self.view.addSubview(imageView)
} }
self.imageNode?.image = image self.imageView?.image = image
if self.imageNode?.image?.renderingMode == .alwaysTemplate { if self.imageView?.image?.renderingMode == .alwaysTemplate {
self.imageNode?.tintColor = self.color self.imageView?.tintColor = self.color
} else { } else {
self.imageNode?.tintColor = nil self.imageView?.tintColor = nil
} }
} else if let imageNode = self.imageNode { } else if let imageView = self.imageView {
imageNode.removeFromSupernode() imageView.removeFromSuperview()
self.imageNode = nil self.imageView = nil
} }
self.invalidateCalculatedLayout() self.invalidateCalculatedLayout()
@ -311,11 +309,15 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
public var color: UIColor = UIColor(rgb: 0x0088ff) { public var color: UIColor = UIColor(rgb: 0x0088ff) {
didSet { didSet {
if self.imageNode?.image?.renderingMode == .alwaysTemplate { if self.color != oldValue {
self.imageNode?.tintColor = self.color if self.imageView?.image?.renderingMode == .alwaysTemplate {
} self.imageView?.tintColor = self.color
if let text = self._text { } else {
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) self.image = self.image
}
if let text = self._text {
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
}
} }
} }
} }
@ -422,11 +424,11 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height))
node.frame = CGRect(origin: CGPoint(), size: nodeSize) node.frame = CGRect(origin: CGPoint(), size: nodeSize)
return size return size
} else if let imageNode = self.imageNode { } else if let imageView = self.imageView {
let nodeSize = imageNode.image?.size ?? CGSize() let nodeSize = imageView.image?.size ?? CGSize()
let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(44.0, max(nodeSize.height, superSize.height))) let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(44.0, max(nodeSize.height, superSize.height)))
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0), y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize) let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0), y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize)
imageNode.frame = imageFrame imageView.frame = imageFrame
return size return size
} else { } else {
superSize.height = max(44.0, superSize.height) superSize.height = max(44.0, superSize.height)
@ -524,9 +526,15 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
public final class NavigationButtonNodeImpl: ContextControllerSourceNode, NavigationButtonNode { public final class NavigationButtonNodeImpl: ContextControllerSourceNode, NavigationButtonNode {
enum ContentType {
case accent
case generic
}
private let isGlass: Bool private let isGlass: Bool
private var isBack: Bool = false private var isBack: Bool = false
private var items: [UIBarButtonItem] = []
private var nodes: [NavigationButtonItemNode] = [] private var nodes: [NavigationButtonItemNode] = []
private var disappearingNodes: [(frame: CGRect, size: CGSize, node: NavigationButtonItemNode)] = [] private var disappearingNodes: [(frame: CGRect, size: CGSize, node: NavigationButtonItemNode)] = []
@ -648,6 +656,8 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga
} }
public func updateItems(_ items: [UIBarButtonItem], animated: Bool) { public func updateItems(_ items: [UIBarButtonItem], animated: Bool) {
self.items = items
for i in 0 ..< items.count { for i in 0 ..< items.count {
let node: NavigationButtonItemNode let node: NavigationButtonItemNode
if self.nodes.count > i { if self.nodes.count > i {
@ -676,8 +686,11 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga
node.alpha = self.manualAlpha node.alpha = self.manualAlpha
node.item = items[i] node.item = items[i]
if items[i].title == "___close" { if items[i].title == "___close" {
//node.image = glassCloseImage node.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/Close"), color: .white)?.withRenderingMode(.alwaysTemplate)
node.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/Close"), color: self.color) } else if items[i].title == "___clear" {
node.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: .white)?.withRenderingMode(.alwaysTemplate)
} else if items[i].title == "___done" {
node.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/Done"), color: .white)?.withRenderingMode(.alwaysTemplate)
} else { } else {
node.image = items[i].image node.image = items[i].image
node.text = items[i].title ?? "" node.text = items[i].title ?? ""
@ -749,7 +762,7 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga
nodeOrigin.x += 16.0 nodeOrigin.x += 16.0
} }
if !self.isGlass && node.node == nil && node.imageNode != nil && i == self.nodes.count - 1 { if !self.isGlass && node.node == nil && node.imageView != nil && i == self.nodes.count - 1 {
nodeOrigin.x -= 5.0 nodeOrigin.x -= 5.0
} }
} }
@ -791,4 +804,26 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga
} }
return true return true
} }
var commonContentType: ContentType {
var commonType: ContentType?
for item in self.items {
var nodeContentType: ContentType = .generic
if item.title == "___close" {
} else if item.title == "___clear" {
} else if item.title == "___done" {
nodeContentType = .accent
}
if commonType == nil {
commonType = nodeContentType
} else {
if commonType != nodeContentType {
return .generic
}
}
}
return commonType ?? .generic
}
} }

View file

@ -4949,7 +4949,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self?.deactivateSearch() self?.deactivateSearch()
}, fieldStyle: .glass) }, fieldStyle: .glass)
} else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessagesChats = currentPaneKey { } else if let currentPaneKey = self.paneContainerNode.currentPaneKey, case .savedMessagesChats = currentPaneKey {
let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer, filter: [.removeSearchHeader], requestPeerType: nil, location: .savedMessagesChats(peerId: self.context.account.peerId), displaySearchFilters: false, hasDownloads: false, initialFilter: .chats, openPeer: { [weak self] peer, _, _, _ in let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer, filter: [.removeSearchHeader], requestPeerType: nil, location: .savedMessagesChats(peerId: self.context.account.peerId), folder: nil, displaySearchFilters: false, hasDownloads: false, initialFilter: .chats, openPeer: { [weak self] peer, _, _, _ in
guard let self else { guard let self else {
return return
} }
@ -6460,7 +6460,9 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
separatorColor: .clear, separatorColor: .clear,
badgeBackgroundColor: baseNavigationBarPresentationData.theme.badgeBackgroundColor, badgeBackgroundColor: baseNavigationBarPresentationData.theme.badgeBackgroundColor,
badgeStrokeColor: baseNavigationBarPresentationData.theme.badgeStrokeColor, badgeStrokeColor: baseNavigationBarPresentationData.theme.badgeStrokeColor,
badgeTextColor: baseNavigationBarPresentationData.theme.badgeTextColor badgeTextColor: baseNavigationBarPresentationData.theme.badgeTextColor,
accentButtonColor: baseNavigationBarPresentationData.theme.accentButtonColor,
accentForegroundColor: baseNavigationBarPresentationData.theme.accentForegroundColor
), strings: baseNavigationBarPresentationData.strings)) ), strings: baseNavigationBarPresentationData.strings))
self._hasGlassStyle = true self._hasGlassStyle = true

View file

@ -1330,6 +1330,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
filter: self.filter, filter: self.filter,
requestPeerType: self.requestPeerType, requestPeerType: self.requestPeerType,
location: chatListLocation, location: chatListLocation,
folder: nil,
displaySearchFilters: false, displaySearchFilters: false,
hasDownloads: false, hasDownloads: false,
openPeer: { [weak self] peer, chatPeer, threadId, _ in openPeer: { [weak self] peer, chatPeer, threadId, _ in

View file

@ -1172,7 +1172,7 @@ extension ChatControllerImpl {
} }
return updatedState return updatedState
}) })
self.searchResult.set(.single((results, state, .general(scope: .channels, tags: nil, minDate: nil, maxDate: nil)))) self.searchResult.set(.single((results, state, .general(scope: .channels, tags: nil, minDate: nil, maxDate: nil, folderId: nil))))
} }
} }
@ -2412,7 +2412,7 @@ extension ChatControllerImpl {
} }
} }
}, openCalendarSearch: { [weak self] in }, openCalendarSearch: { [weak self] in
self?.openCalendarSearch(timestamp: Int32(Date().timeIntervalSince1970)) self?.openCalendarSearch(timestamp: Int32(Date().timeIntervalSince1970), isMedia: false)
}, toggleMembersSearch: { [weak self] value in }, toggleMembersSearch: { [weak self] value in
if let strongSelf = self { if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in

View file

@ -3468,7 +3468,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch strongSelf.chatLocation { switch strongSelf.chatLocation {
case let .peer(peerId): case let .peer(peerId):
if alreadyThere { if alreadyThere {
strongSelf.openCalendarSearch(timestamp: timestamp) strongSelf.openCalendarSearch(timestamp: timestamp, isMedia: true)
} else { } else {
strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil) strongSelf.navigateToMessage(from: nil, to: .index(MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: 0), timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT()))), scrollPosition: .bottom(0.0), rememberInStack: false, animated: true, completion: nil)
} }

View file

@ -14,6 +14,7 @@ import ChatListUI
import EmojiStatusComponent import EmojiStatusComponent
import TelegramUIPreferences import TelegramUIPreferences
import TranslateUI import TranslateUI
import TelegramNotices
extension ChatControllerImpl { extension ChatControllerImpl {
final class ContentData { final class ContentData {
@ -507,6 +508,25 @@ extension ChatControllerImpl {
messageOptionsTitleInfo = .single(nil) messageOptionsTitleInfo = .single(nil)
} }
var savedMessagesChatsTip: Signal<Bool, NoError> = .single(false)
if case .peer(context.account.peerId) = chatLocation {
let hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved()
if chatLocation.threadId == nil {
savedMessagesChatsTip = hasSavedChats
|> distinctUntilChanged
|> mapToSignal { value -> Signal<Bool, NoError> in
if !value {
return .single(false)
}
return ApplicationSpecificNotice.getSavedMessagesChatListView(accountManager: context.sharedContext.accountManager)
|> map { value -> Bool in
return value < 5
}
}
|> distinctUntilChanged
}
}
self.titleDisposable = (combineLatest( self.titleDisposable = (combineLatest(
queue: Queue.mainQueue(), queue: Queue.mainQueue(),
peerView.get(), peerView.get(),
@ -515,9 +535,10 @@ extension ChatControllerImpl {
subtitleTextSignal, subtitleTextSignal,
configuration, configuration,
hasPeerInfo, hasPeerInfo,
messageOptionsTitleInfo messageOptionsTitleInfo,
savedMessagesChatsTip
) )
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, configuration, hasPeerInfo, messageOptionsTitleInfo in |> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, configuration, hasPeerInfo, messageOptionsTitleInfo, savedMessagesChatsTip in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -586,7 +607,13 @@ extension ChatControllerImpl {
strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(channel.debugDisplayTitle))], subtitle: nil, isEnabled: true) strongSelf.state.chatTitleContent = .custom(title: [ChatTitleContent.TitleTextItem(id: AnyHashable(0), content: .text(channel.debugDisplayTitle))], subtitle: nil, isEnabled: true)
} }
} else { } else {
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) var customSubtitle: String?
if savedMessagesChatsTip {
//TODO:localize
customSubtitle = "Tap to view as chats"
}
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: customSubtitle, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
let imageOverride: AvatarNodeImageOverride? let imageOverride: AvatarNodeImageOverride?
if context.account.peerId == peer.id { if context.account.peerId == peer.id {

View file

@ -10,83 +10,48 @@ import ChatControllerInteraction
import Display import Display
import UIKit import UIKit
import UndoUI import UndoUI
import ChatScheduleTimeController
extension ChatControllerImpl { extension ChatControllerImpl {
func openCalendarSearch(timestamp: Int32) { func openCalendarSearch(timestamp: Int32, isMedia: Bool) {
guard let peerId = self.chatLocation.peerId else { guard let peerId = self.chatLocation.peerId else {
return return
} }
self.chatDisplayNode.dismissInput() self.chatDisplayNode.dismissInput()
let initialTimestamp = timestamp
var dismissCalendarScreen: (() -> Void)?
var selectDay: ((Int32) -> Void)?
var openClearHistory: ((Int32) -> Void)?
let enableMessageRangeDeletion: Bool = peerId.namespace == Namespaces.Peer.CloudUser
let displayMedia = self.presentationInterfaceState.historyFilter == nil if isMedia {
let initialTimestamp = timestamp
let calendarScreen = CalendarMessageScreen( var dismissCalendarScreen: (() -> Void)?
context: self.context, var selectDay: ((Int32) -> Void)?
peerId: peerId, var openClearHistory: ((Int32) -> Void)?
calendarSource: self.context.engine.messages.sparseMessageCalendar(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photoOrVideo, displayMedia: displayMedia),
initialTimestamp: initialTimestamp, let enableMessageRangeDeletion: Bool = peerId.namespace == Namespaces.Peer.CloudUser
enableMessageRangeDeletion: enableMessageRangeDeletion,
canNavigateToEmptyDays: true, let displayMedia = self.presentationInterfaceState.historyFilter == nil
navigateToDay: { [weak self] c, index, timestamp in
guard let strongSelf = self else { let calendarScreen = CalendarMessageScreen(
c.dismiss() context: self.context,
return peerId: peerId,
} calendarSource: self.context.engine.messages.sparseMessageCalendar(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photoOrVideo, displayMedia: displayMedia),
initialTimestamp: initialTimestamp,
strongSelf.alwaysShowSearchResultsAsList = false enableMessageRangeDeletion: enableMessageRangeDeletion,
strongSelf.chatDisplayNode.alwaysShowSearchResultsAsList = false canNavigateToEmptyDays: true,
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in navigateToDay: { [weak self] c, index, timestamp in
return state.updatedDisplayHistoryFilterAsList(false).updatedSearch(nil) guard let strongSelf = self else {
}) c.dismiss()
return
c.dismiss()
strongSelf.loadingMessage.set(.single(.generic))
let peerId: PeerId
let threadId: Int64?
switch strongSelf.chatLocation {
case let .peer(peerIdValue):
peerId = peerIdValue
threadId = nil
case let .replyThread(replyThreadMessage):
peerId = replyThreadMessage.peerId
threadId = replyThreadMessage.threadId
case .customChatContents:
return
}
strongSelf.messageIndexDisposable.set((strongSelf.context.engine.messages.searchMessageIdByTimestamp(peerId: peerId, threadId: threadId, timestamp: timestamp) |> deliverOnMainQueue).startStrict(next: { messageId in
if let strongSelf = self {
strongSelf.loadingMessage.set(.single(nil))
if let messageId = messageId {
strongSelf.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: true)
}
} }
}))
}, strongSelf.alwaysShowSearchResultsAsList = false
previewDay: { [weak self] timestamp, _, sourceNode, sourceRect, gesture in strongSelf.chatDisplayNode.alwaysShowSearchResultsAsList = false
guard let strongSelf = self else { strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
return return state.updatedDisplayHistoryFilterAsList(false).updatedSearch(nil)
} })
var items: [ContextMenuItem] = [] c.dismiss()
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Chat_JumpToDate, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
dismissCalendarScreen?()
strongSelf.loadingMessage.set(.single(.generic)) strongSelf.loadingMessage.set(.single(.generic))
let peerId: PeerId let peerId: PeerId
let threadId: Int64? let threadId: Int64?
switch strongSelf.chatLocation { switch strongSelf.chatLocation {
@ -99,7 +64,7 @@ extension ChatControllerImpl {
case .customChatContents: case .customChatContents:
return return
} }
strongSelf.messageIndexDisposable.set((strongSelf.context.engine.messages.searchMessageIdByTimestamp(peerId: peerId, threadId: threadId, timestamp: timestamp) |> deliverOnMainQueue).startStrict(next: { messageId in strongSelf.messageIndexDisposable.set((strongSelf.context.engine.messages.searchMessageIdByTimestamp(peerId: peerId, threadId: threadId, timestamp: timestamp) |> deliverOnMainQueue).startStrict(next: { messageId in
if let strongSelf = self { if let strongSelf = self {
strongSelf.loadingMessage.set(.single(nil)) strongSelf.loadingMessage.set(.single(nil))
@ -108,84 +73,168 @@ extension ChatControllerImpl {
} }
} }
})) }))
}))) },
previewDay: { [weak self] timestamp, _, sourceNode, sourceRect, gesture in
if enableMessageRangeDeletion && (peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat) { guard let strongSelf = self else {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DialogList_ClearHistoryConfirmation, textColor: .destructive, icon: { theme in return
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Chat_JumpToDate, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
openClearHistory?(timestamp) dismissCalendarScreen?()
})))
strongSelf.loadingMessage.set(.single(.generic))
items.append(.separator)
let peerId: PeerId
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Select, icon: { theme in let threadId: Int64?
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) switch strongSelf.chatLocation {
}, action: { _, f in case let .peer(peerIdValue):
f(.dismissWithoutContent) peerId = peerIdValue
selectDay?(timestamp) threadId = nil
}))) case let .replyThread(replyThreadMessage):
} peerId = replyThreadMessage.peerId
threadId = replyThreadMessage.threadId
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .message(id: .timestamp(timestamp), highlight: nil, timecode: nil, setupReply: false), botStart: nil, mode: .standard(.previewing), params: nil) case .customChatContents:
chatController.canReadHistory.set(false) return
}
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
strongSelf.messageIndexDisposable.set((strongSelf.context.engine.messages.searchMessageIdByTimestamp(peerId: peerId, threadId: threadId, timestamp: timestamp) |> deliverOnMainQueue).startStrict(next: { messageId in
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) if let strongSelf = self {
strongSelf.presentInGlobalOverlay(contextController) strongSelf.loadingMessage.set(.single(nil))
} if let messageId = messageId {
) strongSelf.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: true)
}
calendarScreen.completedWithRemoveMessagesInRange = { [weak self] range, type, dayCount, calendarSource in
guard let strongSelf = self else {
return
}
let statusText: String
switch type {
case .forEveryone:
statusText = strongSelf.presentationData.strings.Chat_MessageRangeDeleted_ForBothSides(Int32(dayCount))
default:
statusText = strongSelf.presentationData.strings.Chat_MessageRangeDeleted_ForMe(Int32(dayCount))
}
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = range
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, action: { value in
guard let strongSelf = self else {
return false
}
if value == .commit {
let _ = calendarSource.removeMessagesInRange(minTimestamp: range.lowerBound, maxTimestamp: range.upperBound, type: type, completion: {
Queue.mainQueue().after(1.0, {
guard let strongSelf = self else {
return
} }
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = nil }))
}) })))
})
return true if enableMessageRangeDeletion && (peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat) {
} else if value == .undo { items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DialogList_ClearHistoryConfirmation, textColor: .destructive, icon: { theme in
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = nil return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
return true }, action: { _, f in
f(.dismissWithoutContent)
openClearHistory?(timestamp)
})))
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Select, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
selectDay?(timestamp)
})))
}
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .message(id: .timestamp(timestamp), highlight: nil, timecode: nil, setupReply: false), botStart: nil, mode: .standard(.previewing), params: nil)
chatController.canReadHistory.set(false)
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
let contextController = makeContextController(presentationData: strongSelf.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
} }
return false )
}), in: .current)
} calendarScreen.completedWithRemoveMessagesInRange = { [weak self] range, type, dayCount, calendarSource in
guard let strongSelf = self else {
self.effectiveNavigationController?.pushViewController(calendarScreen) return
}
dismissCalendarScreen = { [weak calendarScreen] in
calendarScreen?.dismiss(completion: nil) let statusText: String
} switch type {
selectDay = { [weak calendarScreen] timestamp in case .forEveryone:
calendarScreen?.selectDay(timestamp: timestamp) statusText = strongSelf.presentationData.strings.Chat_MessageRangeDeleted_ForBothSides(Int32(dayCount))
} default:
openClearHistory = { [weak calendarScreen] timestamp in statusText = strongSelf.presentationData.strings.Chat_MessageRangeDeleted_ForMe(Int32(dayCount))
calendarScreen?.openClearHistory(timestamp: timestamp) }
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = range
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(context: strongSelf.context, title: NSAttributedString(string: statusText), text: nil), elevatedLayout: false, action: { value in
guard let strongSelf = self else {
return false
}
if value == .commit {
let _ = calendarSource.removeMessagesInRange(minTimestamp: range.lowerBound, maxTimestamp: range.upperBound, type: type, completion: {
Queue.mainQueue().after(1.0, {
guard let strongSelf = self else {
return
}
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = nil
})
})
return true
} else if value == .undo {
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = nil
return true
}
return false
}), in: .current)
}
self.effectiveNavigationController?.pushViewController(calendarScreen)
dismissCalendarScreen = { [weak calendarScreen] in
calendarScreen?.dismiss(completion: nil)
}
selectDay = { [weak calendarScreen] timestamp in
calendarScreen?.selectDay(timestamp: timestamp)
}
openClearHistory = { [weak calendarScreen] timestamp in
calendarScreen?.openClearHistory(timestamp: timestamp)
}
} else {
let controller = ChatScheduleTimeScreen(
context: self.context,
mode: .search,
currentTime: nil,
currentRepeatPeriod: nil,
minimalTime: nil,
isDark: false,
completion: { [weak self] result in
guard let self else {
return
}
self.alwaysShowSearchResultsAsList = false
self.chatDisplayNode.alwaysShowSearchResultsAsList = false
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
return state.updatedDisplayHistoryFilterAsList(false).updatedSearch(nil)
})
self.loadingMessage.set(.single(.generic))
let peerId: PeerId
let threadId: Int64?
switch self.chatLocation {
case let .peer(peerIdValue):
peerId = peerIdValue
threadId = nil
case let .replyThread(replyThreadMessage):
peerId = replyThreadMessage.peerId
threadId = replyThreadMessage.threadId
case .customChatContents:
return
}
self.messageIndexDisposable.set((self.context.engine.messages.searchMessageIdByTimestamp(peerId: peerId, threadId: threadId, timestamp: result.time) |> deliverOnMainQueue).startStrict(next: { [weak self] messageId in
guard let self else {
return
}
self.loadingMessage.set(.single(nil))
if let messageId {
self.navigateToMessage(from: nil, to: .id(messageId, NavigateToMessageParams(timestamp: nil, quote: nil)), forceInCurrentChat: true)
}
}))
}
)
self.push(controller)
} }
} }
} }

View file

@ -47,10 +47,14 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
} }
if canClear { if canClear {
return ChatNavigationButton(action: .clearHistory, buttonItem: UIBarButtonItem(title: title, style: .plain, target: target, action: selector)) let buttonItem = UIBarButtonItem(title: "___clear", style: .plain, target: target, action: selector)
buttonItem.accessibilityLabel = title
return ChatNavigationButton(action: .clearHistory, buttonItem: buttonItem)
} else { } else {
title = strings.Conversation_ClearCache title = strings.Conversation_ClearCache
return ChatNavigationButton(action: .clearCache, buttonItem: UIBarButtonItem(title: title, style: .plain, target: target, action: selector)) let buttonItem = UIBarButtonItem(title: "___clear", style: .plain, target: target, action: selector)
buttonItem.accessibilityLabel = title
return ChatNavigationButton(action: .clearCache, buttonItem: buttonItem)
} }
} }
} }
@ -88,7 +92,7 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present
if let currentButton = currentButton, currentButton.action == .cancelMessageSelection { if let currentButton = currentButton, currentButton.action == .cancelMessageSelection {
return currentButton return currentButton
} else { } else {
let buttonItem = UIBarButtonItem(title: strings.Common_Cancel, style: .plain, target: target, action: selector) let buttonItem = UIBarButtonItem(title: "___done", style: .plain, target: target, action: selector)
buttonItem.accessibilityLabel = strings.Common_Cancel buttonItem.accessibilityLabel = strings.Common_Cancel
return ChatNavigationButton(action: .cancelMessageSelection, buttonItem: buttonItem) return ChatNavigationButton(action: .cancelMessageSelection, buttonItem: buttonItem)
} }

View file

@ -60,7 +60,7 @@ final class WebSearchGalleryControllerPresentationArguments {
} }
class WebSearchGalleryController: ViewController { class WebSearchGalleryController: ViewController {
private static let navigationTheme = NavigationBarTheme(overallDarkAppearance: false, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) private static let navigationTheme = NavigationBarTheme(overallDarkAppearance: false, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, accentButtonColor: .white, accentForegroundColor: .black)
private var galleryNode: GalleryControllerNode { private var galleryNode: GalleryControllerNode {
return self.displayNode as! GalleryControllerNode return self.displayNode as! GalleryControllerNode

View file

@ -3800,7 +3800,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
separatorColor: UIColor(rgb: 0x000000, alpha: 0.25), separatorColor: UIColor(rgb: 0x000000, alpha: 0.25),
badgeBackgroundColor: .clear, badgeBackgroundColor: .clear,
badgeStrokeColor: .clear, badgeStrokeColor: .clear,
badgeTextColor: .clear badgeTextColor: .clear,
accentButtonColor: self.presentationData.theme.list.itemCheckColors.fillColor,
accentForegroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor
), ),
strings: NavigationBarStrings(back: "", close: "") strings: NavigationBarStrings(back: "", close: "")
) )