Saved messages improvements

This commit is contained in:
Isaac 2024-01-30 12:09:47 +01:00
parent 85b845e3cb
commit ad271bf102
10 changed files with 348 additions and 135 deletions

View File

@ -1442,10 +1442,16 @@ public final class ChatListNode: ListView {
}
switch error {
case let .limitReached(count):
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .pinnedSavedPeers, count: Int32(count), action: {
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
replaceImpl?(premiumScreen)
return true
})
self.push?(controller)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
default:
break
}

View File

@ -138,7 +138,7 @@ extension UserLimitsConfiguration {
}
self.maxPinnedChatCount = getValue("dialogs_pinned_limit", orElse: defaultValue.maxPinnedChatCount)
self.maxPinnedSavedChatCount = getValue("saved_pinned_limit", orElse: defaultValue.maxPinnedSavedChatCount)
self.maxPinnedSavedChatCount = getValue("saved_dialogs_pinned_limit", orElse: defaultValue.maxPinnedSavedChatCount)
self.maxArchivedPinnedChatCount = getValue("dialogs_folder_pinned_limit", orElse: defaultValue.maxArchivedPinnedChatCount)
self.maxChannelsCount = getValue("channels_limit", orElse: defaultValue.maxChannelsCount)
self.maxPublicLinksCount = getValue("channels_public_limit", orElse: defaultValue.maxPublicLinksCount)

View File

@ -1064,13 +1064,13 @@ public extension TelegramEngine {
public func toggleForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, SetForumChannelTopicPinnedError> {
return self.account.postbox.transaction { transaction -> ([Int64], Int) in
if id == self.account.peerId {
var limit = 5
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
if let data = appConfiguration.data, let value = data["saved_pinned_limit"] as? Double {
limit = Int(value)
}
return (transaction.getPeerPinnedThreads(peerId: id), limit)
let accountPeer = transaction.getPeer(self.account.peerId)
let limitsConfiguration = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: accountPeer?.isPremium ?? false)
let limit = limitsConfiguration.maxPinnedSavedChatCount
return (transaction.getPeerPinnedThreads(peerId: id), Int(limit))
} else {
var limit = 5
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue

View File

@ -24,6 +24,9 @@ swift_library(
"//submodules/TelegramUIPreferences",
"//submodules/UIKitRuntimeUtils",
"//submodules/ChatPresentationInterfaceState",
"//submodules/ContactsPeerItem",
"//submodules/ItemListUI",
"//submodules/ChatListSearchItemHeader",
],
visibility = [
"//visibility:public",

View File

@ -13,6 +13,9 @@ import SwiftSignalKit
import TelegramUIPreferences
import UIKitRuntimeUtils
import ChatPresentationInterfaceState
import ContactsPeerItem
import ItemListUI
import ChatListSearchItemHeader
public final class ChatInlineSearchResultsListComponent: Component {
public struct Presentation: Equatable {
@ -65,7 +68,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
public enum Contents: Equatable {
case empty
case tag(MemoryBuffer)
case search
case search(query: String, includeSavedPeers: Bool)
}
public let context: AccountContext
@ -74,8 +77,10 @@ public final class ChatInlineSearchResultsListComponent: Component {
public let contents: Contents
public let insets: UIEdgeInsets
public let messageSelected: (EngineMessage) -> Void
public let peerSelected: (EnginePeer) -> Void
public let loadTagMessages: (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?
public let getSearchResult: () -> Signal<SearchMessagesResult?, NoError>?
public let getSavedPeers: (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>?
public init(
context: AccountContext,
@ -84,8 +89,10 @@ public final class ChatInlineSearchResultsListComponent: Component {
contents: Contents,
insets: UIEdgeInsets,
messageSelected: @escaping (EngineMessage) -> Void,
peerSelected: @escaping (EnginePeer) -> Void,
loadTagMessages: @escaping (MemoryBuffer, MessageIndex?) -> Signal<MessageHistoryView, NoError>?,
getSearchResult: @escaping () -> Signal<SearchMessagesResult?, NoError>?
getSearchResult: @escaping () -> Signal<SearchMessagesResult?, NoError>?,
getSavedPeers: @escaping (String) -> Signal<[(EnginePeer, MessageIndex?)], NoError>?
) {
self.context = context
self.presentation = presentation
@ -93,8 +100,10 @@ public final class ChatInlineSearchResultsListComponent: Component {
self.contents = contents
self.insets = insets
self.messageSelected = messageSelected
self.peerSelected = peerSelected
self.loadTagMessages = loadTagMessages
self.getSearchResult = getSearchResult
self.getSavedPeers = getSavedPeers
}
public static func ==(lhs: ChatInlineSearchResultsListComponent, rhs: ChatInlineSearchResultsListComponent) -> Bool {
@ -116,23 +125,83 @@ public final class ChatInlineSearchResultsListComponent: Component {
return true
}
private enum Entry: Equatable, Comparable {
enum Id: Hashable {
case peer(EnginePeer.Id)
case message(EngineMessage.Id)
}
case peer(EnginePeer)
case message(EngineMessage)
var id: Id {
switch self {
case let .peer(peer):
return .peer(peer.id)
case let .message(message):
return .message(message.id)
}
}
static func ==(lhs: Entry, rhs: Entry) -> Bool {
switch lhs {
case let .peer(peer):
if case .peer(peer) = rhs {
return true
} else {
return false
}
case let .message(message):
if case .message(message) = rhs {
return true
} else {
return false
}
}
}
static func <(lhs: Entry, rhs: Entry) -> Bool {
switch lhs {
case let .peer(lhsPeer):
switch rhs {
case let .peer(rhsPeer):
if lhsPeer.debugDisplayTitle != rhsPeer.debugDisplayTitle {
return lhsPeer.debugDisplayTitle < rhsPeer.debugDisplayTitle
}
return lhsPeer.id < rhsPeer.id
case .message:
return true
}
case let .message(lhsMessage):
switch rhs {
case .peer:
return false
case let .message(rhsMessage):
return lhsMessage.index > rhsMessage.index
}
}
}
}
private struct ContentsState: Equatable {
enum ContentId: Equatable {
case empty
case tag(MemoryBuffer)
case search
case search(String)
}
var id: Int
var contentId: ContentId
var entries: [EngineMessage]
var entries: [Entry]
var messages: [EngineMessage]
var hasEarlier: Bool
var hasLater: Bool
init(id: Int, contentId: ContentId, entries: [EngineMessage], hasEarlier: Bool, hasLater: Bool) {
init(id: Int, contentId: ContentId, entries: [Entry], messages: [EngineMessage], hasEarlier: Bool, hasLater: Bool) {
self.id = id
self.contentId = contentId
self.entries = entries
self.messages = messages
self.hasEarlier = hasEarlier
self.hasLater = hasLater
}
@ -248,11 +317,11 @@ public final class ChatInlineSearchResultsListComponent: Component {
var loadAroundIndex: MessageIndex?
if visibleRange.firstIndex <= 5 {
if contentsState.hasLater {
loadAroundIndex = contentsState.entries.first?.index
loadAroundIndex = contentsState.messages.first?.index
}
} else if visibleRange.lastIndex >= contentsState.entries.count - 5 {
} else if visibleRange.lastIndex >= contentsState.messages.count - 5 {
if contentsState.hasEarlier {
loadAroundIndex = contentsState.entries.last?.index
loadAroundIndex = contentsState.messages.last?.index
}
}
@ -273,14 +342,19 @@ public final class ChatInlineSearchResultsListComponent: Component {
return
}
let messages = view.entries.reversed().map { entry in
return EngineMessage(entry.message)
}
let contentsId = self.nextContentsId
self.nextContentsId += 1
self.contentsState = ContentsState(
id: contentsId,
contentId: .tag(tag),
entries: view.entries.reversed().map { entry in
return EngineMessage(entry.message)
entries: messages.map { message in
return .message(message)
},
messages: messages,
hasEarlier: view.earlierId != nil,
hasLater: view.laterId != nil
)
@ -316,6 +390,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
id: contentsId,
contentId: .empty,
entries: [],
messages: [],
hasEarlier: false,
hasLater: false
)
@ -346,14 +421,19 @@ public final class ChatInlineSearchResultsListComponent: Component {
return
}
let messages = view.entries.reversed().map { entry in
return EngineMessage(entry.message)
}
let contentsId = self.nextContentsId
self.nextContentsId += 1
self.contentsState = ContentsState(
id: contentsId,
contentId: .tag(tag),
entries: view.entries.reversed().map { entry in
return EngineMessage(entry.message)
entries: messages.map { message in
return .message(message)
},
messages: messages,
hasEarlier: view.earlierId != nil,
hasLater: view.laterId != nil
)
@ -368,7 +448,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
}))
}
}
case .search:
case let .search(query, includeSavedPeers):
if previousComponent?.contents != component.contents {
self.tagContents?.disposable?.dispose()
self.tagContents = nil
@ -379,21 +459,53 @@ public final class ChatInlineSearchResultsListComponent: Component {
let disposable = MetaDisposable()
self.searchContents = (nil, disposable)
let savedPeers: Signal<[(EnginePeer, MessageIndex?)], NoError>
if includeSavedPeers, !query.isEmpty, let savedPeersSignal = component.getSavedPeers(query) {
savedPeers = savedPeersSignal
} else {
savedPeers = .single([])
}
if let historySignal = component.getSearchResult() {
disposable.set((historySignal
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
disposable.set((savedPeers
|> mapToSignal { savedPeers -> Signal<([(EnginePeer, MessageIndex?)], SearchMessagesResult?), NoError> in
if savedPeers.isEmpty {
return historySignal
|> map { result in
return ([], result)
}
} else {
return (.single(nil) |> then(historySignal))
|> map { result in
return (savedPeers, result)
}
}
}
|> deliverOnMainQueue).startStrict(next: { [weak self] savedPeers, result in
guard let self else {
return
}
let messages: [EngineMessage] = result?.messages.map { entry in
return EngineMessage(entry)
} ?? []
var entries: [Entry] = []
for (peer, _) in savedPeers {
entries.append(.peer(peer))
}
for message in messages {
entries.append(.message(message))
}
entries.sort()
let contentsId = self.nextContentsId
self.nextContentsId += 1
self.contentsState = ContentsState(
id: contentsId,
contentId: .search,
entries: result?.messages.map { entry in
return EngineMessage(entry)
} ?? [],
contentId: .search(query),
entries: entries,
messages: messages,
hasEarlier: false,
hasLater: false
)
@ -418,17 +530,19 @@ public final class ChatInlineSearchResultsListComponent: Component {
leftList: previousContentsState?.entries ?? [],
rightList: contentsState.entries,
isLess: { lhs, rhs in
return lhs.index > rhs.index
return lhs < rhs
},
isEqual: { lhs, rhs in
return lhs == rhs
},
getId: { message in
return message.stableId
getId: { entry in
return entry.id
},
allUpdated: false
)
let displayMessagesHeader = contentsState.entries.count != contentsState.messages.count
let chatListPresentationData: ChatListPresentationData
if let current = self.currentChatListPresentationData, current.0 == component.presentation {
chatListPresentationData = current.1
@ -536,75 +650,107 @@ public final class ChatInlineSearchResultsListComponent: Component {
self.chatListNodeInteraction = chatListNodeInteraction
}
let messageToItem: (EngineMessage) -> ListViewItem = { message -> ListViewItem in
var effectiveAuthor: EnginePeer?
if let forwardInfo = message.forwardInfo {
effectiveAuthor = forwardInfo.author.flatMap(EnginePeer.init)
if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature {
effectiveAuthor = EnginePeer(TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
}
}
if let sourceAuthorInfo = message._asMessage().sourceAuthorInfo {
if let originalAuthor = sourceAuthorInfo.originalAuthor, let peer = message.peers[originalAuthor] {
effectiveAuthor = EnginePeer(peer)
} else if let authorSignature = sourceAuthorInfo.originalAuthorName {
effectiveAuthor = EnginePeer(TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
}
}
if effectiveAuthor == nil {
effectiveAuthor = message.author
}
let renderedPeer: EngineRenderedPeer
if let effectiveAuthor {
renderedPeer = EngineRenderedPeer(peer: effectiveAuthor)
} else {
renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: [:], associatedMedia: [:])
}
return ChatListItem(
presentationData: chatListPresentationData,
context: component.context,
chatListLocation: .savedMessagesChats,
filterData: nil,
index: .forum(
pinnedIndex: .none,
timestamp: message.timestamp,
threadId: message.threadId ?? component.context.account.peerId.toInt64(),
namespace: message.id.namespace,
id: message.id.id
),
content: .peer(ChatListItemContent.PeerData(
messages: [message],
peer: renderedPeer,
threadInfo: nil,
combinedReadState: nil,
isRemovedFromTotalUnreadCount: false,
presence: nil,
hasUnseenMentions: false,
hasUnseenReactions: false,
draftState: nil,
inputActivities: nil,
promoInfo: nil,
ignoreUnreadBadge: false,
displayAsMessage: false,
hasFailedMessages: false,
forumTopicData: nil,
topForumTopicItems: [],
autoremoveTimeout: nil,
storyState: nil,
let listPresentationData = ItemListPresentationData(component.context.sharedContext.currentPresentationData.with({ $0 }))
let peerSelected = component.peerSelected
let entryToItem: (Entry) -> ListViewItem = { entry -> ListViewItem in
switch entry {
case let .peer(peer):
return ContactsPeerItem(
presentationData: listPresentationData,
sortOrder: component.presentation.nameSortOrder,
displayOrder: component.presentation.nameDisplayOrder,
context: component.context,
peerMode: .generalSearch(isSavedMessages: true),
peer: .peer(peer: peer, chatPeer: peer),
status: .none,
badge: nil,
requiresPremiumForMessaging: false,
displayAsTopicList: false
)),
editing: false,
hasActiveRevealControls: false,
selected: false,
header: nil,
enableContextActions: false,
hiddenOffset: false,
interaction: chatListNodeInteraction
)
enabled: true,
selection: .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
index: nil,
header: displayMessagesHeader ? ChatListSearchItemHeader(type: .chats, theme: listPresentationData.theme, strings: listPresentationData.strings) : nil,
action: { [weak self] peer in
self?.listNode.clearHighlightAnimated(true)
if case let .peer(peer?, _) = peer {
peerSelected(peer)
}
},
animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer
)
case let .message(message):
var effectiveAuthor: EnginePeer?
if let forwardInfo = message.forwardInfo {
effectiveAuthor = forwardInfo.author.flatMap(EnginePeer.init)
if effectiveAuthor == nil, let authorSignature = forwardInfo.authorSignature {
effectiveAuthor = EnginePeer(TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
}
}
if let sourceAuthorInfo = message._asMessage().sourceAuthorInfo {
if let originalAuthor = sourceAuthorInfo.originalAuthor, let peer = message.peers[originalAuthor] {
effectiveAuthor = EnginePeer(peer)
} else if let authorSignature = sourceAuthorInfo.originalAuthorName {
effectiveAuthor = EnginePeer(TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil))
}
}
if effectiveAuthor == nil {
effectiveAuthor = message.author
}
let renderedPeer: EngineRenderedPeer
if let effectiveAuthor {
renderedPeer = EngineRenderedPeer(peer: effectiveAuthor)
} else {
renderedPeer = EngineRenderedPeer(peerId: message.id.peerId, peers: [:], associatedMedia: [:])
}
return ChatListItem(
presentationData: chatListPresentationData,
context: component.context,
chatListLocation: .savedMessagesChats,
filterData: nil,
index: .forum(
pinnedIndex: .none,
timestamp: message.timestamp,
threadId: message.threadId ?? component.context.account.peerId.toInt64(),
namespace: message.id.namespace,
id: message.id.id
),
content: .peer(ChatListItemContent.PeerData(
messages: [message],
peer: renderedPeer,
threadInfo: nil,
combinedReadState: nil,
isRemovedFromTotalUnreadCount: false,
presence: nil,
hasUnseenMentions: false,
hasUnseenReactions: false,
draftState: nil,
inputActivities: nil,
promoInfo: nil,
ignoreUnreadBadge: false,
displayAsMessage: false,
hasFailedMessages: false,
forumTopicData: nil,
topForumTopicItems: [],
autoremoveTimeout: nil,
storyState: nil,
requiresPremiumForMessaging: false,
displayAsTopicList: false
)),
editing: false,
hasActiveRevealControls: false,
selected: false,
header: displayMessagesHeader ? ChatListSearchItemHeader(type: .messages(location: nil), theme: listPresentationData.theme, strings: listPresentationData.strings) : nil,
enableContextActions: false,
hiddenOffset: false,
interaction: chatListNodeInteraction
)
}
}
var scrollToItem: ListViewScrollToItem?
@ -626,7 +772,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
return ListViewInsertItem(
index: index,
previousIndex: previousIndex,
item: messageToItem(item),
item: entryToItem(item),
directionHint: nil,
forceAnimateInsertion: false
)
@ -635,7 +781,7 @@ public final class ChatInlineSearchResultsListComponent: Component {
return ListViewUpdateItem(
index: index,
previousIndex: previousIndex,
item: messageToItem(item),
item: entryToItem(item),
directionHint: nil
)
},

View File

@ -1292,17 +1292,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if !strongSelf.presentationInterfaceState.isPremium {
//TODO:localize
let context = strongSelf.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, action: {
let controller = PremiumIntroScreen(context: context, source: .reactions)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.push(controller)
strongSelf.presentTagPremiumPaywall()
return
}

View File

@ -1407,7 +1407,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var inputPanelNodeHandlesTransition = false
var dismissedInputPanelNode: ChatInputPanelNode?
var dismissedSecondaryInputPanelNode: ASDisplayNode?
var dismissedSecondaryInputPanelNode: ChatInputPanelNode?
var dismissedAccessoryPanelNode: AccessoryPanelNode?
var dismissedInputContextPanelNode: ChatInputContextPanelNode?
var dismissedOverlayContextPanelNode: ChatInputContextPanelNode?
@ -1463,6 +1463,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
immediatelyLayoutSecondaryInputPanelAndAnimateAppearance = true
self.inputPanelClippingNode.insertSubnode(secondaryInputPanelNode, aboveSubnode: self.inputPanelBackgroundNode)
}
if let viewForOverlayContent = secondaryInputPanelNode.viewForOverlayContent, viewForOverlayContent.superview == nil {
self.inputPanelOverlayNode.view.addSubview(viewForOverlayContent)
}
} else {
let inputPanelHeight = secondaryInputPanelNode.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: layout.intrinsicInsets.bottom, additionalSideInsets: layout.additionalInsets, maxHeight: layout.size.height - insets.top - inputPanelBottomInset, isSecondary: true, transition: transition, interfaceState: self.chatPresentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: self.inputPanelContainerNode.expansionFraction == 1.0)
secondaryInputPanelSize = CGSize(width: layout.size.width, height: inputPanelHeight)
@ -2092,6 +2095,14 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
transition.updateFrame(node: secondaryInputPanelNode, frame: apparentSecondaryInputPanelFrame)
transition.updateAlpha(node: secondaryInputPanelNode, alpha: 1.0)
if let viewForOverlayContent = secondaryInputPanelNode.viewForOverlayContent {
if inputPanelNodeHandlesTransition {
viewForOverlayContent.frame = apparentSecondaryInputPanelFrame
} else {
transition.updateFrame(view: viewForOverlayContent, frame: apparentSecondaryInputPanelFrame)
}
}
}
if let accessoryPanelNode = self.accessoryPanelNode, let accessoryPanelFrame = accessoryPanelFrame, !accessoryPanelNode.frame.equalTo(accessoryPanelFrame) {
@ -2275,6 +2286,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
alphaCompleted = true
completed()
})
dismissedSecondaryInputPanelNode.viewForOverlayContent?.removeFromSuperview()
}
if let dismissedAccessoryPanelNode = dismissedAccessoryPanelNode {
@ -2455,9 +2468,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
let mappedContents: ChatInlineSearchResultsListComponent.Contents
if let _ = self.chatPresentationInterfaceState.search?.resultsState {
mappedContents = .search
mappedContents = .search(query: self.chatPresentationInterfaceState.search?.query ?? "", includeSavedPeers: self.alwaysShowSearchResultsAsList)
} else if let historyFilter = self.chatPresentationInterfaceState.historyFilter {
mappedContents = .tag(historyFilter.customTag)
} else if let search = self.chatPresentationInterfaceState.search, self.alwaysShowSearchResultsAsList {
mappedContents = .search(query: search.query, includeSavedPeers: self.alwaysShowSearchResultsAsList)
} else {
mappedContents = .empty
}
@ -2494,6 +2509,34 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
return state.updatedDisplayHistoryFilterAsList(false)
})
},
peerSelected: { [weak self] peer in
guard let self else {
return
}
guard let navigationController = self.controller?.navigationController as? NavigationController else {
return
}
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
navigationController: navigationController,
context: self.context,
chatLocation: .replyThread(ChatReplyThreadMessage(
peerId: self.context.account.peerId,
threadId: peer.id.toInt64(),
channelMessageId: nil,
isChannelPost: false,
isForumPost: false,
maxMessage: nil,
maxReadIncomingMessageId: nil,
maxReadOutgoingMessageId: nil,
unreadCount: 0,
initialFilledHoles: IndexSet(),
initialAnchor: .automatic,
isNotAvailable: false
)),
subject: nil,
keepStack: .always
))
},
loadTagMessages: { tag, index in
let input: ChatHistoryLocationInput
if let index {
@ -2542,6 +2585,27 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|> map { result in
return result?.0
}
},
getSavedPeers: { [weak self] query in
guard let self else {
return nil
}
let strings = self.chatPresentationInterfaceState.strings
let foundLocalPeers = context.engine.messages.searchLocalSavedMessagesPeers(query: query.lowercased(), indexNameMapping: [
context.account.peerId: [
PeerIndexNameRepresentation.title(title: strings.DialogList_MyNotes.lowercased(), addressNames: []),
PeerIndexNameRepresentation.title(title: "my notes".lowercased(), addressNames: [])
],
PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2666000)): [
PeerIndexNameRepresentation.title(title: strings.ChatList_AuthorHidden.lowercased(), addressNames: [])
]
])
|> map { peers -> [(EnginePeer, MessageIndex?)] in
return peers.map { peer in
return (peer, nil)
}
}
return foundLocalPeers
}
)),
environment: {},
@ -3375,6 +3439,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
}
if let secondaryInputPanelNode = self.secondaryInputPanelNode, let viewForOverlayContent = secondaryInputPanelNode.viewForOverlayContent {
if let result = viewForOverlayContent.hitTest(self.view.convert(point, to: viewForOverlayContent), with: event) {
return result
}
if maybeDismissOverlayContent {
viewForOverlayContent.maybeDismissContent(point: self.view.convert(point, to: viewForOverlayContent))
}
}
return nil
}

View File

@ -37,8 +37,6 @@ func preloadedChatHistoryViewForLocation(_ location: ChatHistoryLocationInput, c
}
func chatHistoryViewForLocation(_ location: ChatHistoryLocationInput, ignoreMessagesInTimestampRange: ClosedRange<Int32>?, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, scheduled: Bool, fixedCombinedReadStates: MessageHistoryViewReadState?, tag: HistoryViewInputTag?, appendMessagesFromTheSameGroup: Bool, additionalData: [AdditionalMessageHistoryViewData], orderStatistics: MessageHistoryViewOrderStatistics = []) -> Signal<ChatHistoryViewUpdate, NoError> {
print("request \(location.content)")
let account = context.account
if scheduled {
var first = true

View File

@ -767,6 +767,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
let isScheduled = chatPresentationInterfaceState.subject == .scheduledMessages
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest(
loadLimits,
loadStickerSaveStatusSignal,
@ -804,6 +806,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
loggingSettings = LoggingSettings.defaultSettings
}
var messageActions = messageActions
if isEmbeddedMode {
messageActions = ChatAvailableMessageActions(
options: messageActions.options.intersection([.deleteLocally, .deleteGlobally, .forward]),
banAuthor: nil,
disableDelete: true,
isCopyProtected: messageActions.isCopyProtected,
setTag: false,
editTags: Set()
)
} else if isScheduled {
messageActions.setTag = false
messageActions.editTags = Set()
}
return (MessageContextMenuData(
starStatus: stickerSaveStatus,
canReply: canReply && !isEmbeddedMode,
@ -811,14 +828,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
canEdit: canEdit && !isEmbeddedMode,
canSelect: canSelect && !isEmbeddedMode,
resourceStatus: resourceStatus,
messageActions: isEmbeddedMode ? ChatAvailableMessageActions(
options: messageActions.options.intersection([.deleteLocally, .deleteGlobally, .forward]),
banAuthor: nil,
disableDelete: true,
isCopyProtected: messageActions.isCopyProtected,
setTag: false,
editTags: Set()
) : messageActions
messageActions: messageActions
), updatingMessageMedia, infoSummaryData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings, notificationSoundList, accountPeer)
}

View File

@ -590,20 +590,7 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, Chat
guard let self, let interfaceInteraction = self.interfaceInteraction else {
return
}
//TODO:localize
var replaceImpl: ((ViewController) -> Void)?
let controller = self.context.sharedContext.makePremiumDemoController(context: self.context, subject: .uniqueReactions, action: { [weak self] in
guard let self else {
return
}
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .settings, forceDark: false, dismissed: nil)
replaceImpl?(controller)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
interfaceInteraction.chatController()?.push(controller)
(interfaceInteraction.chatController() as? ChatControllerImpl)?.presentTagPremiumPaywall()
})
self.promoView = promoView
self.scrollView.addSubview(promoView)