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.NoResults" = "No Results";
"ChatList.Search.NoResultsDescription" = "There were no results for \"%@\".\nTry a new search."; "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 { public enum GalleryControllerItemSource {
case peerMessagesAtId(messageId: MessageId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) case peerMessagesAtId(messageId: MessageId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>)
case standaloneMessage(Message) case standaloneMessage(Message)
case searchResult(SearchMessagesResult, SearchMessagesState, MessageId) case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, loadMore: (() -> Void)?)
} }
public final class GalleryControllerActionInteraction { public final class GalleryControllerActionInteraction {

View File

@ -4,7 +4,7 @@ import Display
import TelegramPresentationData import TelegramPresentationData
import ListSectionHeaderNode import ListSectionHeaderNode
public enum ChatListSearchItemHeaderType: Int32 { public enum ChatListSearchItemHeaderType {
case localPeers case localPeers
case members case members
case contacts case contacts
@ -13,7 +13,6 @@ public enum ChatListSearchItemHeaderType: Int32 {
case globalPeers case globalPeers
case deviceContacts case deviceContacts
case recentPeers case recentPeers
case messages
case phoneNumber case phoneNumber
case exceptions case exceptions
case addToExceptions case addToExceptions
@ -22,6 +21,123 @@ public enum ChatListSearchItemHeaderType: Int32 {
case chats case chats
case chatTypes case chatTypes
case faq 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 { 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) { public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String? = nil, action: (() -> Void)? = nil) {
self.type = type self.type = type
self.id = Int64(self.type.rawValue) self.id = Int64(self.type.id.rawValue)
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.actionTitle = actionTitle self.actionTitle = actionTitle
@ -75,43 +191,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
super.init() super.init()
switch type { self.sectionHeaderNode.title = type.title(strings: strings).uppercased()
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
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.action = actionTitle self.sectionHeaderNode.action = actionTitle
self.sectionHeaderNode.activateAction = action self.sectionHeaderNode.activateAction = action
@ -127,43 +207,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
self.actionTitle = actionTitle self.actionTitle = actionTitle
self.action = action self.action = action
switch type { self.sectionHeaderNode.title = type.title(strings: strings).uppercased()
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
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.action = actionTitle self.sectionHeaderNode.action = actionTitle
self.sectionHeaderNode.activateAction = action self.sectionHeaderNode.activateAction = action

View File

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

View File

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

View File

@ -1721,6 +1721,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
strongSelf.setDisplayNavigationBar(false, transition: transition) 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) 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.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 }, peerContextAction: self.peerContextAction, present: { [weak self] c, a in
self?.controller?.present(c, in: .window(.root), with: a) 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 }, navigationController: navigationController, updatedSearchOptions: { options in
updatedSearchOptions?(options) updatedSearchOptions?(options)
}) })

View File

@ -25,6 +25,7 @@ import UniversalMediaPlayer
import PresentationDataUtils import PresentationDataUtils
import AnimatedStickerNode import AnimatedStickerNode
import AppBundle import AppBundle
import GalleryData
private final class PassthroughContainerNode: ASDisplayNode { private final class PassthroughContainerNode: ASDisplayNode {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -271,7 +272,7 @@ public enum ChatListSearchSectionExpandType {
public enum ChatListSearchEntry: Comparable, Identifiable { public enum ChatListSearchEntry: Comparable, Identifiable {
case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType) case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case globalPeer(FoundPeer, (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) case addContact(String, PresentationTheme, PresentationStrings)
public var stableId: ChatListSearchEntryStableId { public var stableId: ChatListSearchEntryStableId {
@ -280,7 +281,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
return .localPeerId(peer.id) return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _): case let .globalPeer(peer, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id) return .globalPeerId(peer.peer.id)
case let .message(message, _, _, _): case let .message(message, _, _, _, _):
return .messageId(message.id) return .messageId(message.id)
case .addContact: case .addContact:
return .addContact return .addContact
@ -301,8 +302,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData): case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData) = rhs { if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount) = rhs {
if lhsMessage.id != rhsMessage.id { if lhsMessage.id != rhsMessage.id {
return false return false
} }
@ -318,6 +319,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
if lhsCombinedPeerReadState != rhsCombinedPeerReadState { if lhsCombinedPeerReadState != rhsCombinedPeerReadState {
return false return false
} }
if lhsTotalCount != rhsTotalCount {
return false
}
return true return true
} else { } else {
return false return false
@ -357,8 +361,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case .message, .addContact: case .message, .addContact:
return true return true
} }
case let .message(lhsMessage, _, _, _): case let .message(lhsMessage, _, _, _, _):
if case let .message(rhsMessage, _, _, _) = rhs { if case let .message(rhsMessage, _, _, _, _) = rhs {
return lhsMessage.index < rhsMessage.index return lhsMessage.index < rhsMessage.index
} else if case .addContact = rhs { } else if case .addContact = rhs {
return true 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 { switch self {
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType): case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: Peer let primaryPeer: Peer
@ -509,8 +513,27 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
peerContextAction(peer.peer, .search, node, gesture) peerContextAction(peer.peer, .search, node, gesture)
} }
}) })
case let .message(message, peer, readState, presentationData): case let .message(message, peer, readState, presentationData, totalCount):
let header: ChatListSearchItemHeader? = enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil 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 { if let tags = searchOptions?.messageTags, tags != .photoOrVideo {
let interaction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in let interaction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in
@ -712,6 +735,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = [] private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = []
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (ContainerViewLayout, CGFloat)?
private var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
private let recentDisposable = MetaDisposable() private let recentDisposable = MetaDisposable()
private let updatedRecentPeersDisposable = MetaDisposable() private let updatedRecentPeersDisposable = MetaDisposable()
@ -751,13 +776,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
private let emptyResultsAnimationNode: AnimatedStickerNode private let emptyResultsAnimationNode: AnimatedStickerNode
private var animationSize: CGSize = CGSize() 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.context = context
self.filter = filter self.filter = filter
self.dimNode = ASDisplayNode() self.dimNode = ASDisplayNode()
self.navigationController = navigationController self.navigationController = navigationController
self.updatedSearchOptions = updatedSearchOptions self.updatedSearchOptions = updatedSearchOptions
self.presentInGlobalOverlay = presentInGlobalOverlay
self.openMessage = originalOpenMessage self.openMessage = originalOpenMessage
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@ -769,11 +796,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
var openMediaMessageImpl: ((Message, ChatControllerInteractionOpenMessageMode) -> Void)? var openMediaMessageImpl: ((Message, ChatControllerInteractionOpenMessageMode) -> Void)?
var messageContextActionImpl: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?
var transitionNodeImpl: ((MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?)? var transitionNodeImpl: ((MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?)?
var addToTransitionSurfaceImpl: ((UIView) -> Void)? var addToTransitionSurfaceImpl: ((UIView) -> Void)?
self.mediaNode = ChatListSearchMediaNode(context: self.context, contentType: .photoOrVideo, openMessage: { message, mode in self.mediaNode = ChatListSearchMediaNode(context: self.context, contentType: .photoOrVideo, openMessage: { message, mode in
openMediaMessageImpl?(message, mode) openMediaMessageImpl?(message, mode)
}, messageContextAction: { message, sourceNode, sourceRect, gesture in
messageContextActionImpl?(message, sourceNode, sourceRect, gesture)
}) })
self.listNode = ListView() self.listNode = ListView()
@ -805,51 +835,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self.animationSize = CGSize(width: 124.0, height: 124.0) 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.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 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.listNode.isHidden = true
self.mediaNode.isHidden = true self.mediaNode.isHidden = true
self.listNode.visibleBottomContentOffsetChanged = { offset in 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 return
} }
updateSearchContext { previous in updateSearchContext { previous in
@ -903,9 +888,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
} }
} }
self.recentListNode.isHidden = filter.contains(.excludeRecent) self.recentListNode.isHidden = filter.contains(.excludeRecent)
let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer])?>(value: nil) let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer])?>(value: nil)
let presentationDataPromise = self.presentationDataPromise let presentationDataPromise = self.presentationDataPromise
let searchStatePromise = self.searchStatePromise let searchStatePromise = self.searchStatePromise
let foundItems = combineLatest(self.searchQuery.get(), self.searchOptions.get()) let foundItems = combineLatest(self.searchQuery.get(), self.searchOptions.get())
@ -1012,7 +996,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
return .complete() return .complete()
} }
} else { } else {
return .single(((searchContext.result.messages, searchContext.result.readStates, 0), false)) return .single(((searchContext.result.messages, searchContext.result.readStates, searchContext.result.totalCount), false))
} }
} else { } else {
return .complete() return .complete()
@ -1026,7 +1010,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
updateSearchContext { _ in updateSearchContext { _ in
return (ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil), true) 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()) |> delay(0.2, queue: Queue.concurrentDefaultQueue())
|> then(loadMore) |> then(loadMore)
@ -1036,7 +1020,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let resolvedMessage = .single(nil) let resolvedMessage = .single(nil)
|> then(context.sharedContext.resolveUrl(account: context.account, url: finalQuery) |> then(context.sharedContext.resolveUrl(account: context.account, url: finalQuery)
|> mapToSignal { resolvedUrl -> Signal<Message?, NoError> in |> 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) return downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: messageId)
} else { } else {
return .single(nil) return .single(nil)
@ -1200,7 +1184,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
peer = RenderedPeer(peer: channelPeer) peer = RenderedPeer(peer: channelPeer)
} }
} }
entries.append(.message(message, peer, nil, presentationData)) entries.append(.message(message, peer, nil, presentationData, 1))
index += 1 index += 1
} }
@ -1213,12 +1197,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
peer = RenderedPeer(peer: channelPeer) 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 index += 1
} }
} }
if addContact != nil && isViablePhoneNumber(finalQuery) { if let _ = addContact, isViablePhoneNumber(finalQuery) {
entries.append(.addContact(finalQuery, presentationData.theme, presentationData.strings)) 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 previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
let openPeer: (Peer, Bool) -> Void = { [weak self] peer, value in let openPeer: (Peer, Bool) -> Void = { peer, value in
guard let strongSelf = self else {
return
}
originalOpenPeer(peer, value) originalOpenPeer(peer, value)
if peer.id.namespace != Namespaces.Peer.SecretChat { 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.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedPeerId(peer.id, peerName: peer.compactDisplayTitle), clearQuery: true)
strongSelf.dismissInput?() strongSelf.dismissInput?()
}, searchResults: newEntries.compactMap { entry -> Message? in }, searchResults: newEntries.compactMap { entry -> Message? in
if case let .message(message, _, _, _) = entry { if case let .message(message, _, _, _, _) = entry {
return message return message
} else { } else {
return nil return nil
@ -1493,6 +1544,25 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
self?.dismissInput?() 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 self.filterContainerNode.filterPressed = { [weak self] filter in
guard let strongSelf = self else { guard let strongSelf = self else {
return 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.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.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 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)) 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?) { func messageContextAction(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) {
guard let node = node as? ContextExtractedContentContainingNode else { guard let node = node as? ContextExtractedContentContainingNode else {
return 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 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 c.dismiss(completion: { [weak self] in
if let strongSelf = self { 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) items.append(.separator)
self.interaction?.present(controller) 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
// self.controller?.window?.presentInGlobalOverlay(controller) 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() { public override func searchTextClearTokens() {
self.updateSearchOptions(nil) self.updateSearchOptions(nil)
self.setQuery?(nil, [], self.searchQueryValue ?? "") 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 { private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
@ -2153,3 +2355,32 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) 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 { final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
enum ContentType { enum ContentType {
case photoOrVideo case photoOrVideo
@ -612,18 +606,17 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
private var hiddenMediaDisposable: Disposable? private var hiddenMediaDisposable: Disposable?
private var mediaItems: [VisualMediaItem] = [] private var mediaItems: [VisualMediaItem] = []
private var itemsLayout: ItemsLayout? private var itemsLayout: ItemsLayout?
private var visibleMediaItems: [Int32: VisualMediaItemNode] = [:] private var visibleMediaItems: [UInt32: VisualMediaItemNode] = [:]
private var initialized = false
private var numberOfItemsToRequest: Int = 50
private var currentView: MessageHistoryView?
private var isRequestingView: Bool = false
private var isFirstHistoryView: Bool = true
private var decelerationAnimator: ConstantDisplayLinkAnimator? private var decelerationAnimator: ConstantDisplayLinkAnimator?
private var animationTimer: SwiftSignalKit.Timer? 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.context = context
self.contentType = contentType self.contentType = contentType
@ -638,7 +631,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
let _ = openMessage(message, .default) let _ = openMessage(message, .default)
}, },
openMessageContextActions: { [weak self] message, sourceNode, sourceRect, gesture in 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 toggleSelection: { [weak self] id, value in
// self?.chatControllerInteraction.toggleMessagesSelection([id], value) // self?.chatControllerInteraction.toggleMessagesSelection([id], value)
@ -658,8 +651,6 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
self.addSubnode(self.scrollNode) self.addSubnode(self.scrollNode)
self.addSubnode(self.floatingHeaderNode) self.addSubnode(self.floatingHeaderNode)
self.requestHistoryAroundVisiblePosition()
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -683,41 +674,25 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
self.animationTimer?.invalidate() 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) { func updateHistory(entries: [ChatListSearchEntry], updateType: ViewUpdateType) {
// self.currentView = view
switch updateType { switch updateType {
case .FillHole: case .FillHole:
self.requestHistoryAroundVisiblePosition() break
default: default:
self.mediaItems.removeAll() self.mediaItems.removeAll()
for entry in entries.reversed() { for entry in entries {
if case let .message(message, _, _, _) = entry { if case let .message(message, _, _, _, _) = entry {
self.mediaItems.append(VisualMediaItem(message: message)) self.mediaItems.append(VisualMediaItem(message: message))
} }
} }
self.itemsLayout = nil self.itemsLayout = nil
let wasFirstHistoryView = self.isFirstHistoryView let wasInitialized = self.initialized
self.isFirstHistoryView = false self.initialized = true
if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { 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 { if !self.didSetReady {
self.didSetReady = true self.didSetReady = true
self.ready.set(.single(true)) self.ready.set(.single(true))
@ -749,51 +724,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
itemNode.updateHiddenMedia() 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() { func cancelPreviewGestures() {
for (_, itemNode) in self.visibleMediaItems { for (_, itemNode) in self.visibleMediaItems {
itemNode.cancelPreviewGesture() itemNode.cancelPreviewGesture()
@ -803,7 +734,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
for item in self.mediaItems { for item in self.mediaItems {
if item.message.id == messageId { 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() return itemNode.transitionNode()
} }
break break
@ -863,17 +794,16 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
} }
self.updateHeaderFlashing(animated: true) self.updateHeaderFlashing(animated: true)
self.beganInteractiveDragging?()
} }
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let (size, sideInset, bottomInset, visibleHeight, _, _, presentationData) = self.currentParams { 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) 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 scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0 {
if !self.isRequestingView { self.loadMore?()
self.numberOfItemsToRequest += 50
self.requestHistoryAroundVisiblePosition()
}
} }
} }
} }
@ -908,13 +838,10 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
var headerItem: Message? var headerItem: Message?
var validIds = Set<Int32>() var validIds = Set<UInt32>()
if minVisibleIndex <= maxVisibleIndex { if minVisibleIndex <= maxVisibleIndex {
for i in minVisibleIndex ... maxVisibleIndex { for i in minVisibleIndex ... maxVisibleIndex {
var stableId = Int32(self.mediaItems[i].message.stableId) let stableId = self.mediaItems[i].message.stableId
if stableId == 0 {
stableId = getStableId(message: self.mediaItems[i].message)
}
validIds.insert(stableId) validIds.insert(stableId)
let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset) let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset)
@ -939,7 +866,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
itemNode.updateIsVisible(itemFrame.intersects(activeRect)) itemNode.updateIsVisible(itemFrame.intersects(activeRect))
} }
} }
var removeKeys: [Int32] = [] var removeKeys: [UInt32] = []
for (id, _) in self.visibleMediaItems { for (id, _) in self.visibleMediaItems {
if !validIds.contains(id) { if !validIds.contains(id) {
removeKeys.append(id) removeKeys.append(id)

View File

@ -673,6 +673,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
additionalTitleInset += badgeSize 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 (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())) 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 { private enum GalleryMessageHistoryView {
case view(MessageHistoryView) case view(MessageHistoryView)
case single(MessageHistoryEntry) case entries([MessageHistoryEntry], Bool, Bool)
case searchResults(SearchMessagesResult, SearchMessagesState)
var entries: [MessageHistoryEntry] { var entries: [MessageHistoryEntry] {
switch self { switch self {
case let .view(view): case let .view(view):
return view.entries return view.entries
case let .single(entry): case let .entries(entries, _, _):
return [entry] return entries
case let .searchResults(result, _):
return result.messages.map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }
} }
} }
var tagMask: MessageTags? { var tagMask: MessageTags? {
switch self { switch self {
case .single, .searchResults: case .entries:
return nil return nil
case let .view(view): case let .view(view):
return view.tagMask return view.tagMask
@ -271,23 +268,19 @@ private enum GalleryMessageHistoryView {
var hasEarlier: Bool { var hasEarlier: Bool {
switch self { switch self {
case .single: case let .entries(_, hasEarlier, _):
return false return hasEarlier
case let .view(view): case let .view(view):
return view.earlierId != nil return view.earlierId != nil
case let .searchResults(result, _):
return false
} }
} }
var hasLater: Bool { var hasLater: Bool {
switch self { switch self {
case .single: case let .entries(_ , _, hasLater):
return false return hasLater
case let .view(view): case let .view(view):
return view.laterId != nil 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) message = context.account.postbox.messageAtId(messageId)
case let .standaloneMessage(m): case let .standaloneMessage(m):
message = .single(m) message = .single(m)
case let .searchResult(result, _, messageId): case let .custom(messages, messageId, _):
message = .single(result.messages.first(where: { $0.id == messageId })) message = messages
|> take(1)
|> map { messages, _, _ in
return messages.first(where: { $0.id == messageId })
}
} }
let messageView = message let messageView = message
|> filter({ $0 != nil }) |> filter({ $0 != nil })
|> mapToSignal { message -> Signal<GalleryMessageHistoryView?, NoError> in |> mapToSignal { message -> Signal<GalleryMessageHistoryView?, NoError> in
switch source { switch source {
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder): case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
if let tags = tagsForMessage(message!) { if let tags = tagsForMessage(message!) {
let namespaces: MessageIdNamespaces let namespaces: MessageIdNamespaces
if Namespaces.Message.allScheduled.contains(message!.id.namespace) { if Namespaces.Message.allScheduled.contains(message!.id.namespace) {
@ -440,17 +437,26 @@ public class GalleryController: ViewController, StandalonePresentableController
namespaces = .not(Namespaces.Message.allScheduled) 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]) 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 |> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view) let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped) return .single(mapped)
} }
} else { } 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: case .standaloneMessage:
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 let .searchResult(result, state, _): case let .custom(messages, _, _):
return .single(GalleryMessageHistoryView.searchResults(result, state)) 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) |> take(1)
@ -486,7 +492,7 @@ public class GalleryController: ViewController, StandalonePresentableController
centralEntryStableId = message.stableId centralEntryStableId = message.stableId
break loop break loop
} }
case let .searchResult(result, state, messageId): case let .custom(_, messageId, _):
if message.id == messageId { if message.id == messageId {
centralEntryStableId = message.stableId centralEntryStableId = message.stableId
break loop break loop
@ -995,70 +1001,74 @@ public class GalleryController: ViewController, StandalonePresentableController
} }
switch strongSelf.source { switch strongSelf.source {
case let .peerMessagesAtId(initialMessageId, chatLocation, chatLocationContextHolder): case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
var reloadAroundIndex: MessageIndex? var reloadAroundIndex: MessageIndex?
if index <= 2 && strongSelf.hasLeftEntries { if index <= 2 && strongSelf.hasLeftEntries {
reloadAroundIndex = strongSelf.entries.first?.index reloadAroundIndex = strongSelf.entries.first?.index
} else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries { } else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
reloadAroundIndex = strongSelf.entries.last?.index 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)
} }
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]) if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask {
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in let namespaces: MessageIdNamespaces
let mapped = GalleryMessageHistoryView.view(view) if Namespaces.Message.allScheduled.contains(message.id.namespace) {
return .single(mapped) namespaces = .just(Namespaces.Message.allScheduled)
}
|> 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 { } else {
strongSelf.entries = entries namespaces = .not(Namespaces.Message.allScheduled)
strongSelf.hasLeftEntries = view.hasEarlier
strongSelf.hasRightEntries = view.hasLater
} }
if strongSelf.isViewLoaded { 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])
var items: [GalleryItem] = [] |> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
var centralItemIndex: Int? let mapped = GalleryMessageHistoryView.view(view)
for entry in strongSelf.entries { return .single(mapped)
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)
}
} }
|> take(1)
strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex)
} 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: case let .custom(_, _, loadMore):
break if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
loadMore?()
}
default:
break
} }
} }
if strongSelf.didSetReady { 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 search = searchMessages(account: context.account, location: location, query: query, state: nil)
let foundMessages: Signal<[ChatListSearchEntry], NoError> = search let foundMessages: Signal<[ChatListSearchEntry], NoError> = search
|> map { result, _ in |> 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: { let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { _, _ in }, 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 (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())) 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 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 var contentHeight = 9.0 + titleNodeLayout.size.height + 10.0 + descriptionNodeLayout.size.height + linkNodeLayout.size.height
if item.isGlobalSearchResult { if item.isGlobalSearchResult {

View File

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

View File

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

View File

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

View File

@ -231,7 +231,7 @@ private class SearchBarTextField: UITextField {
} }
if !tokenSizes.isEmpty { if !tokenSizes.isEmpty {
leftOffset -= 4.0 leftOffset -= 6.0
} }
self.tokensWidth = leftOffset self.tokensWidth = leftOffset
@ -300,7 +300,7 @@ private class SearchBarTextField: UITextField {
if bounds.size.width.isZero { if bounds.size.width.isZero {
return CGRect(origin: CGPoint(), size: CGSize()) 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 rect.origin.y += 1.0
let prefixSize = self.measurePrefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height)) 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 { if let iconImage = self.iconNode.image {
let iconSize = iconImage.size 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 { if let activityIndicator = self.activityIndicator {
let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0)) 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)) 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 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) self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction)
let textFieldFrame = self.textField.frame 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) self.textField.layer.animateFrame(from: initialLabelNodeFrame, to: self.textField.frame, duration: duration, timingFunction: timingFunction)
let iconFrame = self.iconNode.frame 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) transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
let textFieldFrame = self.textField.frame 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) 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 #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
if let snapshot = node.labelNode.layer.snapshotContentTree() { if let snapshot = node.labelNode.layer.snapshotContentTree() {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,22 +1,12 @@
{ {
"images" : [ "images" : [
{ {
"idiom" : "universal", "filename" : "ic_search_search.pdf",
"scale" : "1x" "idiom" : "universal"
},
{
"idiom" : "universal",
"filename" : "IconSearch@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "IconSearch@3x.png",
"scale" : "3x"
} }
], ],
"info" : { "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 TelegramIntents
import TooltipUI import TooltipUI
import StatisticsUI import StatisticsUI
import MediaResources
import GalleryData
extension ChatLocation { extension ChatLocation {
var peerId: PeerId { var peerId: PeerId {

View File

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

View File

@ -12,65 +12,7 @@ import AccountContext
import WebsiteType import WebsiteType
import InstantPageUI import InstantPageUI
import UrlHandling import UrlHandling
import GalleryData
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 []
}
private let titleFont: UIFont = Font.semibold(15.0) private let titleFont: UIFont = Font.semibold(15.0)

View File

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

View File

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

View File

@ -22,226 +22,7 @@ import PresentationDataUtils
import ShareController import ShareController
import UndoUI import UndoUI
import WebsiteType import WebsiteType
import GalleryData
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)
}
func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { 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) { 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 SaveToCameraRoll
import PeerInfoUI import PeerInfoUI
import ListMessageItem import ListMessageItem
import GalleryData
protocol PeerInfoScreenItem: class { protocol PeerInfoScreenItem: class {
var id: AnyHashable { get } var id: AnyHashable { get }

View File

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