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