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 !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 {
peers.append(peer)
}
@ -988,7 +988,7 @@ private final class WidgetIntentHandler {
if let searchTerm = searchTerm {
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 {
peers.append(peer)
}

View file

@ -34,7 +34,7 @@ private enum InnerState: Equatable {
public final class AuthorizationSequenceController: NavigationController, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
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

View file

@ -1949,7 +1949,21 @@ final class ChatListControllerNode: ASDisplayNode, ASGestureRecognizerDelegate {
//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)
}, openDisabledPeer: { _, _, _ in
}, openRecentPeerOptions: { [weak self] peer in

View file

@ -43,6 +43,7 @@ import ComponentDisplayAdapters
private enum ChatListTokenId: Int32 {
case archive
case folder
case forum
case filter
case peer
@ -102,6 +103,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let peersFilter: ChatListNodePeersFilter
private let requestPeerType: [ReplyMarkupButtonRequestPeerType]?
private var location: ChatListControllerLocation
private var folder: (Int32, String)?
private let displaySearchFilters: Bool
private let hasDownloads: Bool
private var interaction: ChatListSearchInteraction?
@ -165,7 +167,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var recentAppsDisposable: 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
if case .chats = initialFilter, case .forum = location {
initialFilter = .topics
@ -175,6 +177,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.peersFilter = filter
self.requestPeerType = requestPeerType
self.location = location
self.folder = folder
self.displaySearchFilters = displaySearchFilters
self.hasDownloads = hasDownloads
self.navigationController = navigationController
@ -194,6 +199,11 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.paneContainerNode.clipsToBounds = true
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
@ -588,7 +598,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
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]) {
@ -609,12 +619,19 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
if !tokensIdSet.contains(ChatListTokenId.peer.rawValue) && updatedOptions?.peer != nil {
updatedOptions = updatedOptions?.withUpdatedPeer(nil)
}
if !tokensIdSet.contains(ChatListTokenId.folder.rawValue) && updatedOptions?.folder != nil {
updatedOptions = updatedOptions?.withUpdatedFolder(nil)
self.folder = nil
}
self.updateSearchOptions(updatedOptions)
}
private func updateSearchOptions(_ options: ChatListSearchOptions?, clearQuery: Bool = false) {
var options = options
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 {
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 {
@ -737,10 +754,13 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
key = .chats
}
self.paneContainerNode.requestSelectPane(key)
self.updateSearchOptions(nil)
self.updateSearchOptions(self.currentSearchOptions)
self.searchTextUpdated(text: query ?? "")
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 {
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 {

View file

@ -1448,17 +1448,22 @@ public enum ChatListSearchContextActionSource {
public struct ChatListSearchOptions {
let peer: (EnginePeer.Id, Bool, String)?
let date: (Int32?, Int32, String)?
let folder: (Int32, String)?
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 {
return ChatListSearchOptions(peer: peerIdIsGroupAndName, date: self.date)
return ChatListSearchOptions(peer: peerIdIsGroupAndName, date: self.date, folder: self.folder)
}
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))
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
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)
|> debounceOnMainThread
|> 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)
return .single(nil)
}
@ -2244,7 +2249,24 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
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
return context.engine.data.subscribe(
EngineDataMap(peers.map { peer in
@ -2552,7 +2574,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if case .savedMessagesChats = location {
foundRemotePeers = .single(([], [], [], false))
} else if let query = query, case .chats = key {
if query.hasPrefix("#") {
if query.hasPrefix("#") || options?.folder != nil {
foundRemotePeers = .single(([], [], [], false))
} else {
foundRemotePeers = (
@ -2584,28 +2606,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
let searchLocations: [SearchMessagesLocation]
if key == .globalPosts {
searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil)]
} else if let options = options {
searchLocations = [SearchMessagesLocation.general(scope: .globalPosts(allowPaidStars: approvedGlobalPostQueryState?.price), tags: nil, minDate: nil, maxDate: nil, folderId: nil)]
} else if let options {
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 {
searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, reactions: nil, threadId: nil, minDate: options.date?.0, maxDate: options.date?.1)]
} else {
if case let .chatList(groupId) = location, case .archive = groupId {
searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)]
} 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 {
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 {
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 {
searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)]
} 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>]
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 {
searchSignals = searchLocations.map { searchLocation in
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)
}
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 {
let previousPanelHideBehavior = self.component?.panelHideBehavior

View file

@ -28,10 +28,12 @@ public final class NavigationBarTheme {
public let badgeStrokeColor: UIColor
public let badgeTextColor: UIColor
public let edgeEffectColor: UIColor?
public let accentButtonColor: UIColor
public let accentForegroundColor: UIColor
public let style: NavigationBar.Style
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.buttonColor = buttonColor
self.disabledButtonColor = disabledButtonColor
@ -44,16 +46,18 @@ public final class NavigationBarTheme {
self.badgeStrokeColor = badgeStrokeColor
self.badgeTextColor = badgeTextColor
self.edgeEffectColor = edgeEffectColor
self.accentButtonColor = accentButtonColor
self.accentForegroundColor = accentForegroundColor
self.style = style
self.glassStyle = glassStyle
}
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 {
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 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 {
return self.displayNode as! GalleryControllerNode

View file

@ -54,7 +54,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol {
if self.publicPosts {
search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: nil)
} 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)
@ -102,7 +102,7 @@ final class HashtagSearchGlobalChatContents: ChatCustomContentsProtocol {
if self.publicPosts {
search = self.context.engine.messages.searchHashtagPosts(hashtag: self.query, state: self.currentSearchState)
} 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()

View file

@ -39,7 +39,7 @@ public class SetupTwoStepVerificationController: ViewController {
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

View file

@ -71,7 +71,7 @@ public final class TwoFactorDataInputScreen: ViewController {
self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
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)))

View file

@ -57,7 +57,7 @@ public final class TwoFactorAuthSplashScreen: ViewController {
self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
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)))

View file

@ -1213,9 +1213,9 @@ public final class Transaction {
self.postbox?.reindexUnreadCounters(currentTransaction: self)
}
public func searchPeers(query: String) -> [RenderedPeer] {
public func searchPeers(query: String, predicate: ChatListFilterPredicate?) -> [RenderedPeer] {
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) {
@ -3800,13 +3800,13 @@ final class PostboxImpl {
} |> 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 .single(transaction.searchPeers(query: query))
return .single(transaction.searchPeers(query: query, predicate: predicate))
} |> switchToLatest
}
fileprivate func searchPeers(query: String) -> [RenderedPeer] {
fileprivate func searchPeers(transaction: Transaction, query: String, predicate: ChatListFilterPredicate?) -> [RenderedPeer] {
var peerIds = Set<PeerId>()
var chatPeers: [RenderedPeer] = []
@ -3823,6 +3823,30 @@ final class PostboxImpl {
}
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 {
if let peer = self.peerTable.get(peerId) {
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
let disposable = MetaDisposable()
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

View file

@ -77,7 +77,7 @@ public final class QrCodeScanScreen: ViewController {
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)))

View file

@ -928,6 +928,9 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
} set {
self.textField.tokens = newValue
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)
}
public func searchLocalPeers(query: String, scope: TelegramSearchPeersScope = .everywhere) -> Signal<[EngineRenderedPeer], NoError> {
return self.account.postbox.searchPeers(query: query)
public func searchLocalPeers(query: String, scope: TelegramSearchPeersScope = .everywhere, predicate: ChatListFilterPredicate? = nil) -> Signal<[EngineRenderedPeer], NoError> {
return self.account.postbox.searchPeers(query: query, predicate: predicate)
|> map { peers in
switch scope {
case .everywhere:

View file

@ -6,7 +6,7 @@ import MtProtoKit
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 peer(peerId: PeerId, fromId: PeerId?, tags: MessageTags?, reactions: [MessageReaction.Reaction]?, threadId: Int64?, minDate: Int32?, maxDate: Int32?)
case sentMedia(tags: MessageTags?)
@ -471,7 +471,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
}
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
let folderId: Int32?
if case let .group(groupId, _, _, _) = location {
@ -481,7 +481,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
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
var lowerBound: MessageIndex?
if let state = state, let message = state.main.messages.last {
@ -508,7 +508,7 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
}
}
} else {
if case let .general(scope, _, _, _) = location {
if case let .general(scope, _, _, _, _) = location {
switch scope {
case .everywhere:
break
@ -567,9 +567,9 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
if state?.additional == nil {
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]
if case let .general(scope, _, _, _) = location, case .channels = scope {
if case let .general(scope, _, _, _, _) = location, case .channels = scope {
secretMessages = []
} else {
secretMessages = transaction.searchMessages(peerId: nil, query: query, tags: tags)

View file

@ -208,6 +208,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case giftCraftingTips = 85
case copyProtectionTips = 86
case aiTextProcessingStyleSelectionTips = 87
case savedMessagesChatListView = 88
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -589,6 +590,10 @@ private struct ApplicationSpecificNoticeKeys {
static func aiTextProcessingStyleSelectionTips() -> NoticeEntryKey {
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 {
@ -2610,4 +2615,31 @@ public struct ApplicationSpecificNotice {
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
}
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:
recentDocuments = .single(nil)
|> 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
return result.0.messages
}

View file

@ -530,7 +530,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
case .audio:
shared = .single(nil)
|> 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())
|> map { result -> [Message]? in
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 stateContext: StateContext?
private let entityKeyboardView: ComponentHostView<Empty>
private let entityKeyboardView: ComponentView<Empty>
private let defaultToEmojiTab: Bool
private var stableReorderableGroupOrder: [EntityKeyboardComponent.ReorderCategory: [ItemCollectionId]] = [:]
@ -449,7 +449,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
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") {
return false
}
@ -496,11 +496,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
self.interaction = interaction
self.clippingView = UIView()
self.clippingView = SparseContainerView()
self.clippingView.clipsToBounds = true
self.clippingView.layer.cornerRadius = keyboardCornerRadius
self.entityKeyboardView = ComponentHostView<Empty>()
self.entityKeyboardView = ComponentView()
super.init()
@ -538,7 +538,6 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
)
}
self.clippingView.addSubview(self.entityKeyboardView)
self.view.addSubview(self.clippingView)
if let backgroundChromeView = self.backgroundChromeView {
@ -692,7 +691,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
for featuredStickerPack in stickerPacks {
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(
context: self.context,
forceTheme: self.interaction?.forceTheme,
@ -1326,7 +1325,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
))
},
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()
}
},
@ -1630,7 +1629,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
var transition: ComponentTransition = .immediate
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") {
useAnimation = strongSelf.currentInputData.emoji != inputData.emoji
} 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)
},
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()
}
},
@ -1798,8 +1797,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = super.hitTest(point, with: event) {
return result
if self.alpha.isZero || !self.view.isUserInteractionEnabled {
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) {
@ -2025,7 +2029,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
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)
@ -2366,7 +2375,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
}
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)
}
}

View file

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

View file

@ -272,7 +272,7 @@ public final class EntityKeyboardComponent: Component {
public final class View: UIView {
private let tintContainerView: UIView
private let pagerView: ComponentHostView<EntityKeyboardChildEnvironment>
private let pagerView: ComponentView<EntityKeyboardChildEnvironment>
private var component: EntityKeyboardComponent?
public private(set) weak var state: EmptyComponentState?
@ -295,20 +295,32 @@ public final class EntityKeyboardComponent: Component {
override init(frame: CGRect) {
self.tintContainerView = UIView()
self.pagerView = ComponentHostView<EntityKeyboardChildEnvironment>()
self.pagerView = ComponentView()
super.init(frame: frame)
//self.clipsToBounds = true
self.disablesInteractiveTransitionGestureRecognizer = true
self.addSubview(self.pagerView)
}
required init?(coder: NSCoder) {
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 {
self.state = state
@ -787,7 +799,12 @@ public final class EntityKeyboardComponent: Component {
forceUpdate: forceUpdate,
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
if let searchComponent = self.searchComponent, let accountContext = accountContext {

View file

@ -441,9 +441,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
self.leftButtonNodeImpl.view.removeFromSuperview()
var backTitle: String?
if case .glass = self.presentationData.theme.style {
backTitle = ""
} else if let customBackButtonText = self.customBackButtonText {
if let customBackButtonText = self.customBackButtonText {
backTitle = customBackButtonText
} else if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance {
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 {
self.backButtonNodeImpl.updateManualText(backTitle, isBack: true)
if self.backButtonNodeImpl.supernode == nil {
@ -631,10 +635,6 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
self.backButtonNodeImpl.color = self.presentationData.theme.buttonColor
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)
if let title = self.title {
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.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)
if let title = self.title {
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
}
} 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)
leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0
@ -937,6 +942,15 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
var rightButtonsWidth: CGFloat = 0.0
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)
if !self.rightButtonNodeImpl.isEmpty {
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.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)
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 {
@ -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.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 {
rightButtonsBackgroundView.background.isHidden = true
}
@ -1038,7 +1069,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
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)
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)
} else {
let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), 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)
let titleSize = CGSize(width: max(1.0, size.width - leftTitleInset - rightTitleInset), height: nominalHeight)
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
if titleView.frame.isEmpty {
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?
public var image: UIImage? {
@ -271,23 +271,21 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
_image = value
if let _ = value {
if self.imageNode == nil {
let imageNode = ASImageNode()
imageNode.displayWithoutProcessing = true
imageNode.displaysAsynchronously = false
self.imageNode = imageNode
if self.imageView == nil {
let imageView = UIImageView()
self.imageView = imageView
self.addSubnode(imageNode)
self.view.addSubview(imageView)
}
self.imageNode?.image = image
if self.imageNode?.image?.renderingMode == .alwaysTemplate {
self.imageNode?.tintColor = self.color
self.imageView?.image = image
if self.imageView?.image?.renderingMode == .alwaysTemplate {
self.imageView?.tintColor = self.color
} else {
self.imageNode?.tintColor = nil
self.imageView?.tintColor = nil
}
} else if let imageNode = self.imageNode {
imageNode.removeFromSupernode()
self.imageNode = nil
} else if let imageView = self.imageView {
imageView.removeFromSuperview()
self.imageView = nil
}
self.invalidateCalculatedLayout()
@ -311,11 +309,15 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
public var color: UIColor = UIColor(rgb: 0x0088ff) {
didSet {
if self.imageNode?.image?.renderingMode == .alwaysTemplate {
self.imageNode?.tintColor = self.color
}
if let text = self._text {
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
if self.color != oldValue {
if self.imageView?.image?.renderingMode == .alwaysTemplate {
self.imageView?.tintColor = self.color
} else {
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))
node.frame = CGRect(origin: CGPoint(), size: nodeSize)
return size
} else if let imageNode = self.imageNode {
let nodeSize = imageNode.image?.size ?? CGSize()
} else if let imageView = self.imageView {
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 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
} else {
superSize.height = max(44.0, superSize.height)
@ -524,9 +526,15 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
public final class NavigationButtonNodeImpl: ContextControllerSourceNode, NavigationButtonNode {
enum ContentType {
case accent
case generic
}
private let isGlass: Bool
private var isBack: Bool = false
private var items: [UIBarButtonItem] = []
private var nodes: [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) {
self.items = items
for i in 0 ..< items.count {
let node: NavigationButtonItemNode
if self.nodes.count > i {
@ -676,8 +686,11 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga
node.alpha = self.manualAlpha
node.item = items[i]
if items[i].title == "___close" {
//node.image = glassCloseImage
node.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/Close"), color: self.color)
node.image = generateTintedImage(image: UIImage(bundleImageName: "Navigation/Close"), color: .white)?.withRenderingMode(.alwaysTemplate)
} 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 {
node.image = items[i].image
node.text = items[i].title ?? ""
@ -749,7 +762,7 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga
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
}
}
@ -791,4 +804,26 @@ public final class NavigationButtonNodeImpl: ContextControllerSourceNode, Naviga
}
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()
}, fieldStyle: .glass)
} 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 {
return
}
@ -6460,7 +6460,9 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
separatorColor: .clear,
badgeBackgroundColor: baseNavigationBarPresentationData.theme.badgeBackgroundColor,
badgeStrokeColor: baseNavigationBarPresentationData.theme.badgeStrokeColor,
badgeTextColor: baseNavigationBarPresentationData.theme.badgeTextColor
badgeTextColor: baseNavigationBarPresentationData.theme.badgeTextColor,
accentButtonColor: baseNavigationBarPresentationData.theme.accentButtonColor,
accentForegroundColor: baseNavigationBarPresentationData.theme.accentForegroundColor
), strings: baseNavigationBarPresentationData.strings))
self._hasGlassStyle = true

View file

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

View file

@ -1172,7 +1172,7 @@ extension ChatControllerImpl {
}
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
self?.openCalendarSearch(timestamp: Int32(Date().timeIntervalSince1970))
self?.openCalendarSearch(timestamp: Int32(Date().timeIntervalSince1970), isMedia: false)
}, toggleMembersSearch: { [weak self] value in
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in

View file

@ -3468,7 +3468,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch strongSelf.chatLocation {
case let .peer(peerId):
if alreadyThere {
strongSelf.openCalendarSearch(timestamp: timestamp)
strongSelf.openCalendarSearch(timestamp: timestamp, isMedia: true)
} 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)
}

View file

@ -14,6 +14,7 @@ import ChatListUI
import EmojiStatusComponent
import TelegramUIPreferences
import TranslateUI
import TelegramNotices
extension ChatControllerImpl {
final class ContentData {
@ -507,6 +508,25 @@ extension ChatControllerImpl {
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(
queue: Queue.mainQueue(),
peerView.get(),
@ -515,9 +535,10 @@ extension ChatControllerImpl {
subtitleTextSignal,
configuration,
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 {
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)
}
} 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?
if context.account.peerId == peer.id {

View file

@ -10,83 +10,48 @@ import ChatControllerInteraction
import Display
import UIKit
import UndoUI
import ChatScheduleTimeController
extension ChatControllerImpl {
func openCalendarSearch(timestamp: Int32) {
func openCalendarSearch(timestamp: Int32, isMedia: Bool) {
guard let peerId = self.chatLocation.peerId else {
return
}
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
let calendarScreen = CalendarMessageScreen(
context: self.context,
peerId: peerId,
calendarSource: self.context.engine.messages.sparseMessageCalendar(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photoOrVideo, displayMedia: displayMedia),
initialTimestamp: initialTimestamp,
enableMessageRangeDeletion: enableMessageRangeDeletion,
canNavigateToEmptyDays: true,
navigateToDay: { [weak self] c, index, timestamp in
guard let strongSelf = self else {
c.dismiss()
return
}
strongSelf.alwaysShowSearchResultsAsList = false
strongSelf.chatDisplayNode.alwaysShowSearchResultsAsList = false
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
return state.updatedDisplayHistoryFilterAsList(false).updatedSearch(nil)
})
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)
}
if isMedia {
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
let calendarScreen = CalendarMessageScreen(
context: self.context,
peerId: peerId,
calendarSource: self.context.engine.messages.sparseMessageCalendar(peerId: peerId, threadId: self.chatLocation.threadId, tag: .photoOrVideo, displayMedia: displayMedia),
initialTimestamp: initialTimestamp,
enableMessageRangeDeletion: enableMessageRangeDeletion,
canNavigateToEmptyDays: true,
navigateToDay: { [weak self] c, index, timestamp in
guard let strongSelf = self else {
c.dismiss()
return
}
}))
},
previewDay: { [weak self] timestamp, _, sourceNode, sourceRect, gesture in
guard let strongSelf = self else {
return
}
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
f(.dismissWithoutContent)
dismissCalendarScreen?()
strongSelf.alwaysShowSearchResultsAsList = false
strongSelf.chatDisplayNode.alwaysShowSearchResultsAsList = false
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
return state.updatedDisplayHistoryFilterAsList(false).updatedSearch(nil)
})
c.dismiss()
strongSelf.loadingMessage.set(.single(.generic))
let peerId: PeerId
let threadId: Int64?
switch strongSelf.chatLocation {
@ -99,7 +64,7 @@ extension ChatControllerImpl {
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))
@ -108,84 +73,168 @@ extension ChatControllerImpl {
}
}
}))
})))
if enableMessageRangeDeletion && (peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat) {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DialogList_ClearHistoryConfirmation, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
},
previewDay: { [weak self] timestamp, _, sourceNode, sourceRect, gesture in
guard let strongSelf = self else {
return
}
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
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)
}
)
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
dismissCalendarScreen?()
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.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = nil
})
})
return true
} else if value == .undo {
strongSelf.chatDisplayNode.historyNode.ignoreMessagesInTimestampRange = nil
return true
}))
})))
if enableMessageRangeDeletion && (peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat) {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.DialogList_ClearHistoryConfirmation, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
}, 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)
}
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)
)
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
} 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 {
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 {
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 {
return currentButton
} 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
return ChatNavigationButton(action: .cancelMessageSelection, buttonItem: buttonItem)
}

View file

@ -60,7 +60,7 @@ final class WebSearchGalleryControllerPresentationArguments {
}
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 {
return self.displayNode as! GalleryControllerNode

View file

@ -3800,7 +3800,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
separatorColor: UIColor(rgb: 0x000000, alpha: 0.25),
badgeBackgroundColor: .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: "")
)