mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Search filters improvements
This commit is contained in:
parent
9c52f3a9a6
commit
3fd6ee004b
@ -5754,3 +5754,31 @@ Any member of this group will be able to see messages in the channel.";
|
||||
|
||||
"ChatList.Search.NoResults" = "No Results";
|
||||
"ChatList.Search.NoResultsDescription" = "There were no results for \"%@\".\nTry a new search.";
|
||||
|
||||
"ChatList.Search.Messages_0" = "%@ messages";
|
||||
"ChatList.Search.Messages_1" = "%@ message";
|
||||
"ChatList.Search.Messages_2" = "%@ messages";
|
||||
"ChatList.Search.Messages_3_10" = "%@ messages";
|
||||
"ChatList.Search.Messages_many" = "%@ messages";
|
||||
"ChatList.Search.Messages_any" = "%@ messages";
|
||||
|
||||
"ChatList.Search.Links_0" = "%@ links";
|
||||
"ChatList.Search.Links_1" = "%@ link";
|
||||
"ChatList.Search.Links_2" = "%@ links";
|
||||
"ChatList.Search.Links_3_10" = "%@ links";
|
||||
"ChatList.Search.Links_many" = "%@ links";
|
||||
"ChatList.Search.Links_any" = "%@ links";
|
||||
|
||||
"ChatList.Search.Files_0" = "%@ files";
|
||||
"ChatList.Search.Files_1" = "%@ file";
|
||||
"ChatList.Search.Files_2" = "%@ files";
|
||||
"ChatList.Search.Files_3_10" = "%@ files";
|
||||
"ChatList.Search.Files_many" = "%@ files";
|
||||
"ChatList.Search.Files_any" = "%@ files";
|
||||
|
||||
"ChatList.Search.Music_0" = "%@ audio files";
|
||||
"ChatList.Search.Music_1" = "%@ audio file";
|
||||
"ChatList.Search.Music_2" = "%@ audio files";
|
||||
"ChatList.Search.Music_3_10" = "%@ audio files";
|
||||
"ChatList.Search.Music_many" = "%@ audio files";
|
||||
"ChatList.Search.Music_any" = "%@ audio files";
|
||||
|
||||
@ -6,7 +6,7 @@ import TelegramCore
|
||||
public enum GalleryControllerItemSource {
|
||||
case peerMessagesAtId(messageId: MessageId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>)
|
||||
case standaloneMessage(Message)
|
||||
case searchResult(SearchMessagesResult, SearchMessagesState, MessageId)
|
||||
case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, loadMore: (() -> Void)?)
|
||||
}
|
||||
|
||||
public final class GalleryControllerActionInteraction {
|
||||
|
||||
@ -4,7 +4,7 @@ import Display
|
||||
import TelegramPresentationData
|
||||
import ListSectionHeaderNode
|
||||
|
||||
public enum ChatListSearchItemHeaderType: Int32 {
|
||||
public enum ChatListSearchItemHeaderType {
|
||||
case localPeers
|
||||
case members
|
||||
case contacts
|
||||
@ -13,7 +13,6 @@ public enum ChatListSearchItemHeaderType: Int32 {
|
||||
case globalPeers
|
||||
case deviceContacts
|
||||
case recentPeers
|
||||
case messages
|
||||
case phoneNumber
|
||||
case exceptions
|
||||
case addToExceptions
|
||||
@ -22,6 +21,123 @@ public enum ChatListSearchItemHeaderType: Int32 {
|
||||
case chats
|
||||
case chatTypes
|
||||
case faq
|
||||
case messages(Int32)
|
||||
case links(Int32)
|
||||
case files(Int32)
|
||||
case music(Int32)
|
||||
|
||||
fileprivate func title(strings: PresentationStrings) -> String {
|
||||
switch self {
|
||||
case .localPeers:
|
||||
return strings.DialogList_SearchSectionDialogs
|
||||
case .members:
|
||||
return strings.Channel_Info_Members
|
||||
case .contacts:
|
||||
return strings.Contacts_TopSection
|
||||
case .bots:
|
||||
return strings.MemberSearch_BotSection
|
||||
case .admins:
|
||||
return strings.Channel_Management_Title
|
||||
case .globalPeers:
|
||||
return strings.DialogList_SearchSectionGlobal
|
||||
case .deviceContacts:
|
||||
return strings.Contacts_NotRegisteredSection
|
||||
case .recentPeers:
|
||||
return strings.DialogList_SearchSectionRecent
|
||||
case .phoneNumber:
|
||||
return strings.Contacts_PhoneNumber
|
||||
case .exceptions:
|
||||
return strings.GroupInfo_Permissions_Exceptions
|
||||
case .addToExceptions:
|
||||
return strings.Exceptions_AddToExceptions
|
||||
case .mapAddress:
|
||||
return strings.Map_AddressOnMap
|
||||
case .nearbyVenues:
|
||||
return strings.Map_PlacesNearby
|
||||
case .chats:
|
||||
return strings.Cache_ByPeerHeader
|
||||
case .chatTypes:
|
||||
return strings.ChatList_ChatTypesSection
|
||||
case .faq:
|
||||
return strings.Settings_FrequentlyAskedQuestions
|
||||
case let .messages(count):
|
||||
return strings.ChatList_Search_Messages(count)
|
||||
case let .links(count):
|
||||
return strings.ChatList_Search_Links(count)
|
||||
case let .files(count):
|
||||
return strings.ChatList_Search_Files(count)
|
||||
case let .music(count):
|
||||
return strings.ChatList_Search_Music(count)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var id: ChatListSearchItemHeaderId {
|
||||
switch self {
|
||||
case .localPeers:
|
||||
return .localPeers
|
||||
case .members:
|
||||
return .members
|
||||
case .contacts:
|
||||
return .contacts
|
||||
case .bots:
|
||||
return .bots
|
||||
case .admins:
|
||||
return .admins
|
||||
case .globalPeers:
|
||||
return .globalPeers
|
||||
case .deviceContacts:
|
||||
return .deviceContacts
|
||||
case .recentPeers:
|
||||
return .recentPeers
|
||||
case .phoneNumber:
|
||||
return .phoneNumber
|
||||
case .exceptions:
|
||||
return .exceptions
|
||||
case .addToExceptions:
|
||||
return .addToExceptions
|
||||
case .mapAddress:
|
||||
return .mapAddress
|
||||
case .nearbyVenues:
|
||||
return .nearbyVenues
|
||||
case .chats:
|
||||
return .chats
|
||||
case .chatTypes:
|
||||
return .chatTypes
|
||||
case .faq:
|
||||
return .faq
|
||||
case .messages:
|
||||
return .messages
|
||||
case .links:
|
||||
return .links
|
||||
case .files:
|
||||
return .files
|
||||
case .music:
|
||||
return .music
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ChatListSearchItemHeaderId: Int32 {
|
||||
case localPeers
|
||||
case members
|
||||
case contacts
|
||||
case bots
|
||||
case admins
|
||||
case globalPeers
|
||||
case deviceContacts
|
||||
case recentPeers
|
||||
case phoneNumber
|
||||
case exceptions
|
||||
case addToExceptions
|
||||
case mapAddress
|
||||
case nearbyVenues
|
||||
case chats
|
||||
case chatTypes
|
||||
case faq
|
||||
case messages
|
||||
case links
|
||||
case files
|
||||
case music
|
||||
}
|
||||
|
||||
public final class ChatListSearchItemHeader: ListViewItemHeader {
|
||||
@ -37,7 +153,7 @@ public final class ChatListSearchItemHeader: ListViewItemHeader {
|
||||
|
||||
public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String? = nil, action: (() -> Void)? = nil) {
|
||||
self.type = type
|
||||
self.id = Int64(self.type.rawValue)
|
||||
self.id = Int64(self.type.id.rawValue)
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.actionTitle = actionTitle
|
||||
@ -75,43 +191,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
|
||||
|
||||
super.init()
|
||||
|
||||
switch type {
|
||||
case .localPeers:
|
||||
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
|
||||
case .members:
|
||||
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
|
||||
case .contacts:
|
||||
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
|
||||
case .bots:
|
||||
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
|
||||
case .admins:
|
||||
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
|
||||
case .globalPeers:
|
||||
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
|
||||
case .deviceContacts:
|
||||
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
|
||||
case .messages:
|
||||
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
|
||||
case .recentPeers:
|
||||
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
|
||||
case .phoneNumber:
|
||||
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
|
||||
case .exceptions:
|
||||
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
|
||||
case .addToExceptions:
|
||||
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
|
||||
case .mapAddress:
|
||||
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
|
||||
case .nearbyVenues:
|
||||
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
|
||||
case .chats:
|
||||
self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased()
|
||||
case .chatTypes:
|
||||
self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased()
|
||||
case .faq:
|
||||
self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased()
|
||||
}
|
||||
|
||||
self.sectionHeaderNode.title = type.title(strings: strings).uppercased()
|
||||
self.sectionHeaderNode.action = actionTitle
|
||||
self.sectionHeaderNode.activateAction = action
|
||||
|
||||
@ -127,43 +207,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
|
||||
self.actionTitle = actionTitle
|
||||
self.action = action
|
||||
|
||||
switch type {
|
||||
case .localPeers:
|
||||
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
|
||||
case .members:
|
||||
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
|
||||
case .contacts:
|
||||
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
|
||||
case .bots:
|
||||
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
|
||||
case .admins:
|
||||
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
|
||||
case .globalPeers:
|
||||
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
|
||||
case .deviceContacts:
|
||||
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
|
||||
case .messages:
|
||||
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
|
||||
case .recentPeers:
|
||||
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
|
||||
case .phoneNumber:
|
||||
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
|
||||
case .exceptions:
|
||||
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
|
||||
case .addToExceptions:
|
||||
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
|
||||
case .mapAddress:
|
||||
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
|
||||
case .nearbyVenues:
|
||||
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
|
||||
case .chats:
|
||||
self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased()
|
||||
case .chatTypes:
|
||||
self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased()
|
||||
case .faq:
|
||||
self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased()
|
||||
}
|
||||
|
||||
self.sectionHeaderNode.title = type.title(strings: strings).uppercased()
|
||||
self.sectionHeaderNode.action = actionTitle
|
||||
self.sectionHeaderNode.activateAction = action
|
||||
|
||||
|
||||
@ -49,6 +49,7 @@ static_library(
|
||||
"//submodules/ListMessageItem:ListMessageItem",
|
||||
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
||||
@ -49,6 +49,7 @@ swift_library(
|
||||
"//submodules/ListMessageItem:ListMessageItem",
|
||||
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
"//submodules/GalleryData:GalleryData",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -1721,6 +1721,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
|
||||
strongSelf.setDisplayNavigationBar(false, transition: transition)
|
||||
|
||||
(strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1743,8 +1745,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
parentController.navigationBar?.setSecondaryContentNode(filtersIsEmpty ? nil : self.tabContainerNode, animated: animated)
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.5, curve: .spring) : .immediate
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
|
||||
self.setDisplayNavigationBar(true, transition: transition)
|
||||
|
||||
(self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1167,6 +1167,8 @@ final class ChatListControllerNode: ASDisplayNode {
|
||||
}
|
||||
}, peerContextAction: self.peerContextAction, present: { [weak self] c, a in
|
||||
self?.controller?.present(c, in: .window(.root), with: a)
|
||||
}, presentInGlobalOverlay: { [weak self] c, a in
|
||||
self?.controller?.presentInGlobalOverlay(c, with: a)
|
||||
}, navigationController: navigationController, updatedSearchOptions: { options in
|
||||
updatedSearchOptions?(options)
|
||||
})
|
||||
|
||||
@ -25,6 +25,7 @@ import UniversalMediaPlayer
|
||||
import PresentationDataUtils
|
||||
import AnimatedStickerNode
|
||||
import AppBundle
|
||||
import GalleryData
|
||||
|
||||
private final class PassthroughContainerNode: ASDisplayNode {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
@ -271,7 +272,7 @@ public enum ChatListSearchSectionExpandType {
|
||||
public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
case localPeer(Peer, Peer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
|
||||
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
|
||||
case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData)
|
||||
case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData, Int32)
|
||||
case addContact(String, PresentationTheme, PresentationStrings)
|
||||
|
||||
public var stableId: ChatListSearchEntryStableId {
|
||||
@ -280,7 +281,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
return .localPeerId(peer.id)
|
||||
case let .globalPeer(peer, _, _, _, _, _, _, _):
|
||||
return .globalPeerId(peer.peer.id)
|
||||
case let .message(message, _, _, _):
|
||||
case let .message(message, _, _, _, _):
|
||||
return .messageId(message.id)
|
||||
case .addContact:
|
||||
return .addContact
|
||||
@ -301,8 +302,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData):
|
||||
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData) = rhs {
|
||||
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount):
|
||||
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount) = rhs {
|
||||
if lhsMessage.id != rhsMessage.id {
|
||||
return false
|
||||
}
|
||||
@ -318,6 +319,9 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
if lhsCombinedPeerReadState != rhsCombinedPeerReadState {
|
||||
return false
|
||||
}
|
||||
if lhsTotalCount != rhsTotalCount {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -357,8 +361,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
case .message, .addContact:
|
||||
return true
|
||||
}
|
||||
case let .message(lhsMessage, _, _, _):
|
||||
if case let .message(rhsMessage, _, _, _) = rhs {
|
||||
case let .message(lhsMessage, _, _, _, _):
|
||||
if case let .message(rhsMessage, _, _, _, _) = rhs {
|
||||
return lhsMessage.index < rhsMessage.index
|
||||
} else if case .addContact = rhs {
|
||||
return true
|
||||
@ -370,7 +374,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, presentDatePicker: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem {
|
||||
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, interaction: ChatListNodeInteraction, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, presentDatePicker: @escaping () -> Void, searchPeer: @escaping (Peer) -> Void, searchResults: [Message], searchOptions: ChatListSearchOptions?, messageContextAction: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?) -> ListViewItem {
|
||||
switch self {
|
||||
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
|
||||
let primaryPeer: Peer
|
||||
@ -509,8 +513,27 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
peerContextAction(peer.peer, .search, node, gesture)
|
||||
}
|
||||
})
|
||||
case let .message(message, peer, readState, presentationData):
|
||||
let header: ChatListSearchItemHeader? = enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil
|
||||
case let .message(message, peer, readState, presentationData, totalCount):
|
||||
let header: ChatListSearchItemHeader?
|
||||
if enableHeaders {
|
||||
let type: ChatListSearchItemHeaderType
|
||||
if let searchOptions = searchOptions {
|
||||
if searchOptions.messageTags == .webPage {
|
||||
type = .links(totalCount)
|
||||
} else if searchOptions.messageTags == .file {
|
||||
type = .files(totalCount)
|
||||
} else if searchOptions.messageTags == .music {
|
||||
type = .music(totalCount)
|
||||
} else {
|
||||
type = .messages(totalCount)
|
||||
}
|
||||
} else {
|
||||
type = .messages(totalCount)
|
||||
}
|
||||
header = ChatListSearchItemHeader(type: type, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
} else {
|
||||
header = nil
|
||||
}
|
||||
|
||||
if let tags = searchOptions?.messageTags, tags != .photoOrVideo {
|
||||
let interaction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in
|
||||
@ -712,6 +735,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = []
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private var presentInGlobalOverlay: ((ViewController, Any?) -> Void)?
|
||||
|
||||
private let recentDisposable = MetaDisposable()
|
||||
private let updatedRecentPeersDisposable = MetaDisposable()
|
||||
|
||||
@ -751,13 +776,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
private let emptyResultsAnimationNode: AnimatedStickerNode
|
||||
private var animationSize: CGSize = CGSize()
|
||||
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?) -> Void)? = nil) {
|
||||
public init(context: AccountContext, filter: ChatListNodePeersFilter, groupId: PeerGroupId, openPeer originalOpenPeer: @escaping (Peer, Bool) -> Void, openDisabledPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage originalOpenMessage: @escaping (Peer, MessageId) -> Void, addContact: ((String) -> Void)?, peerContextAction: ((Peer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, present: @escaping (ViewController, Any?) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?) -> Void)? = nil) {
|
||||
self.context = context
|
||||
self.filter = filter
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.navigationController = navigationController
|
||||
self.updatedSearchOptions = updatedSearchOptions
|
||||
|
||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||
|
||||
self.openMessage = originalOpenMessage
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -769,11 +796,14 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
|
||||
var openMediaMessageImpl: ((Message, ChatControllerInteractionOpenMessageMode) -> Void)?
|
||||
var messageContextActionImpl: ((Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void)?
|
||||
var transitionNodeImpl: ((MessageId, Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?)?
|
||||
var addToTransitionSurfaceImpl: ((UIView) -> Void)?
|
||||
|
||||
self.mediaNode = ChatListSearchMediaNode(context: self.context, contentType: .photoOrVideo, openMessage: { message, mode in
|
||||
openMediaMessageImpl?(message, mode)
|
||||
}, messageContextAction: { message, sourceNode, sourceRect, gesture in
|
||||
messageContextActionImpl?(message, sourceNode, sourceRect, gesture)
|
||||
})
|
||||
|
||||
self.listNode = ListView()
|
||||
@ -805,51 +835,6 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.animationSize = CGSize(width: 124.0, height: 124.0)
|
||||
}
|
||||
|
||||
openMediaMessageImpl = { [weak self] message, mode in
|
||||
if let searchContext = self?.searchContextValue.with({ $0 }) {
|
||||
let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, navigationController: nil, dismissInput: {
|
||||
self?.view.window?.endEditing(true)
|
||||
}, present: { c, a in
|
||||
present(c, a)
|
||||
}, transitionNode: { messageId, media in
|
||||
return transitionNodeImpl?(messageId, media)
|
||||
}, addToTransitionSurface: { view in
|
||||
addToTransitionSurfaceImpl?(view)
|
||||
}, openUrl: { [weak self] url in
|
||||
openUserGeneratedUrl(context: context, url: url, concealed: false, present: { [weak self] c in
|
||||
present(c, nil)
|
||||
}, openResolved: { [weak self] resolved in
|
||||
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in
|
||||
// self?.openPeer(peerId: peerId, navigation: navigation)
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
present: { c, a in
|
||||
present(c, a)
|
||||
}, dismissInput: {
|
||||
self?.view.window?.endEditing(true)
|
||||
}, contentContext: nil)
|
||||
})
|
||||
}, openPeer: { peer, navigation in
|
||||
//self?.openPeer(peerId: peer.id, navigation: navigation)
|
||||
}, callPeer: { _, _ in
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .searchResult(SearchMessagesResult(messages: searchContext.result.messages, readStates: searchContext.result.readStates, totalCount: searchContext.result.totalCount, completed: !searchContext.result.hasMore), searchContext.result.state, message.id)))
|
||||
}
|
||||
}
|
||||
|
||||
transitionNodeImpl = { [weak self] messageId, media in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.mediaNode.transitionNodeForGallery(messageId: messageId, media: media)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
addToTransitionSurfaceImpl = { [weak self] view in
|
||||
if let strongSelf = self {
|
||||
strongSelf.mediaNode.addToTransitionSurface(view: view)
|
||||
}
|
||||
}
|
||||
self.dimNode.backgroundColor = filter.contains(.excludeRecent) ? UIColor.black.withAlphaComponent(0.5) : self.presentationData.theme.chatList.backgroundColor
|
||||
|
||||
self.backgroundColor = filter.contains(.excludeRecent) ? nil : self.presentationData.theme.chatList.backgroundColor
|
||||
@ -886,7 +871,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.listNode.isHidden = true
|
||||
self.mediaNode.isHidden = true
|
||||
self.listNode.visibleBottomContentOffsetChanged = { offset in
|
||||
guard case let .known(value) = offset, value < 100.0 else {
|
||||
guard case let .known(value) = offset, value < 160.0 else {
|
||||
return
|
||||
}
|
||||
updateSearchContext { previous in
|
||||
@ -903,9 +888,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
}
|
||||
self.recentListNode.isHidden = filter.contains(.excludeRecent)
|
||||
|
||||
|
||||
let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer])?>(value: nil)
|
||||
|
||||
let presentationDataPromise = self.presentationDataPromise
|
||||
let searchStatePromise = self.searchStatePromise
|
||||
let foundItems = combineLatest(self.searchQuery.get(), self.searchOptions.get())
|
||||
@ -1012,7 +996,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .single(((searchContext.result.messages, searchContext.result.readStates, 0), false))
|
||||
return .single(((searchContext.result.messages, searchContext.result.readStates, searchContext.result.totalCount), false))
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
@ -1026,7 +1010,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
updateSearchContext { _ in
|
||||
return (ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil), true)
|
||||
}
|
||||
return ((foundMessages.messages, foundMessages.readStates, 0), false)
|
||||
return ((foundMessages.messages, foundMessages.readStates, foundMessages.totalCount), false)
|
||||
}
|
||||
|> delay(0.2, queue: Queue.concurrentDefaultQueue())
|
||||
|> then(loadMore)
|
||||
@ -1036,7 +1020,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
let resolvedMessage = .single(nil)
|
||||
|> then(context.sharedContext.resolveUrl(account: context.account, url: finalQuery)
|
||||
|> mapToSignal { resolvedUrl -> Signal<Message?, NoError> in
|
||||
if case let .channelMessage(peerId, messageId) = resolvedUrl {
|
||||
if case let .channelMessage(_, messageId) = resolvedUrl {
|
||||
return downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: messageId)
|
||||
} else {
|
||||
return .single(nil)
|
||||
@ -1200,7 +1184,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
peer = RenderedPeer(peer: channelPeer)
|
||||
}
|
||||
}
|
||||
entries.append(.message(message, peer, nil, presentationData))
|
||||
entries.append(.message(message, peer, nil, presentationData, 1))
|
||||
index += 1
|
||||
}
|
||||
|
||||
@ -1213,12 +1197,12 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
peer = RenderedPeer(peer: channelPeer)
|
||||
}
|
||||
}
|
||||
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData))
|
||||
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData, foundRemoteMessages.0.2))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
if addContact != nil && isViablePhoneNumber(finalQuery) {
|
||||
if let _ = addContact, isViablePhoneNumber(finalQuery) {
|
||||
entries.append(.addContact(finalQuery, presentationData.theme, presentationData.strings))
|
||||
}
|
||||
|
||||
@ -1226,12 +1210,79 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
}
|
||||
|
||||
let foundMessages = searchContext.get() |> map { searchContext -> ([Message], Int32, Bool) in
|
||||
if let result = searchContext?.result {
|
||||
return (result.messages, result.totalCount, result.hasMore)
|
||||
} else {
|
||||
return ([], 0, false)
|
||||
}
|
||||
}
|
||||
openMediaMessageImpl = { [weak self] message, mode in
|
||||
let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, navigationController: nil, dismissInput: {
|
||||
self?.view.window?.endEditing(true)
|
||||
}, present: { c, a in
|
||||
present(c, a)
|
||||
}, transitionNode: { messageId, media in
|
||||
return transitionNodeImpl?(messageId, media)
|
||||
}, addToTransitionSurface: { view in
|
||||
addToTransitionSurfaceImpl?(view)
|
||||
}, openUrl: { [weak self] url in
|
||||
openUserGeneratedUrl(context: context, url: url, concealed: false, present: { [weak self] c in
|
||||
present(c, nil)
|
||||
}, openResolved: { [weak self] resolved in
|
||||
context.sharedContext.openResolvedUrl(resolved, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peerId, navigation in
|
||||
// self?.openPeer(peerId: peerId, navigation: navigation)
|
||||
}, sendFile: nil,
|
||||
sendSticker: nil,
|
||||
present: { c, a in
|
||||
present(c, a)
|
||||
}, dismissInput: {
|
||||
self?.view.window?.endEditing(true)
|
||||
}, contentContext: nil)
|
||||
})
|
||||
}, openPeer: { peer, navigation in
|
||||
//self?.openPeer(peerId: peer.id, navigation: navigation)
|
||||
}, callPeer: { _, _ in
|
||||
}, enqueueMessage: { _ in
|
||||
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages, messageId: message.id, loadMore: {
|
||||
updateSearchContext { previous in
|
||||
guard let previous = previous else {
|
||||
return (nil, false)
|
||||
}
|
||||
if previous.loadMoreIndex != nil {
|
||||
return (previous, false)
|
||||
}
|
||||
guard let last = previous.result.messages.last else {
|
||||
return (previous, false)
|
||||
}
|
||||
return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true)
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
messageContextActionImpl = { [weak self] message, sourceNode, sourceRect, gesture in
|
||||
if let strongSelf = self {
|
||||
strongSelf.messageContextActions(message, node: sourceNode, rect: sourceRect, gesture: gesture)
|
||||
}
|
||||
}
|
||||
|
||||
transitionNodeImpl = { [weak self] messageId, media in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.mediaNode.transitionNodeForGallery(messageId: messageId, media: media)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
addToTransitionSurfaceImpl = { [weak self] view in
|
||||
if let strongSelf = self {
|
||||
strongSelf.mediaNode.addToTransitionSurface(view: view)
|
||||
}
|
||||
}
|
||||
|
||||
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
||||
|
||||
let openPeer: (Peer, Bool) -> Void = { [weak self] peer, value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let openPeer: (Peer, Bool) -> Void = { peer, value in
|
||||
originalOpenPeer(peer, value)
|
||||
|
||||
if peer.id.namespace != Namespaces.Peer.SecretChat {
|
||||
@ -1457,7 +1508,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
strongSelf.updateSearchOptions(strongSelf.currentSearchOptions.withUpdatedPeerId(peer.id, peerName: peer.compactDisplayTitle), clearQuery: true)
|
||||
strongSelf.dismissInput?()
|
||||
}, searchResults: newEntries.compactMap { entry -> Message? in
|
||||
if case let .message(message, _, _, _) = entry {
|
||||
if case let .message(message, _, _, _, _) = entry {
|
||||
return message
|
||||
} else {
|
||||
return nil
|
||||
@ -1493,6 +1544,25 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self?.dismissInput?()
|
||||
}
|
||||
|
||||
self.mediaNode.beganInteractiveDragging = { [weak self] in
|
||||
self?.dismissInput?()
|
||||
}
|
||||
|
||||
self.mediaNode.loadMore = {
|
||||
updateSearchContext { previous in
|
||||
guard let previous = previous else {
|
||||
return (nil, false)
|
||||
}
|
||||
if previous.loadMoreIndex != nil {
|
||||
return (previous, false)
|
||||
}
|
||||
guard let last = previous.result.messages.last else {
|
||||
return (previous, false)
|
||||
}
|
||||
return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true)
|
||||
}
|
||||
}
|
||||
|
||||
self.filterContainerNode.filterPressed = { [weak self] filter in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1979,7 +2049,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: topInset, left: layout.safeInsets.left, bottom: layout.insets(options: [.input]).bottom, right: layout.safeInsets.right), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
self.mediaNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset))
|
||||
self.mediaNode.update(size: layout.size, sideInset: 0, bottomInset: 0, visibleHeight: layout.size.height - navigationBarHeight, isScrollingLockedAtTop: false, expandProgress: 1.0, presentationData: self.presentationData, synchronous: true, transition: transition)
|
||||
self.mediaNode.update(size: layout.size, sideInset: layout.safeInsets.left, bottomInset: layout.insets(options: [.input]).bottom, visibleHeight: layout.size.height - navigationBarHeight, isScrollingLockedAtTop: false, expandProgress: 1.0, presentationData: self.presentationData, synchronous: true, transition: transition)
|
||||
|
||||
let padding: CGFloat = 16.0
|
||||
let emptyTitleSize = self.emptyResultsTitleNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - padding * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
@ -2081,6 +2151,61 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
}
|
||||
|
||||
func messageContextActions(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) {
|
||||
let gesture: ContextGesture? = anyRecognizer as? ContextGesture
|
||||
let _ = (chatMediaListPreviewControllerData(context: self.context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: self.navigationController)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] previewData in
|
||||
guard let strongSelf = self else {
|
||||
gesture?.cancel()
|
||||
return
|
||||
}
|
||||
if let previewData = previewData {
|
||||
let context = strongSelf.context
|
||||
let strings = strongSelf.presentationData.strings
|
||||
// let items = chatAvailableMessageActionsImpl(postbox: strongSelf.context.account.postbox, accountPeerId: strongSelf.context.account.peerId, messageIds: [message.id])
|
||||
// |> map { actions -> [ContextMenuItem] in
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
self?.openMessage(message.peers[message.id.peerId]!, message.id)
|
||||
})
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
if let strongSelf = self {
|
||||
strongSelf.forwardMessages(messageIds: [message.id])
|
||||
}
|
||||
})
|
||||
})))
|
||||
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuMore, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
// strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true)
|
||||
// strongSelf.expandTabs()
|
||||
f(.default)
|
||||
})))
|
||||
|
||||
// }
|
||||
|
||||
switch previewData {
|
||||
case let .gallery(gallery):
|
||||
gallery.setHintWillBePresentedInPreviewingContext(true)
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||
strongSelf.presentInGlobalOverlay?(contextController, nil)
|
||||
case .instantPage:
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func messageContextAction(_ message: Message, node: ASDisplayNode?, rect: CGRect?, gesture anyRecognizer: UIGestureRecognizer?) {
|
||||
guard let node = node as? ContextExtractedContentContainingNode else {
|
||||
return
|
||||
@ -2113,7 +2238,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuForward, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
c.dismiss(completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
// strongSelf.forwardMessages(messageIds: Set([message.id]))
|
||||
strongSelf.forwardMessages(messageIds: Set([message.id]))
|
||||
}
|
||||
})
|
||||
})))
|
||||
@ -2123,16 +2248,93 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
})
|
||||
})))
|
||||
|
||||
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
|
||||
self.interaction?.present(controller)
|
||||
// self.controller?.window?.presentInGlobalOverlay(controller)
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuMore, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
|
||||
c.dismiss(completion: {
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.chatInterfaceInteraction.toggleMessagesSelection([message.id], true)
|
||||
// strongSelf.expandTabs()
|
||||
// }
|
||||
})
|
||||
})))
|
||||
|
||||
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(MessageContextExtractedContentSource(sourceNode: node)), items: .single(items), reactionItems: [], recognizer: nil, gesture: gesture)
|
||||
self.presentInGlobalOverlay?(controller, nil)
|
||||
}
|
||||
|
||||
public override func searchTextClearTokens() {
|
||||
self.updateSearchOptions(nil)
|
||||
self.setQuery?(nil, [], self.searchQueryValue ?? "")
|
||||
}
|
||||
|
||||
func forwardMessages(messageIds: Set<MessageId>?) {
|
||||
// if let messageIds = messageIds, !messageIds.isEmpty {
|
||||
// let peerSelectionController = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, filter: [.onlyWriteable, .excludeDisabled]))
|
||||
// peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peerId in
|
||||
// if let strongSelf = self, let _ = peerSelectionController {
|
||||
// if peerId == strongSelf.context.account.peerId {
|
||||
//// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
|
||||
//
|
||||
// let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
|
||||
// return .forward(source: id, grouping: .auto, attributes: [])
|
||||
// })
|
||||
// |> deliverOnMainQueue).start(next: { [weak self] messageIds in
|
||||
// if let strongSelf = self {
|
||||
// let signals: [Signal<Bool, NoError>] = messageIds.compactMap({ id -> Signal<Bool, NoError>? in
|
||||
// guard let id = id else {
|
||||
// return nil
|
||||
// }
|
||||
// return strongSelf.context.account.pendingMessageManager.pendingMessageStatus(id)
|
||||
// |> mapToSignal { status, _ -> Signal<Bool, NoError> in
|
||||
// if status != nil {
|
||||
// return .never()
|
||||
// } else {
|
||||
// return .single(true)
|
||||
// }
|
||||
// }
|
||||
// |> take(1)
|
||||
// })
|
||||
// strongSelf.activeActionDisposable.set((combineLatest(signals)
|
||||
// |> deliverOnMainQueue).start(completed: {
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.controller?.present(OverlayStatusController(theme: strongSelf.presentationData.theme, type: .success), in: .window(.root))
|
||||
// }))
|
||||
// }
|
||||
// })
|
||||
// if let peerSelectionController = peerSelectionController {
|
||||
// peerSelectionController.dismiss()
|
||||
// }
|
||||
// } else {
|
||||
// let _ = (strongSelf.context.account.postbox.transaction({ transaction -> Void in
|
||||
// transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
|
||||
// if let currentState = currentState as? ChatInterfaceState {
|
||||
// return currentState.withUpdatedForwardMessageIds(Array(messageIds))
|
||||
// } else {
|
||||
// return ChatInterfaceState().withUpdatedForwardMessageIds(Array(messageIds))
|
||||
// }
|
||||
// })
|
||||
// }) |> deliverOnMainQueue).start(completed: {
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
|
||||
//
|
||||
// let ready = Promise<Bool>()
|
||||
// strongSelf.activeActionDisposable.set((ready.get() |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { _ in
|
||||
// if let peerSelectionController = peerSelectionController {
|
||||
// peerSelectionController.dismiss()
|
||||
// }
|
||||
// }))
|
||||
//
|
||||
// (strongSelf.controller?.navigationController as? NavigationController)?.replaceTopController(ChatControllerImpl(context: strongSelf.context, chatLocation: .peer(peerId)), animated: false, ready: ready)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// self.controller?.push(peerSelectionController)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private final class MessageContextExtractedContentSource: ContextExtractedContentSource {
|
||||
@ -2153,3 +2355,32 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten
|
||||
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
weak var sourceNode: ASDisplayNode?
|
||||
|
||||
let navigationController: NavigationController? = nil
|
||||
|
||||
let passthroughTouches: Bool = false
|
||||
|
||||
init(controller: ViewController, sourceNode: ASDisplayNode?) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||
let sourceNode = self.sourceNode
|
||||
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
||||
if let sourceNode = sourceNode {
|
||||
return (sourceNode, sourceNode.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func animatedIn() {
|
||||
self.controller.didAppearInContextPreview()
|
||||
}
|
||||
}
|
||||
|
||||
@ -573,12 +573,6 @@ private enum ItemsLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private func getStableId(message: Message) -> Int32 {
|
||||
var hash = message.id.id
|
||||
hash = hash &* 31 &+ message.id.peerId.id
|
||||
return hash
|
||||
}
|
||||
|
||||
final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
enum ContentType {
|
||||
case photoOrVideo
|
||||
@ -612,18 +606,17 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var hiddenMediaDisposable: Disposable?
|
||||
private var mediaItems: [VisualMediaItem] = []
|
||||
private var itemsLayout: ItemsLayout?
|
||||
private var visibleMediaItems: [Int32: VisualMediaItemNode] = [:]
|
||||
|
||||
private var numberOfItemsToRequest: Int = 50
|
||||
private var currentView: MessageHistoryView?
|
||||
private var isRequestingView: Bool = false
|
||||
private var isFirstHistoryView: Bool = true
|
||||
private var visibleMediaItems: [UInt32: VisualMediaItemNode] = [:]
|
||||
private var initialized = false
|
||||
|
||||
private var decelerationAnimator: ConstantDisplayLinkAnimator?
|
||||
|
||||
private var animationTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void) {
|
||||
public var beganInteractiveDragging: (() -> Void)?
|
||||
public var loadMore: (() -> Void)?
|
||||
|
||||
init(context: AccountContext, contentType: ContentType, openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Void, messageContextAction: @escaping (Message, ASDisplayNode?, CGRect?, UIGestureRecognizer?) -> Void) {
|
||||
self.context = context
|
||||
self.contentType = contentType
|
||||
|
||||
@ -638,7 +631,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let _ = openMessage(message, .default)
|
||||
},
|
||||
openMessageContextActions: { [weak self] message, sourceNode, sourceRect, gesture in
|
||||
// self?.chatControllerInteraction.openMessageContextActions(message, sourceNode, sourceRect, gesture)
|
||||
messageContextAction(message, sourceNode, sourceRect, gesture)
|
||||
},
|
||||
toggleSelection: { [weak self] id, value in
|
||||
// self?.chatControllerInteraction.toggleMessagesSelection([id], value)
|
||||
@ -658,8 +651,6 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.addSubnode(self.scrollNode)
|
||||
self.addSubnode(self.floatingHeaderNode)
|
||||
|
||||
self.requestHistoryAroundVisiblePosition()
|
||||
|
||||
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -683,41 +674,25 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.animationTimer?.invalidate()
|
||||
}
|
||||
|
||||
private func requestHistoryAroundVisiblePosition() {
|
||||
// if self.isRequestingView {
|
||||
// return
|
||||
// }
|
||||
// self.isRequestingView = true
|
||||
// self.listDisposable.set((self.context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(self.peerId), index: .upperBound, anchorIndex: .upperBound, count: self.numberOfItemsToRequest, fixedCombinedReadStates: nil, tagMask: tagMaskForType(self.contentType))
|
||||
// |> deliverOnMainQueue).start(next: { [weak self] (view, updateType, _) in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.updateHistory(view: view, updateType: updateType)
|
||||
// strongSelf.isRequestingView = false
|
||||
// }))
|
||||
}
|
||||
|
||||
func updateHistory(entries: [ChatListSearchEntry], updateType: ViewUpdateType) {
|
||||
// self.currentView = view
|
||||
|
||||
switch updateType {
|
||||
case .FillHole:
|
||||
self.requestHistoryAroundVisiblePosition()
|
||||
break
|
||||
default:
|
||||
self.mediaItems.removeAll()
|
||||
for entry in entries.reversed() {
|
||||
if case let .message(message, _, _, _) = entry {
|
||||
for entry in entries {
|
||||
if case let .message(message, _, _, _, _) = entry {
|
||||
self.mediaItems.append(VisualMediaItem(message: message))
|
||||
}
|
||||
}
|
||||
self.itemsLayout = nil
|
||||
|
||||
let wasFirstHistoryView = self.isFirstHistoryView
|
||||
self.isFirstHistoryView = false
|
||||
let wasInitialized = self.initialized
|
||||
self.initialized = true
|
||||
|
||||
if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate)
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .immediate)
|
||||
if !self.didSetReady {
|
||||
self.didSetReady = true
|
||||
self.ready.set(.single(true))
|
||||
@ -749,51 +724,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
itemNode.updateHiddenMedia()
|
||||
}
|
||||
}
|
||||
|
||||
func transferVelocity(_ velocity: CGFloat) {
|
||||
if velocity > 0.0 {
|
||||
//print("transferVelocity \(velocity)")
|
||||
self.decelerationAnimator?.isPaused = true
|
||||
let startTime = CACurrentMediaTime()
|
||||
var currentOffset = self.scrollNode.view.contentOffset
|
||||
let decelerationRate: CGFloat = 0.998
|
||||
self.scrollViewDidEndDragging(self.scrollNode.view, willDecelerate: true)
|
||||
self.decelerationAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let t = CACurrentMediaTime() - startTime
|
||||
var currentVelocity = velocity * 15.0 * CGFloat(pow(Double(decelerationRate), 1000.0 * t))
|
||||
//print("value at \(t) = \(currentVelocity)")
|
||||
currentOffset.y += currentVelocity
|
||||
let maxOffset = strongSelf.scrollNode.view.contentSize.height - strongSelf.scrollNode.bounds.height
|
||||
if currentOffset.y >= maxOffset {
|
||||
currentOffset.y = maxOffset
|
||||
currentVelocity = 0.0
|
||||
}
|
||||
if currentOffset.y < 0.0 {
|
||||
currentOffset.y = 0.0
|
||||
currentVelocity = 0.0
|
||||
}
|
||||
|
||||
var didEnd = false
|
||||
if abs(currentVelocity) < 0.1 {
|
||||
strongSelf.decelerationAnimator?.isPaused = true
|
||||
strongSelf.decelerationAnimator = nil
|
||||
didEnd = true
|
||||
}
|
||||
var contentOffset = strongSelf.scrollNode.view.contentOffset
|
||||
contentOffset.y = floorToScreenPixels(currentOffset.y)
|
||||
strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false)
|
||||
strongSelf.scrollViewDidScroll(strongSelf.scrollNode.view)
|
||||
if didEnd {
|
||||
strongSelf.scrollViewDidEndDecelerating(strongSelf.scrollNode.view)
|
||||
}
|
||||
})
|
||||
self.decelerationAnimator?.isPaused = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func cancelPreviewGestures() {
|
||||
for (_, itemNode) in self.visibleMediaItems {
|
||||
itemNode.cancelPreviewGesture()
|
||||
@ -803,7 +734,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||
for item in self.mediaItems {
|
||||
if item.message.id == messageId {
|
||||
if let itemNode = self.visibleMediaItems[getStableId(message: item.message)] {
|
||||
if let itemNode = self.visibleMediaItems[item.message.stableId] {
|
||||
return itemNode.transitionNode()
|
||||
}
|
||||
break
|
||||
@ -863,17 +794,16 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
self.updateHeaderFlashing(animated: true)
|
||||
|
||||
self.beganInteractiveDragging?()
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if let (size, sideInset, bottomInset, visibleHeight, _, _, presentationData) = self.currentParams {
|
||||
self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: false)
|
||||
|
||||
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0, let currentView = self.currentView, currentView.earlierId != nil {
|
||||
if !self.isRequestingView {
|
||||
self.numberOfItemsToRequest += 50
|
||||
self.requestHistoryAroundVisiblePosition()
|
||||
}
|
||||
if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0 {
|
||||
self.loadMore?()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -908,13 +838,10 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var headerItem: Message?
|
||||
|
||||
var validIds = Set<Int32>()
|
||||
var validIds = Set<UInt32>()
|
||||
if minVisibleIndex <= maxVisibleIndex {
|
||||
for i in minVisibleIndex ... maxVisibleIndex {
|
||||
var stableId = Int32(self.mediaItems[i].message.stableId)
|
||||
if stableId == 0 {
|
||||
stableId = getStableId(message: self.mediaItems[i].message)
|
||||
}
|
||||
let stableId = self.mediaItems[i].message.stableId
|
||||
validIds.insert(stableId)
|
||||
|
||||
let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset)
|
||||
@ -939,7 +866,7 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
itemNode.updateIsVisible(itemFrame.intersects(activeRect))
|
||||
}
|
||||
}
|
||||
var removeKeys: [Int32] = []
|
||||
var removeKeys: [UInt32] = []
|
||||
for (id, _) in self.visibleMediaItems {
|
||||
if !validIds.contains(id) {
|
||||
removeKeys.append(id)
|
||||
@ -673,6 +673,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
additionalTitleInset += badgeSize
|
||||
|
||||
if let arrowButtonImage = arrowButtonImage {
|
||||
additionalTitleInset += arrowButtonImage.size.width
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - additionalTitleInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - badgeSize), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
31
submodules/GalleryData/BUCK
Normal file
31
submodules/GalleryData/BUCK
Normal 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",
|
||||
],
|
||||
)
|
||||
29
submodules/GalleryData/BUILD
Normal file
29
submodules/GalleryData/BUILD
Normal 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",
|
||||
],
|
||||
)
|
||||
296
submodules/GalleryData/Sources/GalleryData.swift
Normal file
296
submodules/GalleryData/Sources/GalleryData.swift
Normal 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)
|
||||
}
|
||||
@ -246,23 +246,20 @@ public final class GalleryControllerPresentationArguments {
|
||||
|
||||
private enum GalleryMessageHistoryView {
|
||||
case view(MessageHistoryView)
|
||||
case single(MessageHistoryEntry)
|
||||
case searchResults(SearchMessagesResult, SearchMessagesState)
|
||||
case entries([MessageHistoryEntry], Bool, Bool)
|
||||
|
||||
var entries: [MessageHistoryEntry] {
|
||||
switch self {
|
||||
case let .view(view):
|
||||
return view.entries
|
||||
case let .single(entry):
|
||||
return [entry]
|
||||
case let .searchResults(result, _):
|
||||
return result.messages.map { MessageHistoryEntry(message: $0, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)) }
|
||||
case let .entries(entries, _, _):
|
||||
return entries
|
||||
}
|
||||
}
|
||||
|
||||
var tagMask: MessageTags? {
|
||||
switch self {
|
||||
case .single, .searchResults:
|
||||
case .entries:
|
||||
return nil
|
||||
case let .view(view):
|
||||
return view.tagMask
|
||||
@ -271,23 +268,19 @@ private enum GalleryMessageHistoryView {
|
||||
|
||||
var hasEarlier: Bool {
|
||||
switch self {
|
||||
case .single:
|
||||
return false
|
||||
case let .entries(_, hasEarlier, _):
|
||||
return hasEarlier
|
||||
case let .view(view):
|
||||
return view.earlierId != nil
|
||||
case let .searchResults(result, _):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
var hasLater: Bool {
|
||||
switch self {
|
||||
case .single:
|
||||
return false
|
||||
case let .entries(_ , _, hasLater):
|
||||
return hasLater
|
||||
case let .view(view):
|
||||
return view.laterId != nil
|
||||
case let .searchResults(result, _):
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -423,15 +416,19 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
message = context.account.postbox.messageAtId(messageId)
|
||||
case let .standaloneMessage(m):
|
||||
message = .single(m)
|
||||
case let .searchResult(result, _, messageId):
|
||||
message = .single(result.messages.first(where: { $0.id == messageId }))
|
||||
case let .custom(messages, messageId, _):
|
||||
message = messages
|
||||
|> take(1)
|
||||
|> map { messages, _, _ in
|
||||
return messages.first(where: { $0.id == messageId })
|
||||
}
|
||||
}
|
||||
|
||||
let messageView = message
|
||||
|> filter({ $0 != nil })
|
||||
|> mapToSignal { message -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
switch source {
|
||||
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
|
||||
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
|
||||
if let tags = tagsForMessage(message!) {
|
||||
let namespaces: MessageIdNamespaces
|
||||
if Namespaces.Message.allScheduled.contains(message!.id.namespace) {
|
||||
@ -440,17 +437,26 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
namespaces = .not(Namespaces.Message.allScheduled)
|
||||
}
|
||||
return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
}
|
||||
} else {
|
||||
return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))))
|
||||
}
|
||||
return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false, false))
|
||||
}
|
||||
case .standaloneMessage:
|
||||
return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))))
|
||||
case let .searchResult(result, state, _):
|
||||
return .single(GalleryMessageHistoryView.searchResults(result, state))
|
||||
return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false ,false))
|
||||
case let .custom(messages, _, _):
|
||||
return messages
|
||||
|> map { messages, totalCount, hasMore in
|
||||
var entries: [MessageHistoryEntry] = []
|
||||
var index = messages.count - 1
|
||||
for message in messages.reversed() {
|
||||
entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))
|
||||
index -= 1
|
||||
}
|
||||
return GalleryMessageHistoryView.entries(entries, false, hasMore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> take(1)
|
||||
@ -486,7 +492,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
centralEntryStableId = message.stableId
|
||||
break loop
|
||||
}
|
||||
case let .searchResult(result, state, messageId):
|
||||
case let .custom(_, messageId, _):
|
||||
if message.id == messageId {
|
||||
centralEntryStableId = message.stableId
|
||||
break loop
|
||||
@ -995,70 +1001,74 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
}
|
||||
|
||||
switch strongSelf.source {
|
||||
case let .peerMessagesAtId(initialMessageId, chatLocation, chatLocationContextHolder):
|
||||
var reloadAroundIndex: MessageIndex?
|
||||
if index <= 2 && strongSelf.hasLeftEntries {
|
||||
reloadAroundIndex = strongSelf.entries.first?.index
|
||||
} else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
|
||||
reloadAroundIndex = strongSelf.entries.last?.index
|
||||
}
|
||||
if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask {
|
||||
let namespaces: MessageIdNamespaces
|
||||
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
|
||||
namespaces = .just(Namespaces.Message.allScheduled)
|
||||
} else {
|
||||
namespaces = .not(Namespaces.Message.allScheduled)
|
||||
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
|
||||
var reloadAroundIndex: MessageIndex?
|
||||
if index <= 2 && strongSelf.hasLeftEntries {
|
||||
reloadAroundIndex = strongSelf.entries.first?.index
|
||||
} else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
|
||||
reloadAroundIndex = strongSelf.entries.last?.index
|
||||
}
|
||||
let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
}
|
||||
|> take(1)
|
||||
|
||||
strongSelf.updateVisibleDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { view in
|
||||
guard let strongSelf = self, let view = view else {
|
||||
return
|
||||
}
|
||||
|
||||
let entries = view.entries
|
||||
|
||||
if strongSelf.invertItemOrder {
|
||||
strongSelf.entries = entries.reversed()
|
||||
strongSelf.hasLeftEntries = view.hasLater
|
||||
strongSelf.hasRightEntries = view.hasEarlier
|
||||
if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask {
|
||||
let namespaces: MessageIdNamespaces
|
||||
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
|
||||
namespaces = .just(Namespaces.Message.allScheduled)
|
||||
} else {
|
||||
strongSelf.entries = entries
|
||||
strongSelf.hasLeftEntries = view.hasEarlier
|
||||
strongSelf.hasRightEntries = view.hasLater
|
||||
namespaces = .not(Namespaces.Message.allScheduled)
|
||||
}
|
||||
if strongSelf.isViewLoaded {
|
||||
var items: [GalleryItem] = []
|
||||
var centralItemIndex: Int?
|
||||
for entry in strongSelf.entries {
|
||||
var isCentral = false
|
||||
if entry.message.stableId == strongSelf.centralEntryStableId {
|
||||
isCentral = true
|
||||
}
|
||||
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
}) {
|
||||
if isCentral {
|
||||
centralItemIndex = items.count
|
||||
}
|
||||
items.append(item)
|
||||
}
|
||||
let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation])
|
||||
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
|
||||
let mapped = GalleryMessageHistoryView.view(view)
|
||||
return .single(mapped)
|
||||
}
|
||||
|
||||
strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex)
|
||||
}
|
||||
}))
|
||||
|> take(1)
|
||||
|
||||
strongSelf.updateVisibleDisposable.set((signal
|
||||
|> deliverOnMainQueue).start(next: { view in
|
||||
guard let strongSelf = self, let view = view else {
|
||||
return
|
||||
}
|
||||
|
||||
let entries = view.entries
|
||||
|
||||
if strongSelf.invertItemOrder {
|
||||
strongSelf.entries = entries.reversed()
|
||||
strongSelf.hasLeftEntries = view.hasLater
|
||||
strongSelf.hasRightEntries = view.hasEarlier
|
||||
} else {
|
||||
strongSelf.entries = entries
|
||||
strongSelf.hasLeftEntries = view.hasEarlier
|
||||
strongSelf.hasRightEntries = view.hasLater
|
||||
}
|
||||
if strongSelf.isViewLoaded {
|
||||
var items: [GalleryItem] = []
|
||||
var centralItemIndex: Int?
|
||||
for entry in strongSelf.entries {
|
||||
var isCentral = false
|
||||
if entry.message.stableId == strongSelf.centralEntryStableId {
|
||||
isCentral = true
|
||||
}
|
||||
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
}) {
|
||||
if isCentral {
|
||||
centralItemIndex = items.count
|
||||
}
|
||||
items.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex)
|
||||
}
|
||||
}))
|
||||
}
|
||||
default:
|
||||
break
|
||||
case let .custom(_, _, loadMore):
|
||||
if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
|
||||
loadMore?()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if strongSelf.didSetReady {
|
||||
|
||||
@ -45,7 +45,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
let search = searchMessages(account: context.account, location: location, query: query, state: nil)
|
||||
let foundMessages: Signal<[ChatListSearchEntry], NoError> = search
|
||||
|> map { result, _ in
|
||||
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData) })
|
||||
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData, result.totalCount) })
|
||||
}
|
||||
let interaction = ChatListNodeInteraction(activateSearch: {
|
||||
}, peerSelected: { _, _ in
|
||||
|
||||
@ -574,7 +574,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
|
||||
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - dateNodeLayout.size.width, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
|
||||
@ -425,7 +425,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
|
||||
|
||||
let authorText = NSAttributedString(string: authorString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
|
||||
|
||||
let (authorNodeLayout, authorNodeApply) = authorNodeMakeLayout(TextNodeLayoutArguments(attributedString: authorText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 12.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (authorNodeLayout, authorNodeApply) = authorNodeMakeLayout(TextNodeLayoutArguments(attributedString: authorText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var contentHeight = 9.0 + titleNodeLayout.size.height + 10.0 + descriptionNodeLayout.size.height + linkNodeLayout.size.height
|
||||
if item.isGlobalSearchResult {
|
||||
|
||||
@ -10,6 +10,7 @@ static_library(
|
||||
"//submodules/SyncCore:SyncCore#shared",
|
||||
"//submodules/Postbox:Postbox#shared",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
||||
@ -11,6 +11,7 @@ swift_library(
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -156,6 +156,11 @@ public struct MessageHistoryEntryLocation: Equatable {
|
||||
public let index: Int
|
||||
public let count: Int
|
||||
|
||||
public init(index: Int, count: Int) {
|
||||
self.index = index
|
||||
self.count = count
|
||||
}
|
||||
|
||||
var predecessor: MessageHistoryEntryLocation? {
|
||||
if index == 0 {
|
||||
return nil
|
||||
|
||||
@ -231,7 +231,7 @@ private class SearchBarTextField: UITextField {
|
||||
}
|
||||
|
||||
if !tokenSizes.isEmpty {
|
||||
leftOffset -= 4.0
|
||||
leftOffset -= 6.0
|
||||
}
|
||||
|
||||
self.tokensWidth = leftOffset
|
||||
@ -300,7 +300,7 @@ private class SearchBarTextField: UITextField {
|
||||
if bounds.size.width.isZero {
|
||||
return CGRect(origin: CGPoint(), size: CGSize())
|
||||
}
|
||||
var rect = bounds.insetBy(dx: 4.0, dy: 4.0)
|
||||
var rect = bounds.insetBy(dx: 7.0, dy: 4.0)
|
||||
rect.origin.y += 1.0
|
||||
|
||||
let prefixSize = self.measurePrefixLabel.updateLayout(CGSize(width: floor(bounds.size.width * 0.7), height: bounds.size.height))
|
||||
@ -682,16 +682,16 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
||||
|
||||
if let iconImage = self.iconNode.image {
|
||||
let iconSize = iconImage.size
|
||||
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 8.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 5.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - iconSize.height) / 2.0) - UIScreenPixel), size: iconSize))
|
||||
}
|
||||
|
||||
if let activityIndicator = self.activityIndicator {
|
||||
let indicatorSize = activityIndicator.measure(CGSize(width: 32.0, height: 32.0))
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 7.0, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0)), size: indicatorSize))
|
||||
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.minX + 9.0 + UIScreenPixel, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - indicatorSize.height) / 2.0) - 1.0), size: indicatorSize))
|
||||
}
|
||||
|
||||
let clearSize = self.clearButton.measure(CGSize(width: 100.0, height: 100.0))
|
||||
transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 8.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize))
|
||||
transition.updateFrame(node: self.clearButton, frame: CGRect(origin: CGPoint(x: textBackgroundFrame.maxX - 6.0 - clearSize.width, y: textBackgroundFrame.minY + floor((textBackgroundFrame.size.height - clearSize.height) / 2.0)), size: clearSize))
|
||||
|
||||
self.textField.frame = textFrame
|
||||
}
|
||||
@ -729,7 +729,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
||||
self.textBackgroundNode.layer.animateFrame(from: initialTextBackgroundFrame, to: self.textBackgroundNode.frame, duration: duration, timingFunction: timingFunction)
|
||||
|
||||
let textFieldFrame = self.textField.frame
|
||||
let initialLabelNodeFrame = CGRect(origin: node.labelNode.frame.offsetBy(dx: initialTextBackgroundFrame.origin.x - 4.0, dy: initialTextBackgroundFrame.origin.y - 8.0).origin, size: textFieldFrame.size)
|
||||
let initialLabelNodeFrame = CGRect(origin: node.labelNode.frame.offsetBy(dx: initialTextBackgroundFrame.origin.x - 7.0, dy: initialTextBackgroundFrame.origin.y - 8.0).origin, size: textFieldFrame.size)
|
||||
self.textField.layer.animateFrame(from: initialLabelNodeFrame, to: self.textField.frame, duration: duration, timingFunction: timingFunction)
|
||||
|
||||
let iconFrame = self.iconNode.frame
|
||||
@ -808,7 +808,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
||||
transitionBackgroundNode.layer.animateFrame(from: self.textBackgroundNode.frame, to: targetTextBackgroundFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
||||
|
||||
let textFieldFrame = self.textField.frame
|
||||
let targetLabelNodeFrame = CGRect(origin: CGPoint(x: node.labelNode.frame.minX + targetTextBackgroundFrame.origin.x - 4.0, y: targetTextBackgroundFrame.minY + floorToScreenPixels((targetTextBackgroundFrame.size.height - textFieldFrame.size.height) / 2.0) - UIScreenPixel), size: textFieldFrame.size)
|
||||
let targetLabelNodeFrame = CGRect(origin: CGPoint(x: node.labelNode.frame.minX + targetTextBackgroundFrame.origin.x - 7.0, y: targetTextBackgroundFrame.minY + floorToScreenPixels((targetTextBackgroundFrame.size.height - textFieldFrame.size.height) / 2.0) - UIScreenPixel), size: textFieldFrame.size)
|
||||
self.textField.layer.animateFrame(from: self.textField.frame, to: targetLabelNodeFrame, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
if let snapshot = node.labelNode.layer.snapshotContentTree() {
|
||||
|
||||
@ -143,7 +143,7 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
|
||||
|
||||
var iconSize = CGSize()
|
||||
var totalWidth = labelLayoutResult.size.width
|
||||
let spacing: CGFloat = 8.0
|
||||
let spacing: CGFloat = 6.0
|
||||
|
||||
if let iconImage = strongSelf.iconNode.image {
|
||||
iconSize = iconImage.size
|
||||
|
||||
@ -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 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -209,6 +209,7 @@ framework(
|
||||
"//submodules/ListMessageItem:ListMessageItem",
|
||||
"//submodules/FileMediaResourceStatus:FileMediaResourceStatus",
|
||||
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
|
||||
"//submodules/GalleryData:GalleryData",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
|
||||
@ -204,6 +204,7 @@ swift_library(
|
||||
"//submodules/ListMessageItem:ListMessageItem",
|
||||
"//submodules/FileMediaResourceStatus:FileMediaResourceStatus",
|
||||
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
|
||||
"//submodules/GalleryData:GalleryData",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Clear.pdf"
|
||||
"filename" : "ic_search_clear.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@ -1,22 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "IconSearch@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "IconSearch@3x.png",
|
||||
"scale" : "3x"
|
||||
"filename" : "ic_search_search.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 964 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
BIN
submodules/TelegramUI/Images.xcassets/Components/Search Bar/Loupe.imageset/ic_search_search.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Components/Search Bar/Loupe.imageset/ic_search_search.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -60,6 +60,8 @@ import UrlWhitelist
|
||||
import TelegramIntents
|
||||
import TooltipUI
|
||||
import StatisticsUI
|
||||
import MediaResources
|
||||
import GalleryData
|
||||
|
||||
extension ChatLocation {
|
||||
var peerId: PeerId {
|
||||
|
||||
@ -15,6 +15,7 @@ import UrlEscaping
|
||||
import PhotoResources
|
||||
import WebsiteType
|
||||
import ChatMessageInteractiveMediaBadge
|
||||
import GalleryData
|
||||
|
||||
private let buttonFont = Font.semibold(13.0)
|
||||
|
||||
|
||||
@ -12,65 +12,7 @@ import AccountContext
|
||||
import WebsiteType
|
||||
import InstantPageUI
|
||||
import UrlHandling
|
||||
|
||||
func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] {
|
||||
var result: [InstantPageGalleryEntry] = []
|
||||
var counter: Int = 0
|
||||
|
||||
for block in page.blocks {
|
||||
result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter))
|
||||
}
|
||||
|
||||
var found = false
|
||||
for item in result {
|
||||
if item.media.media.id == galleryMedia.id {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0)
|
||||
}
|
||||
|
||||
for i in 0 ..< result.count {
|
||||
let item = result[i]
|
||||
result[i] = InstantPageGalleryEntry(index: Int32(i), pageId: item.pageId, media: item.media, caption: item.caption, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] {
|
||||
switch block {
|
||||
case let .image(id, caption, _, _):
|
||||
if let m = media[id] {
|
||||
let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))]
|
||||
counter += 1
|
||||
return result
|
||||
}
|
||||
case let .video(id, caption, _, _):
|
||||
if let m = media[id] {
|
||||
let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))]
|
||||
counter += 1
|
||||
return result
|
||||
}
|
||||
case let .collage(items, _):
|
||||
var result: [InstantPageGalleryEntry] = []
|
||||
for item in items {
|
||||
result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter))
|
||||
}
|
||||
return result
|
||||
case let .slideshow(items, _):
|
||||
var result: [InstantPageGalleryEntry] = []
|
||||
for item in items {
|
||||
result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter))
|
||||
}
|
||||
return result
|
||||
default:
|
||||
break
|
||||
}
|
||||
return []
|
||||
}
|
||||
import GalleryData
|
||||
|
||||
private let titleFont: UIFont = Font.semibold(15.0)
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import WebSearchUI
|
||||
import InstantPageCache
|
||||
import SettingsUI
|
||||
import WallpaperResources
|
||||
import MediaResources
|
||||
import LocationUI
|
||||
|
||||
private var telegramUIDeclaredEncodables: Void = {
|
||||
|
||||
@ -13,6 +13,7 @@ import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import TelegramUniversalVideoContent
|
||||
import DeviceProximity
|
||||
import MediaResources
|
||||
|
||||
enum SharedMediaPlayerGroup: Int {
|
||||
case music = 0
|
||||
|
||||
@ -22,226 +22,7 @@ import PresentationDataUtils
|
||||
import ShareController
|
||||
import UndoUI
|
||||
import WebsiteType
|
||||
|
||||
private enum ChatMessageGalleryControllerData {
|
||||
case url(String)
|
||||
case pass(TelegramMediaFile)
|
||||
case instantPage(InstantPageGalleryController, Int, Media)
|
||||
case map(TelegramMediaMap)
|
||||
case stickerPack(StickerPackReference)
|
||||
case audio(TelegramMediaFile)
|
||||
case document(TelegramMediaFile, Bool)
|
||||
case gallery(Signal<GalleryController, NoError>)
|
||||
case secretGallery(SecretMediaPreviewController)
|
||||
case chatAvatars(AvatarGalleryController, Media)
|
||||
case theme(TelegramMediaFile)
|
||||
case other(Media)
|
||||
}
|
||||
|
||||
private func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? {
|
||||
var galleryMedia: Media?
|
||||
var otherMedia: Media?
|
||||
var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])?
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
case let .photoUpdated(image):
|
||||
if let peer = messageMainPeer(message), let image = image {
|
||||
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")])
|
||||
let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in
|
||||
|
||||
})
|
||||
return .chatAvatars(galleryController, image)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
galleryMedia = file
|
||||
} else if let image = media as? TelegramMediaImage {
|
||||
galleryMedia = image
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
if let file = content.file {
|
||||
galleryMedia = file
|
||||
} else if let image = content.image {
|
||||
if case .link = mode {
|
||||
} else if ["photo", "document", "video", "gif", "telegram_album"].contains(content.type) {
|
||||
galleryMedia = image
|
||||
}
|
||||
}
|
||||
|
||||
if let instantPage = content.instantPage, let galleryMedia = galleryMedia {
|
||||
switch instantPageType(of: content) {
|
||||
case .album:
|
||||
let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia)
|
||||
if medias.count > 1 {
|
||||
instantPageMedia = (webpage, medias)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let mapMedia = media as? TelegramMediaMap {
|
||||
galleryMedia = mapMedia
|
||||
} else if let contactMedia = media as? TelegramMediaContact {
|
||||
otherMedia = contactMedia
|
||||
}
|
||||
}
|
||||
|
||||
var stream = false
|
||||
var autoplayingVideo = false
|
||||
var landscape = false
|
||||
var timecode: Double? = nil
|
||||
|
||||
switch mode {
|
||||
case .stream:
|
||||
stream = true
|
||||
case .automaticPlayback:
|
||||
autoplayingVideo = true
|
||||
case .landscape:
|
||||
autoplayingVideo = true
|
||||
landscape = true
|
||||
case let .timecode(time):
|
||||
timecode = time
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia {
|
||||
var centralIndex: Int = 0
|
||||
for i in 0 ..< instantPageMedia.count {
|
||||
if instantPageMedia[i].media.media.id == galleryMedia.id {
|
||||
centralIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in
|
||||
if let navigationController = navigationController {
|
||||
navigationController.replaceTopController(controller, animated: false, ready: ready)
|
||||
}
|
||||
}, baseNavigationController: navigationController)
|
||||
return .instantPage(gallery, centralIndex, galleryMedia)
|
||||
} else if let galleryMedia = galleryMedia {
|
||||
if let mapMedia = galleryMedia as? TelegramMediaMap {
|
||||
return .map(mapMedia)
|
||||
} else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) {
|
||||
for attribute in file.attributes {
|
||||
if case let .Sticker(_, reference, _) = attribute {
|
||||
if let reference = reference {
|
||||
return .stickerPack(reference)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let file = galleryMedia as? TelegramMediaFile, file.isAnimatedSticker {
|
||||
return nil
|
||||
} else if let file = galleryMedia as? TelegramMediaFile, file.isMusic || file.isVoice || file.isInstantVideo {
|
||||
return .audio(file)
|
||||
} else if let file = galleryMedia as? TelegramMediaFile, file.mimeType == "application/vnd.apple.pkpass" || (file.fileName != nil && file.fileName!.lowercased().hasSuffix(".pkpass")) {
|
||||
return .pass(file)
|
||||
} else {
|
||||
if let file = galleryMedia as? TelegramMediaFile {
|
||||
if let fileName = file.fileName {
|
||||
let ext = (fileName as NSString).pathExtension.lowercased()
|
||||
if ext == "tgios-theme" {
|
||||
return .theme(file)
|
||||
} else if ext == "wav" || ext == "opus" {
|
||||
return .audio(file)
|
||||
} else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 {
|
||||
if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let composition = LOTComposition(filePath: path), composition.timeDuration > 0.0 {
|
||||
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? ChatLocation.peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
|
||||
navigationController?.replaceTopController(controller, animated: false, ready: ready)
|
||||
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
|
||||
return .gallery(.single(gallery))
|
||||
}
|
||||
}
|
||||
|
||||
if ext == "mkv" {
|
||||
return .document(file, true)
|
||||
}
|
||||
}
|
||||
|
||||
if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") {
|
||||
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
|
||||
navigationController?.replaceTopController(controller, animated: false, ready: ready)
|
||||
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
|
||||
return .gallery(.single(gallery))
|
||||
}
|
||||
|
||||
if !file.isVideo {
|
||||
return .document(file, false)
|
||||
}
|
||||
}
|
||||
|
||||
if message.containsSecretMedia {
|
||||
let gallery = SecretMediaPreviewController(context: context, messageId: message.id)
|
||||
return .secretGallery(gallery)
|
||||
} else {
|
||||
let startTimecode: Signal<Double?, NoError>
|
||||
if let timecode = timecode {
|
||||
startTimecode = .single(timecode)
|
||||
} else {
|
||||
startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id)
|
||||
|> map { state in
|
||||
return state?.timestamp
|
||||
}
|
||||
}
|
||||
|
||||
return .gallery(startTimecode
|
||||
|> deliverOnMainQueue
|
||||
|> map { timecode in
|
||||
let gallery = GalleryController(context: context, source: source ?? (standalone ? .standaloneMessage(message) : .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil))), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
|
||||
navigationController?.replaceTopController(controller, animated: false, ready: ready)
|
||||
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
|
||||
gallery.temporaryDoNotWaitForReady = autoplayingVideo
|
||||
return gallery
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if let otherMedia = otherMedia {
|
||||
return .other(otherMedia)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatMessagePreviewControllerData {
|
||||
case instantPage(InstantPageGalleryController, Int, Media)
|
||||
case gallery(GalleryController)
|
||||
}
|
||||
|
||||
func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? {
|
||||
if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) {
|
||||
switch mediaData {
|
||||
case let .gallery(gallery):
|
||||
break
|
||||
case let .instantPage(gallery, centralIndex, galleryMedia):
|
||||
return .instantPage(gallery, centralIndex, galleryMedia)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal<ChatMessagePreviewControllerData?, NoError> {
|
||||
if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) {
|
||||
switch mediaData {
|
||||
case let .gallery(gallery):
|
||||
return gallery
|
||||
|> map { gallery in
|
||||
return .gallery(gallery)
|
||||
}
|
||||
case let .instantPage(gallery, centralIndex, galleryMedia):
|
||||
return .single(.instantPage(gallery, centralIndex, galleryMedia))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
import GalleryData
|
||||
|
||||
func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
if let mediaData = chatMessageGalleryControllerData(context: params.context, chatLocation: params.chatLocation, chatLocationContextHolder: params.chatLocationContextHolder, message: params.message, navigationController: params.navigationController, standalone: params.standalone, reverseMessageGalleryOrder: params.reverseMessageGalleryOrder, mode: params.mode, source: params.gallerySource, synchronousLoad: false, actionInteraction: params.actionInteraction) {
|
||||
|
||||
@ -50,6 +50,7 @@ import TelegramNotices
|
||||
import SaveToCameraRoll
|
||||
import PeerInfoUI
|
||||
import ListMessageItem
|
||||
import GalleryData
|
||||
|
||||
protocol PeerInfoScreenItem: class {
|
||||
var id: AnyHashable { get }
|
||||
|
||||
@ -224,6 +224,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
requestOpenMessageFromSearch(peer, messageId)
|
||||
}
|
||||
}, addContact: nil, peerContextAction: nil, present: { _, _ in
|
||||
}, presentInGlobalOverlay: { _, _ in
|
||||
}, navigationController: nil), cancel: { [weak self] in
|
||||
if let requestDeactivateSearch = self?.requestDeactivateSearch {
|
||||
requestDeactivateSearch()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user