Search filters improvements

This commit is contained in:
Ilya Laktyushin 2020-09-07 15:22:35 +03:00
parent 9c52f3a9a6
commit 3fd6ee004b
42 changed files with 6399 additions and 6038 deletions

View File

@ -5754,3 +5754,31 @@ Any member of this group will be able to see messages in the channel.";
"ChatList.Search.NoResults" = "No Results";
"ChatList.Search.NoResultsDescription" = "There were no results for \"%@\".\nTry a new search.";
"ChatList.Search.Messages_0" = "%@ messages";
"ChatList.Search.Messages_1" = "%@ message";
"ChatList.Search.Messages_2" = "%@ messages";
"ChatList.Search.Messages_3_10" = "%@ messages";
"ChatList.Search.Messages_many" = "%@ messages";
"ChatList.Search.Messages_any" = "%@ messages";
"ChatList.Search.Links_0" = "%@ links";
"ChatList.Search.Links_1" = "%@ link";
"ChatList.Search.Links_2" = "%@ links";
"ChatList.Search.Links_3_10" = "%@ links";
"ChatList.Search.Links_many" = "%@ links";
"ChatList.Search.Links_any" = "%@ links";
"ChatList.Search.Files_0" = "%@ files";
"ChatList.Search.Files_1" = "%@ file";
"ChatList.Search.Files_2" = "%@ files";
"ChatList.Search.Files_3_10" = "%@ files";
"ChatList.Search.Files_many" = "%@ files";
"ChatList.Search.Files_any" = "%@ files";
"ChatList.Search.Music_0" = "%@ audio files";
"ChatList.Search.Music_1" = "%@ audio file";
"ChatList.Search.Music_2" = "%@ audio files";
"ChatList.Search.Music_3_10" = "%@ audio files";
"ChatList.Search.Music_many" = "%@ audio files";
"ChatList.Search.Music_any" = "%@ audio files";

View File

@ -6,7 +6,7 @@ import TelegramCore
public enum GalleryControllerItemSource {
case peerMessagesAtId(messageId: MessageId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>)
case standaloneMessage(Message)
case searchResult(SearchMessagesResult, SearchMessagesState, MessageId)
case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, loadMore: (() -> Void)?)
}
public final class GalleryControllerActionInteraction {

View File

@ -4,7 +4,7 @@ import Display
import TelegramPresentationData
import ListSectionHeaderNode
public enum ChatListSearchItemHeaderType: Int32 {
public enum ChatListSearchItemHeaderType {
case localPeers
case members
case contacts
@ -13,7 +13,6 @@ public enum ChatListSearchItemHeaderType: Int32 {
case globalPeers
case deviceContacts
case recentPeers
case messages
case phoneNumber
case exceptions
case addToExceptions
@ -22,6 +21,123 @@ public enum ChatListSearchItemHeaderType: Int32 {
case chats
case chatTypes
case faq
case messages(Int32)
case links(Int32)
case files(Int32)
case music(Int32)
fileprivate func title(strings: PresentationStrings) -> String {
switch self {
case .localPeers:
return strings.DialogList_SearchSectionDialogs
case .members:
return strings.Channel_Info_Members
case .contacts:
return strings.Contacts_TopSection
case .bots:
return strings.MemberSearch_BotSection
case .admins:
return strings.Channel_Management_Title
case .globalPeers:
return strings.DialogList_SearchSectionGlobal
case .deviceContacts:
return strings.Contacts_NotRegisteredSection
case .recentPeers:
return strings.DialogList_SearchSectionRecent
case .phoneNumber:
return strings.Contacts_PhoneNumber
case .exceptions:
return strings.GroupInfo_Permissions_Exceptions
case .addToExceptions:
return strings.Exceptions_AddToExceptions
case .mapAddress:
return strings.Map_AddressOnMap
case .nearbyVenues:
return strings.Map_PlacesNearby
case .chats:
return strings.Cache_ByPeerHeader
case .chatTypes:
return strings.ChatList_ChatTypesSection
case .faq:
return strings.Settings_FrequentlyAskedQuestions
case let .messages(count):
return strings.ChatList_Search_Messages(count)
case let .links(count):
return strings.ChatList_Search_Links(count)
case let .files(count):
return strings.ChatList_Search_Files(count)
case let .music(count):
return strings.ChatList_Search_Music(count)
}
}
fileprivate var id: ChatListSearchItemHeaderId {
switch self {
case .localPeers:
return .localPeers
case .members:
return .members
case .contacts:
return .contacts
case .bots:
return .bots
case .admins:
return .admins
case .globalPeers:
return .globalPeers
case .deviceContacts:
return .deviceContacts
case .recentPeers:
return .recentPeers
case .phoneNumber:
return .phoneNumber
case .exceptions:
return .exceptions
case .addToExceptions:
return .addToExceptions
case .mapAddress:
return .mapAddress
case .nearbyVenues:
return .nearbyVenues
case .chats:
return .chats
case .chatTypes:
return .chatTypes
case .faq:
return .faq
case .messages:
return .messages
case .links:
return .links
case .files:
return .files
case .music:
return .music
}
}
}
private enum ChatListSearchItemHeaderId: Int32 {
case localPeers
case members
case contacts
case bots
case admins
case globalPeers
case deviceContacts
case recentPeers
case phoneNumber
case exceptions
case addToExceptions
case mapAddress
case nearbyVenues
case chats
case chatTypes
case faq
case messages
case links
case files
case music
}
public final class ChatListSearchItemHeader: ListViewItemHeader {
@ -37,7 +153,7 @@ public final class ChatListSearchItemHeader: ListViewItemHeader {
public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String? = nil, action: (() -> Void)? = nil) {
self.type = type
self.id = Int64(self.type.rawValue)
self.id = Int64(self.type.id.rawValue)
self.theme = theme
self.strings = strings
self.actionTitle = actionTitle
@ -75,43 +191,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
super.init()
switch type {
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
case .chats:
self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased()
case .chatTypes:
self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased()
case .faq:
self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased()
}
self.sectionHeaderNode.title = type.title(strings: strings).uppercased()
self.sectionHeaderNode.action = actionTitle
self.sectionHeaderNode.activateAction = action
@ -127,43 +207,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
self.actionTitle = actionTitle
self.action = action
switch type {
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
case .chats:
self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased()
case .chatTypes:
self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased()
case .faq:
self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased()
}
self.sectionHeaderNode.title = type.title(strings: strings).uppercased()
self.sectionHeaderNode.action = actionTitle
self.sectionHeaderNode.activateAction = action

View File

@ -49,6 +49,7 @@ static_library(
"//submodules/ListMessageItem:ListMessageItem",
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -49,6 +49,7 @@ swift_library(
"//submodules/ListMessageItem:ListMessageItem",
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/GalleryData:GalleryData",
],
visibility = [
"//visibility:public",

View File

@ -1721,6 +1721,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
strongSelf.setDisplayNavigationBar(false, transition: transition)
(strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring))
})
}
}
@ -1743,8 +1745,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
parentController.navigationBar?.setSecondaryContentNode(filtersIsEmpty ? nil : self.tabContainerNode, animated: animated)
}
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.5, curve: .spring) : .immediate
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
self.setDisplayNavigationBar(true, transition: transition)
(self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring))
}
}

View File

@ -1167,6 +1167,8 @@ final class ChatListControllerNode: ASDisplayNode {
}
}, peerContextAction: self.peerContextAction, present: { [weak self] c, a in
self?.controller?.present(c, in: .window(.root), with: a)
}, presentInGlobalOverlay: { [weak self] c, a in
self?.controller?.presentInGlobalOverlay(c, with: a)
}, navigationController: navigationController, updatedSearchOptions: { options in
updatedSearchOptions?(options)
})

View File

@ -25,6 +25,7 @@ import UniversalMediaPlayer
import PresentationDataUtils
import AnimatedStickerNode
import AppBundle
import GalleryData
private final class PassthroughContainerNode: ASDisplayNode {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -271,7 +272,7 @@ public enum ChatListSearchSectionExpandType {
public enum ChatListSearchEntry: Comparable, Identifiable {
case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData)
case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData, Int32)
case addContact(String, PresentationTheme, PresentationStrings)
public var stableId: ChatListSearchEntryStableId {
@ -280,7 +281,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id)
case let .message(message, _, _, _):
case let .message(message, _, _, _, _):
return .messageId(message.id)
case .addContact:
return .addContact
@ -301,8 +302,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} else {
return false
}
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData) = rhs {
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount) = rhs {
if lhsMessage.id != rhsMessage.id {
return false
}
@ -318,6 +319,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
if lhsCombinedPeerReadState != rhsCombinedPeerReadState {
return false
}
if lhsTotalCount != rhsTotalCount {
return false
}
return true
} else {
return false
@ -357,8 +361,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case .message, .addContact:
return true
}
case let .message(lhsMessage, _, _, _):
if case let .message(rhsMessage, _, _, _) = rhs {
case let .message(lhsMessage, _, _, _, _):
if case let .message(rhsMessage, _, _, _, _) = rhs {
return lhsMessage.index < rhsMessage.index
} else if case .addContact = rhs {
return true
@ -370,7 +374,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
}
}
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, presentDatePicker: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem {
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, presentDatePicker: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem {
switch self {
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: Peer
@ -509,8 +513,27 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
peerContextAction(peer.peer, .search, node, gesture)
}
})
case let .message(message, peer, readState, presentationData):
let header: ChatListSearchItemHeader? = enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil
case let .message(message, peer, readState, presentationData, totalCount):
let header: ChatListSearchItemHeader?
if enableHeaders {
let type: ChatListSearchItemHeaderType
if let searchOptions = searchOptions {
if searchOptions.messageTags == .webPage {
type = .links(totalCount)
} else if searchOptions.messageTags == .file {
type = .files(totalCount)
} else if searchOptions.messageTags == .music {
type = .music(totalCount)
} else {
type = .messages(totalCount)
}
} else {
type = .messages(totalCount)
}
header = ChatListSearchItemHeader(type: type, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
} else {
header = nil
}
if let tags = searchOptions?.messageTags, tags != .photoOrVideo {
let interaction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in
@ -712,6 +735,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = []
private var validLayout: (ContainerViewLayout, CGFloat)?
private var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
private let recentDisposable = MetaDisposable()
private let updatedRecentPeersDisposable = MetaDisposable()
@ -751,13 +776,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let emptyResultsAnimationNode: AnimatedStickerNode
private var animationSize: CGSize = CGSize()
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?) -> Void)? = nil) {
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?) -> Void)? = nil) {
self.context = context
self.filter = filter
self.dimNode = ASDisplayNode()
self.navigationController = navigationController
self.updatedSearchOptions = updatedSearchOptions
self.presentInGlobalOverlay = presentInGlobalOverlay
self.openMessage = originalOpenMessage
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -769,11 +796,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
var openMediaMessageImpl: ((Message, ChatControllerInteractionOpenMessageMode) -> Void)?
var messageContextActionImpl: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?
var transitionNodeImpl: ((MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?)?
var addToTransitionSurfaceImpl: ((UIView) -> Void)?
self.mediaNode = ChatListSearchMediaNode(context: self.context, contentType: .photoOrVideo, openMessage: { message, mode in
openMediaMessageImpl?(message, mode)
}, messageContextAction: { message, sourceNode, sourceRect, gesture in
messageContextActionImpl?(message, sourceNode, sourceRect, gesture)
})
self.listNode = ListView()
@ -805,51 +835,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.animationSize = CGSize(width: 124.0, height: 124.0)
}
openMediaMessageImpl = { [weak self] message, mode in
if let searchContext = self?.searchContextValue.with({ $0 }) {
let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, navigationController: nil, dismissInput: {
self?.view.window?.endEditing(true)
}, present: { c, a in
present(c, a)
}, transitionNode: { messageId, media in
return transitionNodeImpl?(messageId, media)
}, addToTransitionSurface: { view in
addToTransitionSurfaceImpl?(view)
}, openUrl: { [weak self] url in
openUserGeneratedUrl(context: context, url: url, concealed: false, present: { [weak self] c in
present(c, nil)
}, openResolved: { [weak self] resolved in
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in
// self?.openPeer(peerId: peerId, navigation: navigation)
}, sendFile: nil,
sendSticker: nil,
present: { c, a in
present(c, a)
}, dismissInput: {
self?.view.window?.endEditing(true)
}, contentContext: nil)
})
}, openPeer: { peer, navigation in
//self?.openPeer(peerId: peer.id, navigation: navigation)
}, callPeer: { _, _ in
}, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .searchResult(SearchMessagesResult(messages: searchContext.result.messages, readStates: searchContext.result.readStates, totalCount: searchContext.result.totalCount, completed: !searchContext.result.hasMore), searchContext.result.state, message.id)))
}
}
transitionNodeImpl = { [weak self] messageId, media in
if let strongSelf = self {
return strongSelf.mediaNode.transitionNodeForGallery(messageId: messageId, media: media)
} else {
return nil
}
}
addToTransitionSurfaceImpl = { [weak self] view in
if let strongSelf = self {
strongSelf.mediaNode.addToTransitionSurface(view: view)
}
}
self.dimNode.backgroundColor = filter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : self.presentationData.theme.chatList.backgroundColor
self.backgroundColor = filter.contains(.excludeRecent) ? nil : self.presentationData.theme.chatList.backgroundColor
@ -886,7 +871,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.listNode.isHidden = true
self.mediaNode.isHidden = true
self.listNode.visibleBottomContentOffsetChanged = { offset in
guard case let .known(value) = offset, value < 100.0 else {
guard case let .known(value) = offset, value < 160.0 else {
return
}
updateSearchContext { previous in
@ -903,9 +888,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
}
self.recentListNode.isHidden = filter.contains(.excludeRecent)
let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer])?>(value: nil)
let presentationDataPromise = self.presentationDataPromise
let searchStatePromise = self.searchStatePromise
let foundItems = combineLatest(self.searchQuery.get(), self.searchOptions.get())
@ -1012,7 +996,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return .complete()
}
} else {
return .single(((searchContext.result.messages, searchContext.result.readStates, 0), false))
return .single(((searchContext.result.messages, searchContext.result.readStates, searchContext.result.totalCount), false))
}
} else {
return .complete()
@ -1026,7 +1010,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
updateSearchContext { _ in
return (ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil), true)
}
return ((foundMessages.messages, foundMessages.readStates, 0), false)
return ((foundMessages.messages, foundMessages.readStates, foundMessages.totalCount), false)
}
|> delay(0.2, queue: Queue.concurrentDefaultQueue())
|> then(loadMore)
@ -1036,7 +1020,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let resolvedMessage = .single(nil)
|> then(context.sharedContext.resolveUrl(account: context.account, url: finalQuery)
|> mapToSignal { resolvedUrl -> Signal<Message?, NoError> in
if case let .channelMessage(peerId, messageId) = resolvedUrl {
if case let .channelMessage(_, messageId) = resolvedUrl {
return downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: messageId)
} else {
return .single(nil)
@ -1200,7 +1184,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
peer = RenderedPeer(peer: channelPeer)
}
}
entries.append(.message(message, peer, nil, presentationData))
entries.append(.message(message, peer, nil, presentationData, 1))
index += 1
}
@ -1213,12 +1197,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
peer = RenderedPeer(peer: channelPeer)
}
}
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData))
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData, foundRemoteMessages.0.2))
index += 1
}
}
if addContact != nil && isViablePhoneNumber(finalQuery) {
if let _ = addContact, isViablePhoneNumber(finalQuery) {
entries.append(.addContact(finalQuery, presentationData.theme, presentationData.strings))
}
@ -1226,12 +1210,79 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
}
let foundMessages = searchContext.get() |> map { searchContext -> ([Message], Int32, Bool) in
if let result = searchContext?.result {
return (result.messages, result.totalCount, result.hasMore)
} else {
return ([], 0, false)
}
}
openMediaMessageImpl = { [weak self] message, mode in
let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, navigationController: nil, dismissInput: {
self?.view.window?.endEditing(true)
}, present: { c, a in
present(c, a)
}, transitionNode: { messageId, media in
return transitionNodeImpl?(messageId, media)
}, addToTransitionSurface: { view in
addToTransitionSurfaceImpl?(view)
}, openUrl: { [weak self] url in
openUserGeneratedUrl(context: context, url: url, concealed: false, present: { [weak self] c in
present(c, nil)
}, openResolved: { [weak self] resolved in
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in
// self?.openPeer(peerId: peerId, navigation: navigation)
}, sendFile: nil,
sendSticker: nil,
present: { c, a in
present(c, a)
}, dismissInput: {
self?.view.window?.endEditing(true)
}, contentContext: nil)
})
}, openPeer: { peer, navigation in
//self?.openPeer(peerId: peer.id, navigation: navigation)
}, callPeer: { _, _ in
}, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages, messageId: message.id, loadMore: {
updateSearchContext { previous in
guard let previous = previous else {
return (nil, false)
}
if previous.loadMoreIndex != nil {
return (previous, false)
}
guard let last = previous.result.messages.last else {
return (previous, false)
}
return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true)
}
})))
}
messageContextActionImpl = { [weak self] message, sourceNode, sourceRect, gesture in
if let strongSelf = self {
strongSelf.messageContextActions(message, node: sourceNode, rect: sourceRect, gesture: gesture)
}
}
transitionNodeImpl = { [weak self] messageId, media in
if let strongSelf = self {
return strongSelf.mediaNode.transitionNodeForGallery(messageId: messageId, media: media)
} else {
return nil
}
}
addToTransitionSurfaceImpl = { [weak self] view in
if let strongSelf = self {
strongSelf.mediaNode.addToTransitionSurface(view: view)
}
}
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
let openPeer: (Peer, Bool) -> Void = { [weak self] peer, value in
guard let strongSelf = self else {
return
}
let openPeer: (Peer, Bool) -> Void = { peer, value in
originalOpenPeer(peer, value)
if peer.id.namespace != Namespaces.Peer.SecretChat {
@ -1457,7 +1508,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedPeerId(peer.id, peerName: peer.compactDisplayTitle), clearQuery: true)
strongSelf.dismissInput?()
}, searchResults: newEntries.compactMap { entry -> Message? in
if case let .message(message, _, _, _) = entry {
if case let .message(message, _, _, _, _) = entry {
return message
} else {
return nil
@ -1493,6 +1544,25 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self?.dismissInput?()
}
self.mediaNode.beganInteractiveDragging = { [weak self] in
self?.dismissInput?()
}
self.mediaNode.loadMore = {
updateSearchContext { previous in
guard let previous = previous else {
return (nil, false)
}
if previous.loadMoreIndex != nil {
return (previous, false)
}
guard let last = previous.result.messages.last else {
return (previous, false)
}
return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true)
}
}
self.filterContainerNode.filterPressed = { [weak self] filter in
guard let strongSelf = self else {
return
@ -1979,7 +2049,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
self.mediaNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))
self.mediaNode.update(size: layout.size, sideInset: 0, bottomInset: 0, visibleHeight: layout.size.height - navigationBarHeight, isScrollingLockedAtTop: false, expandProgress: 1.0, presentationData: self.presentationData, synchronous: true, transition: transition)
self.mediaNode.update(size: layout.size, sideInset: layout.safeInsets.left, bottomInset: layout.insets(options: [.input]).bottom, visibleHeight: layout.size.height - navigationBarHeight, isScrollingLockedAtTop: false, expandProgress: 1.0, presentationData: self.presentationData, synchronous: true, transition: transition)
let padding: CGFloat = 16.0
let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
@ -2081,6 +2151,61 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
}
}
func messageContextActions(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) {
let gesture: ContextGesture? = anyRecognizer as? ContextGesture
let _ = (chatMediaListPreviewControllerData(context: self.context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.navigationController)
|> deliverOnMainQueue).start(next: { [weak self] previewData in
guard let strongSelf = self else {
gesture?.cancel()
return
}
if let previewData = previewData {
let context = strongSelf.context
let strings = strongSelf.presentationData.strings
// let items = chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id])
// |> map { actions -> [ContextMenuItem] in
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: {
self?.openMessage(message.peers[message.id.peerId]!, message.id)
})
})))
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in
c.dismiss(completion: {
if let strongSelf = self {
strongSelf.forwardMessages(messageIds: [message.id])
}
})
})))
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuMore, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
guard let strongSelf = self else {
return
}
// strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true)
// strongSelf.expandTabs()
f(.default)
})))
// }
switch previewData {
case let .gallery(gallery):
gallery.setHintWillBePresentedInPreviewingContext(true)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay?(contextController, nil)
case .instantPage:
break
}
}
})
}
func messageContextAction(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) {
guard let node = node as? ContextExtractedContentContainingNode else {
return
@ -2113,7 +2238,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
c.dismiss(completion: { [weak self] in
if let strongSelf = self {
// strongSelf.forwardMessages(messageIds: Set([message.id]))
strongSelf.forwardMessages(messageIds: Set([message.id]))
}
})
})))
@ -2123,16 +2248,93 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
})
})))
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
self.interaction?.present(controller)
// self.controller?.window?.presentInGlobalOverlay(controller)
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
c.dismiss(completion: {
// if let strongSelf = self {
// strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true)
// strongSelf.expandTabs()
// }
})
})))
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
self.presentInGlobalOverlay?(controller, nil)
}
public override func searchTextClearTokens() {
self.updateSearchOptions(nil)
self.setQuery?(nil, [], self.searchQueryValue ?? "")
}
func forwardMessages(messageIds: Set<MessageId>?) {
// if let messageIds = messageIds, !messageIds.isEmpty {
// let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled]))
// peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peerId in
// if let strongSelf = self, let _ = peerSelectionController {
// if peerId == strongSelf.context.account.peerId {
//// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
//
// let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
// return .forward(source: id, grouping: .auto, attributes: [])
// })
// |> deliverOnMainQueue).start(next: { [weak self] messageIds in
// if let strongSelf = self {
// let signals: [Signal<Bool, NoError>] = messageIds.compactMap({ id -> Signal<Bool, NoError>? in
// guard let id = id else {
// return nil
// }
// return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id)
// |> mapToSignal { status, _ -> Signal<Bool, NoError> in
// if status != nil {
// return .never()
// } else {
// return .single(true)
// }
// }
// |> take(1)
// })
// strongSelf.activeActionDisposable.set((combineLatest(signals)
// |> deliverOnMainQueue).start(completed: {
// guard let strongSelf = self else {
// return
// }
// strongSelf.controller?.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), in: .window(.root))
// }))
// }
// })
// if let peerSelectionController = peerSelectionController {
// peerSelectionController.dismiss()
// }
// } else {
// let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in
// transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
// if let currentState = currentState as? ChatInterfaceState {
// return currentState.withUpdatedForwardMessageIds(Array(messageIds))
// } else {
// return ChatInterfaceState().withUpdatedForwardMessageIds(Array(messageIds))
// }
// })
// }) |> deliverOnMainQueue).start(completed: {
// if let strongSelf = self {
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
//
// let ready = Promise<Bool>()
// strongSelf.activeActionDisposable.set((ready.get() |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { _ in
// if let peerSelectionController = peerSelectionController {
// peerSelectionController.dismiss()
// }
// }))
//
// (strongSelf.controller?.navigationController as? NavigationController)?.replaceTopController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId)), animated: false, ready: ready)
// }
// })
// }
// }
// }
// self.controller?.push(peerSelectionController)
// }
}
}
private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
@ -2153,3 +2355,32 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
}
}
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController
weak var sourceNode: ASDisplayNode?
let navigationController: NavigationController? = nil
let passthroughTouches: Bool = false
init(controller: ViewController, sourceNode: ASDisplayNode?) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerTakeControllerInfo? {
let sourceNode = self.sourceNode
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
if let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds)
} else {
return nil
}
})
}
func animatedIn() {
self.controller.didAppearInContextPreview()
}
}

View File

@ -573,12 +573,6 @@ private enum ItemsLayout {
}
}
private func getStableId(message: Message) -> Int32 {
var hash = message.id.id
hash = hash &* 31 &+ message.id.peerId.id
return hash
}
final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
enum ContentType {
case photoOrVideo
@ -612,18 +606,17 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
private var hiddenMediaDisposable: Disposable?
private var mediaItems: [VisualMediaItem] = []
private var itemsLayout: ItemsLayout?
private var visibleMediaItems: [Int32: VisualMediaItemNode] = [:]
private var numberOfItemsToRequest: Int = 50
private var currentView: MessageHistoryView?
private var isRequestingView: Bool = false
private var isFirstHistoryView: Bool = true
private var visibleMediaItems: [UInt32: VisualMediaItemNode] = [:]
private var initialized = false
private var decelerationAnimator: ConstantDisplayLinkAnimator?
private var animationTimer: SwiftSignalKit.Timer?
init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void) {
public var beganInteractiveDragging: (() -> Void)?
public var loadMore: (() -> Void)?
init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void, messageContextAction: @escaping (Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void) {
self.context = context
self.contentType = contentType
@ -638,7 +631,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
let _ = openMessage(message, .default)
},
openMessageContextActions: { [weak self] message, sourceNode, sourceRect, gesture in
// self?.chatControllerInteraction.openMessageContextActions(message, sourceNode, sourceRect, gesture)
messageContextAction(message, sourceNode, sourceRect, gesture)
},
toggleSelection: { [weak self] id, value in
// self?.chatControllerInteraction.toggleMessagesSelection([id], value)
@ -658,8 +651,6 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
self.addSubnode(self.scrollNode)
self.addSubnode(self.floatingHeaderNode)
self.requestHistoryAroundVisiblePosition()
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in
guard let strongSelf = self else {
return
@ -683,41 +674,25 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
self.animationTimer?.invalidate()
}
private func requestHistoryAroundVisiblePosition() {
// if self.isRequestingView {
// return
// }
// self.isRequestingView = true
// self.listDisposable.set((self.context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(self.peerId), index: .upperBound, anchorIndex: .upperBound, count: self.numberOfItemsToRequest, fixedCombinedReadStates: nil, tagMask: tagMaskForType(self.contentType))
// |> deliverOnMainQueue).start(next: { [weak self] (view, updateType, _) in
// guard let strongSelf = self else {
// return
// }
// strongSelf.updateHistory(view: view, updateType: updateType)
// strongSelf.isRequestingView = false
// }))
}
func updateHistory(entries: [ChatListSearchEntry], updateType: ViewUpdateType) {
// self.currentView = view
switch updateType {
case .FillHole:
self.requestHistoryAroundVisiblePosition()
break
default:
self.mediaItems.removeAll()
for entry in entries.reversed() {
if case let .message(message, _, _, _) = entry {
for entry in entries {
if case let .message(message, _, _, _, _) = entry {
self.mediaItems.append(VisualMediaItem(message: message))
}
}
self.itemsLayout = nil
let wasFirstHistoryView = self.isFirstHistoryView
self.isFirstHistoryView = false
let wasInitialized = self.initialized
self.initialized = true
if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate)
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .immediate)
if !self.didSetReady {
self.didSetReady = true
self.ready.set(.single(true))
@ -749,51 +724,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
itemNode.updateHiddenMedia()
}
}
func transferVelocity(_ velocity: CGFloat) {
if velocity > 0.0 {
//print("transferVelocity \(velocity)")
self.decelerationAnimator?.isPaused = true
let startTime = CACurrentMediaTime()
var currentOffset = self.scrollNode.view.contentOffset
let decelerationRate: CGFloat = 0.998
self.scrollViewDidEndDragging(self.scrollNode.view, willDecelerate: true)
self.decelerationAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in
guard let strongSelf = self else {
return
}
let t = CACurrentMediaTime() - startTime
var currentVelocity = velocity * 15.0 * CGFloat(pow(Double(decelerationRate), 1000.0 * t))
//print("value at \(t) = \(currentVelocity)")
currentOffset.y += currentVelocity
let maxOffset = strongSelf.scrollNode.view.contentSize.height - strongSelf.scrollNode.bounds.height
if currentOffset.y >= maxOffset {
currentOffset.y = maxOffset
currentVelocity = 0.0
}
if currentOffset.y < 0.0 {
currentOffset.y = 0.0
currentVelocity = 0.0
}
var didEnd = false
if abs(currentVelocity) < 0.1 {
strongSelf.decelerationAnimator?.isPaused = true
strongSelf.decelerationAnimator = nil
didEnd = true
}
var contentOffset = strongSelf.scrollNode.view.contentOffset
contentOffset.y = floorToScreenPixels(currentOffset.y)
strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false)
strongSelf.scrollViewDidScroll(strongSelf.scrollNode.view)
if didEnd {
strongSelf.scrollViewDidEndDecelerating(strongSelf.scrollNode.view)
}
})
self.decelerationAnimator?.isPaused = false
}
}
func cancelPreviewGestures() {
for (_, itemNode) in self.visibleMediaItems {
itemNode.cancelPreviewGesture()
@ -803,7 +734,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
for item in self.mediaItems {
if item.message.id == messageId {
if let itemNode = self.visibleMediaItems[getStableId(message: item.message)] {
if let itemNode = self.visibleMediaItems[item.message.stableId] {
return itemNode.transitionNode()
}
break
@ -863,17 +794,16 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
}
self.updateHeaderFlashing(animated: true)
self.beganInteractiveDragging?()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let (size, sideInset, bottomInset, visibleHeight, _, _, presentationData) = self.currentParams {
self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: false)
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0, let currentView = self.currentView, currentView.earlierId != nil {
if !self.isRequestingView {
self.numberOfItemsToRequest += 50
self.requestHistoryAroundVisiblePosition()
}
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0 {
self.loadMore?()
}
}
}
@ -908,13 +838,10 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
var headerItem: Message?
var validIds = Set<Int32>()
var validIds = Set<UInt32>()
if minVisibleIndex <= maxVisibleIndex {
for i in minVisibleIndex ... maxVisibleIndex {
var stableId = Int32(self.mediaItems[i].message.stableId)
if stableId == 0 {
stableId = getStableId(message: self.mediaItems[i].message)
}
let stableId = self.mediaItems[i].message.stableId
validIds.insert(stableId)
let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset)
@ -939,7 +866,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
itemNode.updateIsVisible(itemFrame.intersects(activeRect))
}
}
var removeKeys: [Int32] = []
var removeKeys: [UInt32] = []
for (id, _) in self.visibleMediaItems {
if !validIds.contains(id) {
removeKeys.append(id)

View File

@ -673,6 +673,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
additionalTitleInset += badgeSize
if let arrowButtonImage = arrowButtonImage {
additionalTitleInset += arrowButtonImage.size.width
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - additionalTitleInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - badgeSize), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))

View File

@ -0,0 +1,31 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "GalleryData",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/InstantPageUI:InstantPageUI",
"//submodules/GalleryUI:GalleryUI",
"//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI",
"//submodules/MediaResources:MediaResources",
"//submodules/WebsiteType:WebsiteType",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
"$SDKROOT/System/Library/Frameworks/QuickLook.framework",
"$SDKROOT/System/Library/Frameworks/SafariServices.framework",
],
)

View File

@ -0,0 +1,29 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "GalleryData",
module_name = "GalleryData",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/SyncCore:SyncCore",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/InstantPageUI:InstantPageUI",
"//submodules/GalleryUI:GalleryUI",
"//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI",
"//submodules/MediaResources:MediaResources",
"//submodules/WebsiteType:WebsiteType",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,296 @@
import Foundation
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SyncCore
import SwiftSignalKit
import PassKit
import Lottie
import TelegramUIPreferences
import TelegramPresentationData
import AccountContext
import InstantPageUI
import PeerAvatarGalleryUI
import GalleryUI
import MediaResources
import WebsiteType
public enum ChatMessageGalleryControllerData {
case url(String)
case pass(TelegramMediaFile)
case instantPage(InstantPageGalleryController, Int, Media)
case map(TelegramMediaMap)
case stickerPack(StickerPackReference)
case audio(TelegramMediaFile)
case document(TelegramMediaFile, Bool)
case gallery(Signal<GalleryController, NoError>)
case secretGallery(SecretMediaPreviewController)
case chatAvatars(AvatarGalleryController, Media)
case theme(TelegramMediaFile)
case other(Media)
}
private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] {
switch block {
case let .image(id, caption, _, _):
if let m = media[id] {
let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))]
counter += 1
return result
}
case let .video(id, caption, _, _):
if let m = media[id] {
let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))]
counter += 1
return result
}
case let .collage(items, _):
var result: [InstantPageGalleryEntry] = []
for item in items {
result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter))
}
return result
case let .slideshow(items, _):
var result: [InstantPageGalleryEntry] = []
for item in items {
result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter))
}
return result
default:
break
}
return []
}
public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] {
var result: [InstantPageGalleryEntry] = []
var counter: Int = 0
for block in page.blocks {
result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter))
}
var found = false
for item in result {
if item.media.media.id == galleryMedia.id {
found = true
break
}
}
if !found {
result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0)
}
for i in 0 ..< result.count {
let item = result[i]
result[i] = InstantPageGalleryEntry(index: Int32(i), pageId: item.pageId, media: item.media, caption: item.caption, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count)))
}
return result
}
public func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? {
var galleryMedia: Media?
var otherMedia: Media?
var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])?
for media in message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case let .photoUpdated(image):
if let peer = messageMainPeer(message), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")])
let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in
})
return .chatAvatars(galleryController, image)
}
default:
break
}
} else if let file = media as? TelegramMediaFile {
galleryMedia = file
} else if let image = media as? TelegramMediaImage {
galleryMedia = image
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let file = content.file {
galleryMedia = file
} else if let image = content.image {
if case .link = mode {
} else if ["photo", "document", "video", "gif", "telegram_album"].contains(content.type) {
galleryMedia = image
}
}
if let instantPage = content.instantPage, let galleryMedia = galleryMedia {
switch instantPageType(of: content) {
case .album:
let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia)
if medias.count > 1 {
instantPageMedia = (webpage, medias)
}
default:
break
}
}
} else if let mapMedia = media as? TelegramMediaMap {
galleryMedia = mapMedia
} else if let contactMedia = media as? TelegramMediaContact {
otherMedia = contactMedia
}
}
var stream = false
var autoplayingVideo = false
var landscape = false
var timecode: Double? = nil
switch mode {
case .stream:
stream = true
case .automaticPlayback:
autoplayingVideo = true
case .landscape:
autoplayingVideo = true
landscape = true
case let .timecode(time):
timecode = time
default:
break
}
if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia {
var centralIndex: Int = 0
for i in 0 ..< instantPageMedia.count {
if instantPageMedia[i].media.media.id == galleryMedia.id {
centralIndex = i
break
}
}
let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in
if let navigationController = navigationController {
navigationController.replaceTopController(controller, animated: false, ready: ready)
}
}, baseNavigationController: navigationController)
return .instantPage(gallery, centralIndex, galleryMedia)
} else if let galleryMedia = galleryMedia {
if let mapMedia = galleryMedia as? TelegramMediaMap {
return .map(mapMedia)
} else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) {
for attribute in file.attributes {
if case let .Sticker(_, reference, _) = attribute {
if let reference = reference {
return .stickerPack(reference)
}
break
}
}
} else if let file = galleryMedia as? TelegramMediaFile, file.isAnimatedSticker {
return nil
} else if let file = galleryMedia as? TelegramMediaFile, file.isMusic || file.isVoice || file.isInstantVideo {
return .audio(file)
} else if let file = galleryMedia as? TelegramMediaFile, file.mimeType == "application/vnd.apple.pkpass" || (file.fileName != nil && file.fileName!.lowercased().hasSuffix(".pkpass")) {
return .pass(file)
} else {
if let file = galleryMedia as? TelegramMediaFile {
if let fileName = file.fileName {
let ext = (fileName as NSString).pathExtension.lowercased()
if ext == "tgios-theme" {
return .theme(file)
} else if ext == "wav" || ext == "opus" {
return .audio(file)
} else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 {
if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let composition = LOTComposition(filePath: path), composition.timeDuration > 0.0 {
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? ChatLocation.peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(.single(gallery))
}
}
if ext == "mkv" {
return .document(file, true)
}
}
if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") {
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(.single(gallery))
}
if !file.isVideo {
return .document(file, false)
}
}
if message.containsSecretMedia {
let gallery = SecretMediaPreviewController(context: context, messageId: message.id)
return .secretGallery(gallery)
} else {
let startTimecode: Signal<Double?, NoError>
if let timecode = timecode {
startTimecode = .single(timecode)
} else {
startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id)
|> map { state in
return state?.timestamp
}
}
return .gallery(startTimecode
|> deliverOnMainQueue
|> map { timecode in
let gallery = GalleryController(context: context, source: source ?? (standalone ? .standaloneMessage(message) : .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil))), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
gallery.temporaryDoNotWaitForReady = autoplayingVideo
return gallery
})
}
}
}
if let otherMedia = otherMedia {
return .other(otherMedia)
} else {
return nil
}
}
public enum ChatMessagePreviewControllerData {
case instantPage(InstantPageGalleryController, Int, Media)
case gallery(GalleryController)
}
public func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? {
if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) {
switch mediaData {
case let .gallery(gallery):
break
case let .instantPage(gallery, centralIndex, galleryMedia):
return .instantPage(gallery, centralIndex, galleryMedia)
default:
break
}
}
return nil
}
public func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal<ChatMessagePreviewControllerData?, NoError> {
if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) {
switch mediaData {
case let .gallery(gallery):
return gallery
|> map { gallery in
return .gallery(gallery)
}
case let .instantPage(gallery, centralIndex, galleryMedia):
return .single(.instantPage(gallery, centralIndex, galleryMedia))
default:
break
}
}
return .single(nil)
}

View File

@ -246,23 +246,20 @@ public final class GalleryControllerPresentationArguments {
private enum GalleryMessageHistoryView {
case view(MessageHistoryView)
case single(MessageHistoryEntry)
case searchResults(SearchMessagesResult, SearchMessagesState)
case entries([MessageHistoryEntry], Bool, Bool)
var entries: [MessageHistoryEntry] {
switch self {
case let .view(view):
return view.entries
case let .single(entry):
return [entry]
case let .searchResults(result, _):
return result.messages.map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }
case let .entries(entries, _, _):
return entries
}
}
var tagMask: MessageTags? {
switch self {
case .single, .searchResults:
case .entries:
return nil
case let .view(view):
return view.tagMask
@ -271,23 +268,19 @@ private enum GalleryMessageHistoryView {
var hasEarlier: Bool {
switch self {
case .single:
return false
case let .entries(_, hasEarlier, _):
return hasEarlier
case let .view(view):
return view.earlierId != nil
case let .searchResults(result, _):
return false
}
}
var hasLater: Bool {
switch self {
case .single:
return false
case let .entries(_ , _, hasLater):
return hasLater
case let .view(view):
return view.laterId != nil
case let .searchResults(result, _):
return false
}
}
}
@ -423,15 +416,19 @@ public class GalleryController: ViewController, StandalonePresentableController
message = context.account.postbox.messageAtId(messageId)
case let .standaloneMessage(m):
message = .single(m)
case let .searchResult(result, _, messageId):
message = .single(result.messages.first(where: { $0.id == messageId }))
case let .custom(messages, messageId, _):
message = messages
|> take(1)
|> map { messages, _, _ in
return messages.first(where: { $0.id == messageId })
}
}
let messageView = message
|> filter({ $0 != nil })
|> mapToSignal { message -> Signal<GalleryMessageHistoryView?, NoError> in
switch source {
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
if let tags = tagsForMessage(message!) {
let namespaces: MessageIdNamespaces
if Namespaces.Message.allScheduled.contains(message!.id.namespace) {
@ -440,17 +437,26 @@ public class GalleryController: ViewController, StandalonePresentableController
namespaces = .not(Namespaces.Message.allScheduled)
}
return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
}
} else {
return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))))
}
return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false, false))
}
case .standaloneMessage:
return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))))
case let .searchResult(result, state, _):
return .single(GalleryMessageHistoryView.searchResults(result, state))
return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false ,false))
case let .custom(messages, _, _):
return messages
|> map { messages, totalCount, hasMore in
var entries: [MessageHistoryEntry] = []
var index = messages.count - 1
for message in messages.reversed() {
entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))
index -= 1
}
return GalleryMessageHistoryView.entries(entries, false, hasMore)
}
}
}
|> take(1)
@ -486,7 +492,7 @@ public class GalleryController: ViewController, StandalonePresentableController
centralEntryStableId = message.stableId
break loop
}
case let .searchResult(result, state, messageId):
case let .custom(_, messageId, _):
if message.id == messageId {
centralEntryStableId = message.stableId
break loop
@ -995,70 +1001,74 @@ public class GalleryController: ViewController, StandalonePresentableController
}
switch strongSelf.source {
case let .peerMessagesAtId(initialMessageId, chatLocation, chatLocationContextHolder):
var reloadAroundIndex: MessageIndex?
if index <= 2 && strongSelf.hasLeftEntries {
reloadAroundIndex = strongSelf.entries.first?.index
} else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
reloadAroundIndex = strongSelf.entries.last?.index
}
if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask {
let namespaces: MessageIdNamespaces
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
namespaces = .just(Namespaces.Message.allScheduled)
} else {
namespaces = .not(Namespaces.Message.allScheduled)
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
var reloadAroundIndex: MessageIndex?
if index <= 2 && strongSelf.hasLeftEntries {
reloadAroundIndex = strongSelf.entries.first?.index
} else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
reloadAroundIndex = strongSelf.entries.last?.index
}
let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
}
|> take(1)
strongSelf.updateVisibleDisposable.set((signal
|> deliverOnMainQueue).start(next: { view in
guard let strongSelf = self, let view = view else {
return
}
let entries = view.entries
if strongSelf.invertItemOrder {
strongSelf.entries = entries.reversed()
strongSelf.hasLeftEntries = view.hasLater
strongSelf.hasRightEntries = view.hasEarlier
if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask {
let namespaces: MessageIdNamespaces
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
namespaces = .just(Namespaces.Message.allScheduled)
} else {
strongSelf.entries = entries
strongSelf.hasLeftEntries = view.hasEarlier
strongSelf.hasRightEntries = view.hasLater
namespaces = .not(Namespaces.Message.allScheduled)
}
if strongSelf.isViewLoaded {
var items: [GalleryItem] = []
var centralItemIndex: Int?
for entry in strongSelf.entries {
var isCentral = false
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
}
}) {
if isCentral {
centralItemIndex = items.count
}
items.append(item)
}
let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
}
strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex)
}
}))
|> take(1)
strongSelf.updateVisibleDisposable.set((signal
|> deliverOnMainQueue).start(next: { view in
guard let strongSelf = self, let view = view else {
return
}
let entries = view.entries
if strongSelf.invertItemOrder {
strongSelf.entries = entries.reversed()
strongSelf.hasLeftEntries = view.hasLater
strongSelf.hasRightEntries = view.hasEarlier
} else {
strongSelf.entries = entries
strongSelf.hasLeftEntries = view.hasEarlier
strongSelf.hasRightEntries = view.hasLater
}
if strongSelf.isViewLoaded {
var items: [GalleryItem] = []
var centralItemIndex: Int?
for entry in strongSelf.entries {
var isCentral = false
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
}
}) {
if isCentral {
centralItemIndex = items.count
}
items.append(item)
}
}
strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex)
}
}))
}
default:
break
case let .custom(_, _, loadMore):
if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
loadMore?()
}
default:
break
}
}
if strongSelf.didSetReady {

View File

@ -45,7 +45,7 @@ public final class HashtagSearchController: TelegramBaseController {
let search = searchMessages(account: context.account, location: location, query: query, state: nil)
let foundMessages: Signal<[ChatListSearchEntry], NoError> = search
|> map { result, _ in
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData) })
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData, result.totalCount) })
}
let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { _, _ in

View File

@ -574,7 +574,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - dateNodeLayout.size.width, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))

View File

@ -425,7 +425,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
let authorText = NSAttributedString(string: authorString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
let (authorNodeLayout, authorNodeApply) = authorNodeMakeLayout(TextNodeLayoutArguments(attributedString: authorText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 12.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (authorNodeLayout, authorNodeApply) = authorNodeMakeLayout(TextNodeLayoutArguments(attributedString: authorText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var contentHeight = 9.0 + titleNodeLayout.size.height + 10.0 + descriptionNodeLayout.size.height + linkNodeLayout.size.height
if item.isGlobalSearchResult {

View File

@ -10,6 +10,7 @@ static_library(
"//submodules/SyncCore:SyncCore#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -11,6 +11,7 @@ swift_library(
"//submodules/SyncCore:SyncCore",
"//submodules/Postbox:Postbox",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
],
visibility = [
"//visibility:public",

View File

@ -156,6 +156,11 @@ public struct MessageHistoryEntryLocation: Equatable {
public let index: Int
public let count: Int
public init(index: Int, count: Int) {
self.index = index
self.count = count
}
var predecessor: MessageHistoryEntryLocation? {
if index == 0 {
return nil

View File

@ -231,7 +231,7 @@ private class SearchBarTextField: UITextField {
}
if !tokenSizes.isEmpty {
leftOffset -= 4.0
leftOffset -= 6.0
}
self.tokensWidth = leftOffset
@ -300,7 +300,7 @@ private class SearchBarTextField: UITextField {
if bounds.size.width.isZero {
return CGRect(origin: CGPoint(), size: CGSize())
}
var rect = bounds.insetBy(dx: 4.0, dy: 4.0)
var rect = bounds.insetBy(dx: 7.0, dy: 4.0)
rect.origin.y += 1.0
let prefixSize = self.measurePrefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
@ -682,16 +682,16 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
if let iconImage = self.iconNode.image {
let iconSize = iconImage.size
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 8.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0)), size: iconSize))
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 5.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0) - UIScreenPixel), size: iconSize))
}
if let activityIndicator = self.activityIndicator {
let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0))
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 7.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0)), size: indicatorSize))
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 9.0 + UIScreenPixel, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0) - 1.0), size: indicatorSize))
}
let clearSize = self.clearButton.measure(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 8.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize))
transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 6.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize))
self.textField.frame = textFrame
}
@ -729,7 +729,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction)
let textFieldFrame = self.textField.frame
let initialLabelNodeFrame = CGRect(origin: node.labelNode.frame.offsetBy(dx: initialTextBackgroundFrame.origin.x - 4.0, dy: initialTextBackgroundFrame.origin.y - 8.0).origin, size: textFieldFrame.size)
let initialLabelNodeFrame = CGRect(origin: node.labelNode.frame.offsetBy(dx: initialTextBackgroundFrame.origin.x - 7.0, dy: initialTextBackgroundFrame.origin.y - 8.0).origin, size: textFieldFrame.size)
self.textField.layer.animateFrame(from: initialLabelNodeFrame, to: self.textField.frame, duration: duration, timingFunction: timingFunction)
let iconFrame = self.iconNode.frame
@ -808,7 +808,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
let textFieldFrame = self.textField.frame
let targetLabelNodeFrame = CGRect(origin: CGPoint(x: node.labelNode.frame.minX + targetTextBackgroundFrame.origin.x - 4.0, y: targetTextBackgroundFrame.minY + floorToScreenPixels((targetTextBackgroundFrame.size.height - textFieldFrame.size.height) / 2.0) - UIScreenPixel), size: textFieldFrame.size)
let targetLabelNodeFrame = CGRect(origin: CGPoint(x: node.labelNode.frame.minX + targetTextBackgroundFrame.origin.x - 7.0, y: targetTextBackgroundFrame.minY + floorToScreenPixels((targetTextBackgroundFrame.size.height - textFieldFrame.size.height) / 2.0) - UIScreenPixel), size: textFieldFrame.size)
self.textField.layer.animateFrame(from: self.textField.frame, to: targetLabelNodeFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
if let snapshot = node.labelNode.layer.snapshotContentTree() {

View File

@ -143,7 +143,7 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
var iconSize = CGSize()
var totalWidth = labelLayoutResult.size.width
let spacing: CGFloat = 8.0
let spacing: CGFloat = 6.0
if let iconImage = strongSelf.iconNode.image {
iconSize = iconImage.size

View File

@ -168,7 +168,11 @@ func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Mes
}
}
return Message(stableId: 0, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
var hash: Int32 = id.id
hash = hash &* 31 &+ id.peerId.id
let stableId = UInt32(clamping: hash)
return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
}
public extension Message {

View File

@ -209,6 +209,7 @@ framework(
"//submodules/ListMessageItem:ListMessageItem",
"//submodules/FileMediaResourceStatus:FileMediaResourceStatus",
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
"//submodules/GalleryData:GalleryData",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -204,6 +204,7 @@ swift_library(
"//submodules/ListMessageItem:ListMessageItem",
"//submodules/FileMediaResourceStatus:FileMediaResourceStatus",
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
"//submodules/GalleryData:GalleryData",
],
visibility = [
"//visibility:public",

View File

@ -1,12 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Clear.pdf"
"filename" : "ic_search_clear.pdf",
"idiom" : "universal"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

View File

@ -1,22 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "IconSearch@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "IconSearch@3x.png",
"scale" : "3x"
"filename" : "ic_search_search.pdf",
"idiom" : "universal"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -60,6 +60,8 @@ import UrlWhitelist
import TelegramIntents
import TooltipUI
import StatisticsUI
import MediaResources
import GalleryData
extension ChatLocation {
var peerId: PeerId {

View File

@ -15,6 +15,7 @@ import UrlEscaping
import PhotoResources
import WebsiteType
import ChatMessageInteractiveMediaBadge
import GalleryData
private let buttonFont = Font.semibold(13.0)

View File

@ -12,65 +12,7 @@ import AccountContext
import WebsiteType
import InstantPageUI
import UrlHandling
func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] {
var result: [InstantPageGalleryEntry] = []
var counter: Int = 0
for block in page.blocks {
result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter))
}
var found = false
for item in result {
if item.media.media.id == galleryMedia.id {
found = true
break
}
}
if !found {
result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0)
}
for i in 0 ..< result.count {
let item = result[i]
result[i] = InstantPageGalleryEntry(index: Int32(i), pageId: item.pageId, media: item.media, caption: item.caption, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count)))
}
return result
}
private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] {
switch block {
case let .image(id, caption, _, _):
if let m = media[id] {
let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))]
counter += 1
return result
}
case let .video(id, caption, _, _):
if let m = media[id] {
let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))]
counter += 1
return result
}
case let .collage(items, _):
var result: [InstantPageGalleryEntry] = []
for item in items {
result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter))
}
return result
case let .slideshow(items, _):
var result: [InstantPageGalleryEntry] = []
for item in items {
result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter))
}
return result
default:
break
}
return []
}
import GalleryData
private let titleFont: UIFont = Font.semibold(15.0)

View File

@ -9,6 +9,7 @@ import WebSearchUI
import InstantPageCache
import SettingsUI
import WallpaperResources
import MediaResources
import LocationUI
private var telegramUIDeclaredEncodables: Void = {

View File

@ -13,6 +13,7 @@ import TelegramUIPreferences
import AccountContext
import TelegramUniversalVideoContent
import DeviceProximity
import MediaResources
enum SharedMediaPlayerGroup: Int {
case music = 0

View File

@ -22,226 +22,7 @@ import PresentationDataUtils
import ShareController
import UndoUI
import WebsiteType
private enum ChatMessageGalleryControllerData {
case url(String)
case pass(TelegramMediaFile)
case instantPage(InstantPageGalleryController, Int, Media)
case map(TelegramMediaMap)
case stickerPack(StickerPackReference)
case audio(TelegramMediaFile)
case document(TelegramMediaFile, Bool)
case gallery(Signal<GalleryController, NoError>)
case secretGallery(SecretMediaPreviewController)
case chatAvatars(AvatarGalleryController, Media)
case theme(TelegramMediaFile)
case other(Media)
}
private func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? {
var galleryMedia: Media?
var otherMedia: Media?
var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])?
for media in message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case let .photoUpdated(image):
if let peer = messageMainPeer(message), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")])
let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in
})
return .chatAvatars(galleryController, image)
}
default:
break
}
} else if let file = media as? TelegramMediaFile {
galleryMedia = file
} else if let image = media as? TelegramMediaImage {
galleryMedia = image
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let file = content.file {
galleryMedia = file
} else if let image = content.image {
if case .link = mode {
} else if ["photo", "document", "video", "gif", "telegram_album"].contains(content.type) {
galleryMedia = image
}
}
if let instantPage = content.instantPage, let galleryMedia = galleryMedia {
switch instantPageType(of: content) {
case .album:
let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia)
if medias.count > 1 {
instantPageMedia = (webpage, medias)
}
default:
break
}
}
} else if let mapMedia = media as? TelegramMediaMap {
galleryMedia = mapMedia
} else if let contactMedia = media as? TelegramMediaContact {
otherMedia = contactMedia
}
}
var stream = false
var autoplayingVideo = false
var landscape = false
var timecode: Double? = nil
switch mode {
case .stream:
stream = true
case .automaticPlayback:
autoplayingVideo = true
case .landscape:
autoplayingVideo = true
landscape = true
case let .timecode(time):
timecode = time
default:
break
}
if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia {
var centralIndex: Int = 0
for i in 0 ..< instantPageMedia.count {
if instantPageMedia[i].media.media.id == galleryMedia.id {
centralIndex = i
break
}
}
let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in
if let navigationController = navigationController {
navigationController.replaceTopController(controller, animated: false, ready: ready)
}
}, baseNavigationController: navigationController)
return .instantPage(gallery, centralIndex, galleryMedia)
} else if let galleryMedia = galleryMedia {
if let mapMedia = galleryMedia as? TelegramMediaMap {
return .map(mapMedia)
} else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) {
for attribute in file.attributes {
if case let .Sticker(_, reference, _) = attribute {
if let reference = reference {
return .stickerPack(reference)
}
break
}
}
} else if let file = galleryMedia as? TelegramMediaFile, file.isAnimatedSticker {
return nil
} else if let file = galleryMedia as? TelegramMediaFile, file.isMusic || file.isVoice || file.isInstantVideo {
return .audio(file)
} else if let file = galleryMedia as? TelegramMediaFile, file.mimeType == "application/vnd.apple.pkpass" || (file.fileName != nil && file.fileName!.lowercased().hasSuffix(".pkpass")) {
return .pass(file)
} else {
if let file = galleryMedia as? TelegramMediaFile {
if let fileName = file.fileName {
let ext = (fileName as NSString).pathExtension.lowercased()
if ext == "tgios-theme" {
return .theme(file)
} else if ext == "wav" || ext == "opus" {
return .audio(file)
} else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 {
if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let composition = LOTComposition(filePath: path), composition.timeDuration > 0.0 {
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? ChatLocation.peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(.single(gallery))
}
}
if ext == "mkv" {
return .document(file, true)
}
}
if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") {
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(.single(gallery))
}
if !file.isVideo {
return .document(file, false)
}
}
if message.containsSecretMedia {
let gallery = SecretMediaPreviewController(context: context, messageId: message.id)
return .secretGallery(gallery)
} else {
let startTimecode: Signal<Double?, NoError>
if let timecode = timecode {
startTimecode = .single(timecode)
} else {
startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id)
|> map { state in
return state?.timestamp
}
}
return .gallery(startTimecode
|> deliverOnMainQueue
|> map { timecode in
let gallery = GalleryController(context: context, source: source ?? (standalone ? .standaloneMessage(message) : .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil))), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
gallery.temporaryDoNotWaitForReady = autoplayingVideo
return gallery
})
}
}
}
if let otherMedia = otherMedia {
return .other(otherMedia)
} else {
return nil
}
}
enum ChatMessagePreviewControllerData {
case instantPage(InstantPageGalleryController, Int, Media)
case gallery(GalleryController)
}
func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? {
if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) {
switch mediaData {
case let .gallery(gallery):
break
case let .instantPage(gallery, centralIndex, galleryMedia):
return .instantPage(gallery, centralIndex, galleryMedia)
default:
break
}
}
return nil
}
func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal<ChatMessagePreviewControllerData?, NoError> {
if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) {
switch mediaData {
case let .gallery(gallery):
return gallery
|> map { gallery in
return .gallery(gallery)
}
case let .instantPage(gallery, centralIndex, galleryMedia):
return .single(.instantPage(gallery, centralIndex, galleryMedia))
default:
break
}
}
return .single(nil)
}
import GalleryData
func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) {

View File

@ -50,6 +50,7 @@ import TelegramNotices
import SaveToCameraRoll
import PeerInfoUI
import ListMessageItem
import GalleryData
protocol PeerInfoScreenItem: class {
var id: AnyHashable { get }

View File

@ -224,6 +224,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
requestOpenMessageFromSearch(peer, messageId)
}
}, addContact: nil, peerContextAction: nil, present: { _, _ in
}, presentInGlobalOverlay: { _, _ in
}, navigationController: nil), cancel: { [weak self] in
if let requestDeactivateSearch = self?.requestDeactivateSearch {
requestDeactivateSearch()