mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-07 16:11:13 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
2f70ec1ca2
@ -443,6 +443,7 @@
|
|||||||
"DialogList.TabTitle" = "Chats";
|
"DialogList.TabTitle" = "Chats";
|
||||||
"DialogList.Title" = "Chats";
|
"DialogList.Title" = "Chats";
|
||||||
"DialogList.SearchLabel" = "Search for messages or users";
|
"DialogList.SearchLabel" = "Search for messages or users";
|
||||||
|
"DialogList.SearchLabelCompact" = "Search";
|
||||||
"DialogList.NoMessagesTitle" = "You have no conversations yet";
|
"DialogList.NoMessagesTitle" = "You have no conversations yet";
|
||||||
"DialogList.NoMessagesText" = "Start messaging by pressing the pencil button in the top right corner or go to the Contacts section.";
|
"DialogList.NoMessagesText" = "Start messaging by pressing the pencil button in the top right corner or go to the Contacts section.";
|
||||||
"DialogList.SingleTypingSuffix" = "%@ is typing";
|
"DialogList.SingleTypingSuffix" = "%@ is typing";
|
||||||
@ -7304,3 +7305,24 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Attachment.OpenSettings" = "Go to Settings";
|
"Attachment.OpenSettings" = "Go to Settings";
|
||||||
"Attachment.OpenCamera" = "Open Camera";
|
"Attachment.OpenCamera" = "Open Camera";
|
||||||
|
|
||||||
|
"Attachment.DeselectedPhotos_1" = "%@ photo deselected";
|
||||||
|
"Attachment.DeselectedPhotos_2" = "%@ photos deselected";
|
||||||
|
"Attachment.DeselectedPhotos_3_10" = "%@ photos deselected";
|
||||||
|
"Attachment.DeselectedPhotos_any" = "%@ photos deselected";
|
||||||
|
"Attachment.DeselectedPhotos_many" = "%@ photos deselected";
|
||||||
|
"Attachment.DeselectedPhotos_0" = "%@ photos deselected";
|
||||||
|
|
||||||
|
"Attachment.DeselectedVideos_1" = "%@ video deselected";
|
||||||
|
"Attachment.DeselectedVideos_2" = "%@ videos deselected";
|
||||||
|
"Attachment.DeselectedVideos_3_10" = "%@ videos deselected";
|
||||||
|
"Attachment.DeselectedVideos_any" = "%@ videos deselected";
|
||||||
|
"Attachment.DeselectedVideos_many" = "%@ videos deselected";
|
||||||
|
"Attachment.DeselectedVideos_0" = "%@ videos deselected";
|
||||||
|
|
||||||
|
"Attachment.DeselectedItems_1" = "%@ item deselected";
|
||||||
|
"Attachment.DeselectedItems_2" = "%@ items deselected";
|
||||||
|
"Attachment.DeselectedItems_3_10" = "%@ items deselected";
|
||||||
|
"Attachment.DeselectedItems_any" = "%@ items deselected";
|
||||||
|
"Attachment.DeselectedItems_many" = "%@ items deselected";
|
||||||
|
"Attachment.DeselectedItems_0" = "%@ items deselected";
|
||||||
|
@ -116,10 +116,10 @@ public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayer
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? {
|
public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? {
|
||||||
if isGlobalSearch {
|
if isGlobalSearch && !isDownloadList {
|
||||||
return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
|
return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
|
||||||
} else if isRecentActions {
|
} else if isRecentActions && !isDownloadList {
|
||||||
return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
|
return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
|
||||||
} else {
|
} else {
|
||||||
return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
|
return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id, messageIndex: message.index))
|
||||||
|
@ -280,8 +280,8 @@ private final class AnimatedStickerDirectFrameSourceCache {
|
|||||||
self.storeQueue = sharedStoreQueue
|
self.storeQueue = sharedStoreQueue
|
||||||
|
|
||||||
self.frameCount = frameCount
|
self.frameCount = frameCount
|
||||||
self.width = alignUp(size: width, align: 8)
|
self.width = width// alignUp(size: width, align: 8)
|
||||||
self.height = alignUp(size: height, align: 8)
|
self.height = height//alignUp(size: height, align: 8)
|
||||||
self.useHardware = useHardware
|
self.useHardware = useHardware
|
||||||
|
|
||||||
let suffix : String
|
let suffix : String
|
||||||
|
@ -23,10 +23,15 @@ public enum AttachmentButtonType: Equatable {
|
|||||||
public protocol AttachmentContainable: ViewController {
|
public protocol AttachmentContainable: ViewController {
|
||||||
var requestAttachmentMenuExpansion: () -> Void { get set }
|
var requestAttachmentMenuExpansion: () -> Void { get set }
|
||||||
|
|
||||||
|
func resetForReuse()
|
||||||
func prepareForReuse()
|
func prepareForReuse()
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension AttachmentContainable {
|
public extension AttachmentContainable {
|
||||||
|
func resetForReuse() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func prepareForReuse() {
|
func prepareForReuse() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ private final class AttachButtonComponent: CombinedComponent {
|
|||||||
availableSize: CGSize(width: 30.0, height: 30.0),
|
availableSize: CGSize(width: 30.0, height: 30.0),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
let title = title.update(
|
let title = title.update(
|
||||||
component: Text(
|
component: Text(
|
||||||
text: name,
|
text: name,
|
||||||
|
@ -116,7 +116,8 @@ public class ChatListSearchItemNode: ListViewItemNode {
|
|||||||
let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor
|
let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor
|
||||||
let placeholderColor = item.theme.list.itemSecondaryTextColor
|
let placeholderColor = item.theme.list.itemSecondaryTextColor
|
||||||
|
|
||||||
let (_, searchBarApply) = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: placeholderColor), CGSize(width: baseWidth - 20.0, height: 36.0), 1.0, placeholderColor, nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
|
let placeholderString = NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: placeholderColor)
|
||||||
|
let (_, searchBarApply) = searchBarNodeLayout(placeholderString, placeholderString, CGSize(width: baseWidth - 20.0, height: 36.0), 1.0, placeholderColor, nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 54.0), insets: UIEdgeInsets())
|
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 54.0), insets: UIEdgeInsets())
|
||||||
|
|
||||||
|
@ -469,7 +469,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
})
|
})
|
||||||
|
|
||||||
if !previewing {
|
if !previewing {
|
||||||
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel, activate: { [weak self] in
|
self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel, compactPlaceholder: self.presentationData.strings.DialogList_SearchLabelCompact, activate: { [weak self] in
|
||||||
self?.activateSearch()
|
self?.activateSearch()
|
||||||
})
|
})
|
||||||
self.searchContentNode?.updateExpansionProgress(0.0)
|
self.searchContentNode?.updateExpansionProgress(0.0)
|
||||||
@ -601,9 +601,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let stateSignal: Signal<State, NoError> = (combineLatest(queue: .mainQueue(), entriesWithFetchStatuses, recentDownloadItems(postbox: context.account.postbox))
|
let displayRecentDownloads = context.account.postbox.tailChatListView(groupId: .root, filterPredicate: nil, count: 11, summaryComponents: ChatListEntrySummaryComponents(components: [:]))
|
||||||
|> map { entries, recentDownloadItems -> State in
|
|> map { view -> Bool in
|
||||||
if !entries.isEmpty {
|
return view.0.entries.count >= 10
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|
||||||
|
let stateSignal: Signal<State, NoError> = (combineLatest(queue: .mainQueue(), entriesWithFetchStatuses, recentDownloadItems(postbox: context.account.postbox), displayRecentDownloads)
|
||||||
|
|> map { entries, recentDownloadItems, displayRecentDownloads -> State in
|
||||||
|
if !entries.isEmpty && displayRecentDownloads {
|
||||||
var totalBytes = 0.0
|
var totalBytes = 0.0
|
||||||
var totalProgressInBytes = 0.0
|
var totalProgressInBytes = 0.0
|
||||||
for (entry, progress) in entries {
|
for (entry, progress) in entries {
|
||||||
@ -799,7 +805,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
self.navigationItem.backBarButtonItem = backBarButtonItem
|
self.navigationItem.backBarButtonItem = backBarButtonItem
|
||||||
}
|
}
|
||||||
|
|
||||||
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel)
|
self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: self.presentationData.strings.DialogList_SearchLabel, compactPlaceholder: self.presentationData.strings.DialogList_SearchLabelCompact)
|
||||||
let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing
|
let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing
|
||||||
let editItem: UIBarButtonItem
|
let editItem: UIBarButtonItem
|
||||||
if editing {
|
if editing {
|
||||||
@ -2088,7 +2094,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
activate()
|
activate(filter != .downloads)
|
||||||
|
|
||||||
if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
if let searchContentNode = strongSelf.chatListDisplayNode.searchDisplayController?.contentNode as? ChatListSearchContainerNode {
|
||||||
searchContentNode.search(filter: filter, query: query)
|
searchContentNode.search(filter: filter, query: query)
|
||||||
|
@ -1193,7 +1193,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, () -> Void)? {
|
func activateSearch(placeholderNode: SearchBarPlaceholderNode, displaySearchFilters: Bool, initialFilter: ChatListSearchFilter, navigationController: NavigationController?) -> (ASDisplayNode, (Bool) -> Void)? {
|
||||||
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1226,7 +1226,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
})
|
})
|
||||||
self.containerNode.accessibilityElementsHidden = true
|
self.containerNode.accessibilityElementsHidden = true
|
||||||
|
|
||||||
return (contentNode.filterContainerNode, { [weak self] in
|
return (contentNode.filterContainerNode, { [weak self] focus in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1239,7 +1239,7 @@ final class ChatListControllerNode: ASDisplayNode {
|
|||||||
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
|
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, placeholder: placeholderNode)
|
}, placeholder: placeholderNode, focus: focus)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,7 +870,28 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
|
|
||||||
if !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty {
|
if isCachedValue {
|
||||||
|
if !items.isEmpty {
|
||||||
|
items.append(.separator)
|
||||||
|
}
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuSelect, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||||
|
c.dismiss(completion: {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.dismissInput()
|
||||||
|
|
||||||
|
strongSelf.updateState { state in
|
||||||
|
return state.withUpdatedSelectedMessageIds([message.id])
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||||
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty {
|
||||||
if !items.isEmpty {
|
if !items.isEmpty {
|
||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
}
|
}
|
||||||
@ -905,7 +926,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
@ -1061,34 +1082,53 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deleteMessages(messageIds: Set<EngineMessage.Id>?) {
|
func deleteMessages(messageIds: Set<EngineMessage.Id>?) {
|
||||||
|
let isDownloads = self.paneContainerNode.currentPaneKey == .downloads
|
||||||
|
|
||||||
if let messageIds = messageIds ?? self.stateValue.selectedMessageIds, !messageIds.isEmpty {
|
if let messageIds = messageIds ?? self.stateValue.selectedMessageIds, !messageIds.isEmpty {
|
||||||
let (peers, messages) = self.currentMessages
|
if isDownloads {
|
||||||
let _ = (self.context.account.postbox.transaction { transaction -> Void in
|
let _ = (self.context.account.postbox.transaction { transaction -> [Message] in
|
||||||
for id in messageIds {
|
return messageIds.compactMap(transaction.getMessage)
|
||||||
if transaction.getMessage(id) == nil, let message = messages[id] {
|
|
||||||
storeMessageFromSearch(transaction: transaction, message: message._asMessage())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).start()
|
|> deliverOnMainQueue).start(next: { [weak self] messages in
|
||||||
|
guard let strongSelf = self else {
|
||||||
self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds, messages: messages, peers: peers)
|
return
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] actions in
|
}
|
||||||
if let strongSelf = self, !actions.options.isEmpty {
|
|
||||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
|
||||||
var items: [ActionSheetItem] = []
|
|
||||||
let personalPeerName: String? = nil
|
|
||||||
|
|
||||||
if actions.options.contains(.deleteGlobally) {
|
let title: String
|
||||||
let globalTitle: String
|
let text: String
|
||||||
if let personalPeerName = personalPeerName {
|
|
||||||
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string
|
//TODO:localize
|
||||||
} else {
|
if messageIds.count == 1 {
|
||||||
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone
|
title = "Remove Document?"
|
||||||
}
|
text = "Are you sure you want to remove this\ndocument from Downloads?\nIt will be deleted from your disk, but\nwill remain accessible in the cloud.";
|
||||||
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
|
} else {
|
||||||
actionSheet?.dismissAnimated()
|
title = "Remove \(messages.count) Documents?"
|
||||||
if let strongSelf = self {
|
text = "Do you want to remove these\n\(messages.count) documents from Downloads?\nThey will be deleted from your disk,\nbut will remain accessible\nin the cloud."
|
||||||
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start()
|
}
|
||||||
|
|
||||||
|
strongSelf.present?(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||||
|
}),
|
||||||
|
//TODO:localize
|
||||||
|
TextAlertAction(type: .defaultAction, title: "Remove", action: {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var resourceIds = Set<MediaResourceId>()
|
||||||
|
for message in messages {
|
||||||
|
for media in message.media {
|
||||||
|
if let file = media as? TelegramMediaFile {
|
||||||
|
resourceIds.insert(file.resource.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (strongSelf.context.account.postbox.mediaBox.removeCachedResources(resourceIds, force: true, notify: true)
|
||||||
|
|> deliverOnMainQueue).start(completed: {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.updateState { state in
|
strongSelf.updateState { state in
|
||||||
return state.withUpdatedSelectedMessageIds(nil)
|
return state.withUpdatedSelectedMessageIds(nil)
|
||||||
@ -1096,34 +1136,74 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}))
|
|
||||||
}
|
|
||||||
if actions.options.contains(.deleteLocally) {
|
|
||||||
let localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
|
|
||||||
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
if let strongSelf = self {
|
|
||||||
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).start()
|
|
||||||
|
|
||||||
strongSelf.updateState { state in
|
|
||||||
return state.withUpdatedSelectedMessageIds(nil)
|
|
||||||
}
|
|
||||||
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
|
||||||
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
})
|
})
|
||||||
])])
|
], actionLayout: .horizontal, parseMarkdown: true), nil)
|
||||||
strongSelf.view.endEditing(true)
|
})
|
||||||
strongSelf.present?(actionSheet, nil)
|
} else {
|
||||||
}
|
let (peers, messages) = self.currentMessages
|
||||||
}))
|
let _ = (self.context.account.postbox.transaction { transaction -> Void in
|
||||||
|
for id in messageIds {
|
||||||
|
if transaction.getMessage(id) == nil, let message = messages[id] {
|
||||||
|
storeMessageFromSearch(transaction: transaction, message: message._asMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start()
|
||||||
|
|
||||||
|
self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds, messages: messages, peers: peers)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] actions in
|
||||||
|
if let strongSelf = self, !actions.options.isEmpty {
|
||||||
|
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||||
|
var items: [ActionSheetItem] = []
|
||||||
|
let personalPeerName: String? = nil
|
||||||
|
|
||||||
|
if actions.options.contains(.deleteGlobally) {
|
||||||
|
let globalTitle: String
|
||||||
|
if let personalPeerName = personalPeerName {
|
||||||
|
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).string
|
||||||
|
} else {
|
||||||
|
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone
|
||||||
|
}
|
||||||
|
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).start()
|
||||||
|
|
||||||
|
strongSelf.updateState { state in
|
||||||
|
return state.withUpdatedSelectedMessageIds(nil)
|
||||||
|
}
|
||||||
|
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||||
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if actions.options.contains(.deleteLocally) {
|
||||||
|
let localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
|
||||||
|
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forLocalPeer).start()
|
||||||
|
|
||||||
|
strongSelf.updateState { state in
|
||||||
|
return state.withUpdatedSelectedMessageIds(nil)
|
||||||
|
}
|
||||||
|
if let (layout, navigationBarHeight) = strongSelf.validLayout {
|
||||||
|
strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])])
|
||||||
|
strongSelf.view.endEditing(true)
|
||||||
|
strongSelf.present?(actionSheet, nil)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,7 +400,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openStorageSettings: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem {
|
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem {
|
||||||
switch self {
|
switch self {
|
||||||
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
|
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
|
||||||
let primaryPeer: EnginePeer
|
let primaryPeer: EnginePeer
|
||||||
@ -564,8 +564,8 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
case .downloaded:
|
case .downloaded:
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
header = ChatListSearchItemHeader(type: .recentDownloads, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Settings", action: {
|
header = ChatListSearchItemHeader(type: .recentDownloads, theme: presentationData.theme, strings: presentationData.strings, actionTitle: "Clear", action: {
|
||||||
openStorageSettings()
|
openClearRecentlyDownloaded()
|
||||||
})
|
})
|
||||||
case .index:
|
case .index:
|
||||||
header = ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
header = ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||||
@ -628,12 +628,12 @@ private func chatListSearchContainerPreparedRecentTransition(from fromEntries: [
|
|||||||
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
|
return ChatListSearchContainerRecentTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openStorageSettings: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition {
|
public func chatListSearchContainerPreparedTransition(from fromEntries: [ChatListSearchEntry], to toEntries: [ChatListSearchEntry], displayingResults: Bool, isEmpty: Bool, isLoading: Bool, animated: Bool, context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ChatListSearchContainerTransition {
|
||||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openStorageSettings: openStorageSettings, toggleAllPaused: toggleAllPaused), directionHint: nil) }
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
|
||||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openStorageSettings: openStorageSettings, toggleAllPaused: toggleAllPaused), directionHint: nil) }
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enableHeaders: enableHeaders, filter: filter, key: key, tagMask: tagMask, interaction: interaction, listInteraction: listInteraction, peerContextAction: peerContextAction, toggleExpandLocalResults: toggleExpandLocalResults, toggleExpandGlobalResults: toggleExpandGlobalResults, searchPeer: searchPeer, searchQuery: searchQuery, searchOptions: searchOptions, messageContextAction: messageContextAction, openClearRecentlyDownloaded: openClearRecentlyDownloaded, toggleAllPaused: toggleAllPaused), directionHint: nil) }
|
||||||
|
|
||||||
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated)
|
return ChatListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, displayingResults: displayingResults, isEmpty: isEmpty, isLoading: isLoading, query: searchQuery, animated: animated)
|
||||||
}
|
}
|
||||||
@ -981,8 +981,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return presentationDataPromise.get()
|
return combineLatest(queue: .mainQueue(), presentationDataPromise.get(), selectionPromise.get())
|
||||||
|> map { presentationData -> ([ChatListSearchEntry], Bool)? in
|
|> map { presentationData, selectionState -> ([ChatListSearchEntry], Bool)? in
|
||||||
var entries: [ChatListSearchEntry] = []
|
var entries: [ChatListSearchEntry] = []
|
||||||
var existingMessageIds = Set<MessageId>()
|
var existingMessageIds = Set<MessageId>()
|
||||||
|
|
||||||
@ -1047,7 +1047,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
peer = EngineRenderedPeer(peer: EnginePeer(channelPeer))
|
peer = EngineRenderedPeer(peer: EnginePeer(channelPeer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entries.append(.message(message, peer, nil, presentationData, 1, nil, false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false))
|
|
||||||
|
entries.append(.message(message, peer, nil, presentationData, 1, selectionState?.contains(message.id), false, .downloaded(timestamp: item.timestamp, index: message.index), (item.resourceId, item.size, false), .recentlyDownloaded, false))
|
||||||
}
|
}
|
||||||
return (entries.sorted(), false)
|
return (entries.sorted(), false)
|
||||||
}
|
}
|
||||||
@ -1501,6 +1502,17 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let playlistLocation: PeerMessagesPlaylistLocation?
|
||||||
|
if strongSelf.key == .downloads {
|
||||||
|
playlistLocation = nil
|
||||||
|
} else {
|
||||||
|
playlistLocation = .custom(messages: foundMessages |> map { message, a, b in
|
||||||
|
return (message.map { $0._asMessage() }, a, b)
|
||||||
|
}, at: message.id, loadMore: {
|
||||||
|
loadMore()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: {
|
return context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: true, mode: mode, navigationController: navigationController, dismissInput: {
|
||||||
interaction.dismissInput()
|
interaction.dismissInput()
|
||||||
}, present: { c, a in
|
}, present: { c, a in
|
||||||
@ -1524,11 +1536,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}, openPeer: { peer, navigation in
|
}, openPeer: { peer, navigation in
|
||||||
}, callPeer: { _, _ in
|
}, callPeer: { _, _ in
|
||||||
}, enqueueMessage: { _ in
|
}, enqueueMessage: { _ in
|
||||||
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages |> map { message, a, b in
|
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource))
|
||||||
return (message.map { $0._asMessage() }, a, b)
|
|
||||||
}, at: message.id, loadMore: {
|
|
||||||
loadMore()
|
|
||||||
}), gallerySource: gallerySource))
|
|
||||||
}, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in
|
}, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in
|
||||||
guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else {
|
guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else {
|
||||||
return
|
return
|
||||||
@ -1641,11 +1649,44 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}, searchPeer: { peer in
|
}, searchPeer: { peer in
|
||||||
}, searchQuery: strongSelf.searchQueryValue, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture, paneKey, downloadResource in
|
}, searchQuery: strongSelf.searchQueryValue, searchOptions: strongSelf.searchOptionsValue, messageContextAction: { message, node, rect, gesture, paneKey, downloadResource in
|
||||||
interaction.messageContextAction(message, node, rect, gesture, paneKey, downloadResource)
|
interaction.messageContextAction(message, node, rect, gesture, paneKey, downloadResource)
|
||||||
}, openStorageSettings: {
|
}, openClearRecentlyDownloaded: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.context.sharedContext.openStorageUsage(context: strongSelf.context)
|
|
||||||
|
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||||
|
var items: [ActionSheetItem] = []
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
items.append(ActionSheetTextItem(title: "Telegram allows to store all received and sent\ndocuments in the cloud and save storage\nspace on your device."))
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
items.append(ActionSheetButtonItem(title: "Manage Device Storage", color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.context.sharedContext.openStorageUsage(context: strongSelf.context)
|
||||||
|
}))
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
items.append(ActionSheetButtonItem(title: "Clear Downloads List", color: .destructive, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = clearRecentDownloadList(postbox: strongSelf.context.account.postbox).start()
|
||||||
|
}))
|
||||||
|
|
||||||
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])])
|
||||||
|
strongSelf.interaction.dismissInput()
|
||||||
|
strongSelf.interaction.present(actionSheet, nil)
|
||||||
}, toggleAllPaused: {
|
}, toggleAllPaused: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -1833,7 +1874,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
loadMore()
|
loadMore()
|
||||||
}
|
}
|
||||||
|
|
||||||
if [.file, .music, .voiceOrInstantVideo].contains(tagMask) {
|
if [.file, .music, .voiceOrInstantVideo].contains(tagMask) || self.key == .downloads {
|
||||||
self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState
|
self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState
|
||||||
|> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in
|
|> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in
|
||||||
if let (account, state, type) = playlistStateAndType {
|
if let (account, state, type) = playlistStateAndType {
|
||||||
@ -1846,7 +1887,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
return .single(nil) |> delay(0.2, queue: .mainQueue())
|
return .single(nil) |> delay(0.2, queue: .mainQueue())
|
||||||
}
|
}
|
||||||
case .music:
|
case .music:
|
||||||
if tagMask != .music {
|
if tagMask != .music && self.key != .downloads {
|
||||||
return .single(nil) |> delay(0.2, queue: .mainQueue())
|
return .single(nil) |> delay(0.2, queue: .mainQueue())
|
||||||
}
|
}
|
||||||
case .file:
|
case .file:
|
||||||
|
@ -1106,6 +1106,11 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape)
|
let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape)
|
||||||
leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0
|
leftTitleInset = leftButtonSize.width + leftButtonInset + 1.0
|
||||||
|
|
||||||
|
var transition = transition
|
||||||
|
if self.leftButtonNode.frame.width.isZero {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
self.leftButtonNode.alpha = 1.0
|
self.leftButtonNode.alpha = 1.0
|
||||||
transition.updateFrame(node: self.leftButtonNode, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize))
|
transition.updateFrame(node: self.leftButtonNode, frame: CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize))
|
||||||
}
|
}
|
||||||
@ -1118,6 +1123,11 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape)
|
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape)
|
||||||
rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0
|
rightTitleInset = rightButtonSize.width + leftButtonInset + 1.0
|
||||||
self.rightButtonNode.alpha = 1.0
|
self.rightButtonNode.alpha = 1.0
|
||||||
|
|
||||||
|
var transition = transition
|
||||||
|
if self.rightButtonNode.frame.width.isZero {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize))
|
transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1182,6 +1192,10 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
self.titleNode.alpha = progress * progress
|
self.titleNode.alpha = progress * progress
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
var transition = transition
|
||||||
|
if self.titleNode.frame.width.isZero {
|
||||||
|
transition = .immediate
|
||||||
|
}
|
||||||
self.titleNode.alpha = 1.0
|
self.titleNode.alpha = 1.0
|
||||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize))
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,12 @@ import SwiftSignalKit
|
|||||||
import UniversalMediaPlayer
|
import UniversalMediaPlayer
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
|
||||||
private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<MediaPlayerStatus?, NoError> {
|
private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal<MediaPlayerStatus?, NoError> {
|
||||||
guard let playerType = peerMessageMediaPlayerType(message) else {
|
guard let playerType = peerMessageMediaPlayerType(message) else {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) {
|
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) {
|
||||||
return context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
|
return context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
|
||||||
|> mapToSignal { state -> Signal<MediaPlayerStatus?, NoError> in
|
|> mapToSignal { state -> Signal<MediaPlayerStatus?, NoError> in
|
||||||
return .single(state?.status)
|
return .single(state?.status)
|
||||||
@ -21,31 +21,32 @@ private func internalMessageFileMediaPlaybackStatus(context: AccountContext, fil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<MediaPlayerStatus, NoError> {
|
public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal<MediaPlayerStatus, NoError> {
|
||||||
var duration = 0.0
|
var duration = 0.0
|
||||||
if let value = file.duration {
|
if let value = file.duration {
|
||||||
duration = Double(value)
|
duration = Double(value)
|
||||||
}
|
}
|
||||||
let defaultStatus = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true)
|
let defaultStatus = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true)
|
||||||
return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status in
|
return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList)
|
||||||
|
|> map { status in
|
||||||
return status ?? defaultStatus
|
return status ?? defaultStatus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<Float, NoError> {
|
public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool, isDownloadList: Bool) -> Signal<Float, NoError> {
|
||||||
guard let playerType = peerMessageMediaPlayerType(message) else {
|
guard let playerType = peerMessageMediaPlayerType(message) else {
|
||||||
return .never()
|
return .never()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) {
|
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) {
|
||||||
return context.sharedContext.mediaManager.filteredPlayerAudioLevelEvents(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
|
return context.sharedContext.mediaManager.filteredPlayerAudioLevelEvents(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
|
||||||
} else {
|
} else {
|
||||||
return .never()
|
return .never()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal<FileMediaResourceStatus, NoError> {
|
public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false, isDownloadList: Bool = false) -> Signal<FileMediaResourceStatus, NoError> {
|
||||||
let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status -> MediaPlayerPlaybackStatus? in
|
let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch, isDownloadList: isDownloadList) |> map { status -> MediaPlayerPlaybackStatus? in
|
||||||
return status?.status
|
return status?.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], key: .chats, tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
|
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], key: .chats, tagMask: nil, interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
|
||||||
}, toggleExpandGlobalResults: {
|
}, toggleExpandGlobalResults: {
|
||||||
}, searchPeer: { _ in
|
}, searchPeer: { _ in
|
||||||
}, searchQuery: "", searchOptions: nil, messageContextAction: nil, openStorageSettings: {}, toggleAllPaused: {})
|
}, searchQuery: "", searchOptions: nil, messageContextAction: nil, openClearRecentlyDownloaded: {}, toggleAllPaused: {})
|
||||||
strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime)
|
strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -531,6 +531,9 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
|
|||||||
{
|
{
|
||||||
TGDispatchOnMainThread(^
|
TGDispatchOnMainThread(^
|
||||||
{
|
{
|
||||||
|
if (!_subscribedForCameraChanges) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ([keyPath isEqualToString:PGCameraAdjustingFocusKey])
|
if ([keyPath isEqualToString:PGCameraAdjustingFocusKey])
|
||||||
{
|
{
|
||||||
bool adjustingFocus = [[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:@YES];
|
bool adjustingFocus = [[change objectForKey:NSKeyValueChangeNewKey] isEqualToNumber:@YES];
|
||||||
|
@ -654,7 +654,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
|
|
||||||
if statusUpdated && item.displayFileInfo {
|
if statusUpdated && item.displayFileInfo {
|
||||||
if let file = selectedMedia as? TelegramMediaFile {
|
if let file = selectedMedia as? TelegramMediaFile {
|
||||||
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList)
|
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList)
|
||||||
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
|
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
|
||||||
if case .Fetching = value.fetchStatus {
|
if case .Fetching = value.fetchStatus {
|
||||||
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
||||||
@ -668,16 +668,20 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
updatedStatusSignal = currentUpdatedStatusSignal
|
updatedStatusSignal = currentUpdatedStatusSignal
|
||||||
|> map { status in
|
|> map { status in
|
||||||
switch status.mediaStatus {
|
switch status.mediaStatus {
|
||||||
case .fetchStatus:
|
case .fetchStatus:
|
||||||
|
if item.isDownloadList {
|
||||||
|
return FileMediaResourceStatus(mediaStatus: .fetchStatus(status.fetchStatus), fetchStatus: status.fetchStatus)
|
||||||
|
} else {
|
||||||
return FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: status.fetchStatus)
|
return FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: status.fetchStatus)
|
||||||
case .playbackStatus:
|
}
|
||||||
return status
|
case .playbackStatus:
|
||||||
|
return status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isVoice {
|
if isVoice {
|
||||||
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList)
|
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult, isDownloadList: item.isDownloadList)
|
||||||
}
|
}
|
||||||
} else if let image = selectedMedia as? TelegramMediaImage {
|
} else if let image = selectedMedia as? TelegramMediaImage {
|
||||||
updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList)
|
updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult || item.isDownloadList)
|
||||||
@ -890,7 +894,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel)))
|
transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel)))
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.contentSize.height + UIScreenPixel))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: nodeLayout.contentSize.height + UIScreenPixel))
|
||||||
|
|
||||||
if let backgroundNode = strongSelf.backgroundNode {
|
if let backgroundNode = strongSelf.backgroundNode {
|
||||||
backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.contentSize.height))
|
backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.contentSize.height))
|
||||||
@ -1098,27 +1102,32 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
if !isAudio && !isInstantVideo {
|
if !isAudio && !isInstantVideo {
|
||||||
self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate)
|
self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate)
|
||||||
} else {
|
} else {
|
||||||
|
if item.isDownloadList {
|
||||||
|
self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate)
|
||||||
|
}
|
||||||
switch status {
|
switch status {
|
||||||
case let .fetchStatus(fetchStatus):
|
case let .fetchStatus(fetchStatus):
|
||||||
switch fetchStatus {
|
switch fetchStatus {
|
||||||
case .Fetching:
|
case let .Fetching(_, progress):
|
||||||
break
|
if item.isDownloadList {
|
||||||
case .Local:
|
iconStatusState = .progress(value: CGFloat(progress), cancelEnabled: true, appearance: nil)
|
||||||
if isAudio || isInstantVideo {
|
|
||||||
iconStatusState = .play
|
|
||||||
}
|
|
||||||
case .Remote, .Paused:
|
|
||||||
if isAudio || isInstantVideo {
|
|
||||||
iconStatusState = .play
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case let .playbackStatus(playbackStatus):
|
case .Local:
|
||||||
switch playbackStatus {
|
if isAudio || isInstantVideo {
|
||||||
case .playing:
|
|
||||||
iconStatusState = .pause
|
|
||||||
case .paused:
|
|
||||||
iconStatusState = .play
|
iconStatusState = .play
|
||||||
}
|
}
|
||||||
|
case .Remote, .Paused:
|
||||||
|
if isAudio || isInstantVideo {
|
||||||
|
iconStatusState = .play
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .playbackStatus(playbackStatus):
|
||||||
|
switch playbackStatus {
|
||||||
|
case .playing:
|
||||||
|
iconStatusState = .pause
|
||||||
|
case .paused:
|
||||||
|
iconStatusState = .play
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.iconStatusNode.backgroundNodeColor = iconStatusBackgroundColor
|
self.iconStatusNode.backgroundNodeColor = iconStatusBackgroundColor
|
||||||
@ -1196,17 +1205,22 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
break
|
break
|
||||||
case let .fetchStatus(fetchStatus):
|
case let .fetchStatus(fetchStatus):
|
||||||
maybeFetchStatus = fetchStatus
|
maybeFetchStatus = fetchStatus
|
||||||
switch fetchStatus {
|
}
|
||||||
case .Fetching(_, let progress), .Paused(let progress):
|
|
||||||
if let file = self.currentMedia as? TelegramMediaFile, let size = file.size {
|
if item.isDownloadList, let fetchStatus = self.fetchStatus {
|
||||||
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))) / \(dataSizeString(size, forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData)))"
|
maybeFetchStatus = fetchStatus
|
||||||
}
|
}
|
||||||
descriptionOffset = 14.0
|
|
||||||
case .Remote:
|
switch maybeFetchStatus {
|
||||||
descriptionOffset = 14.0
|
case .Fetching(_, let progress), .Paused(let progress):
|
||||||
case .Local:
|
if let file = self.currentMedia as? TelegramMediaFile, let size = file.size {
|
||||||
break
|
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData))) / \(dataSizeString(size, forceDecimal: true, formatting: DataSizeStringFormatting(chatPresentationData: item.presentationData)))"
|
||||||
}
|
}
|
||||||
|
descriptionOffset = 14.0
|
||||||
|
case .Remote:
|
||||||
|
descriptionOffset = 14.0
|
||||||
|
case .Local:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
switch maybeFetchStatus {
|
switch maybeFetchStatus {
|
||||||
@ -1269,7 +1283,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
} else {
|
} else {
|
||||||
if let linearProgressNode = self.linearProgressNode {
|
if let linearProgressNode = self.linearProgressNode {
|
||||||
self.linearProgressNode = nil
|
self.linearProgressNode = nil
|
||||||
linearProgressNode.layer.animateAlpha(from: 1.0, to: 1.0, duration: 0.2, removeOnCompletion: false, completion: { [weak linearProgressNode] _ in
|
linearProgressNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak linearProgressNode] _ in
|
||||||
linearProgressNode?.removeFromSupernode()
|
linearProgressNode?.removeFromSupernode()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1525,9 +1539,11 @@ private final class DownloadIconNode: ASImageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateTheme(theme: PresentationTheme) {
|
func updateTheme(theme: PresentationTheme) {
|
||||||
self.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(theme, generate: {
|
if self.image != nil {
|
||||||
return generateDownloadIcon(color: theme.list.itemAccentColor)
|
self.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(theme, generate: {
|
||||||
})
|
return generateDownloadIcon(color: theme.list.itemAccentColor)
|
||||||
|
})
|
||||||
|
}
|
||||||
self.customColor = theme.list.itemAccentColor
|
self.customColor = theme.list.itemAccentColor
|
||||||
self.animationNode?.customColor = self.customColor
|
self.animationNode?.customColor = self.customColor
|
||||||
}
|
}
|
||||||
|
@ -337,7 +337,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab
|
|||||||
self.interaction?.openSearch()
|
self.interaction?.openSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func prepareForReuse() {
|
public func resetForReuse() {
|
||||||
self.interaction?.updateMapMode(.map)
|
self.interaction?.updateMapMode(.map)
|
||||||
self.interaction?.dismissSearch()
|
self.interaction?.dismissSearch()
|
||||||
self.scrollToTop?()
|
self.scrollToTop?()
|
||||||
|
@ -127,7 +127,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
self.checkNode = checkNode
|
self.checkNode = checkNode
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let asset = self.asset, let interaction = self.interaction, let selectionState = interaction.selectionState {
|
if let asset = self.asset, let interaction = self.interaction, let selectionState = interaction.selectionState {
|
||||||
let selected = selectionState.isIdentifierSelected(asset.localIdentifier)
|
let selected = selectionState.isIdentifierSelected(asset.localIdentifier)
|
||||||
if let legacyAsset = TGMediaAsset(phAsset: asset) {
|
if let legacyAsset = TGMediaAsset(phAsset: asset) {
|
||||||
@ -177,7 +177,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
}, error: { _ in
|
}, error: { _ in
|
||||||
}, completed: nil)!
|
}, completed: nil)!
|
||||||
|
|
||||||
return ActionDisposable {
|
return ActionDisposable {
|
||||||
disposable.dispose()
|
disposable.dispose()
|
||||||
}
|
}
|
||||||
@ -185,9 +185,9 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let scale = min(2.0, UIScreenScale)
|
let scale = min(2.0, UIScreenScale)
|
||||||
let targetSize = CGSize(width: 140.0 * scale, height: 140.0 * scale)
|
let targetSize = CGSize(width: 128.0 * scale, height: 128.0 * scale)
|
||||||
let originalSignal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false)
|
let originalSignal = assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false)
|
||||||
let imageSignal: Signal<UIImage?, NoError> = editedSignal
|
let imageSignal: Signal<UIImage?, NoError> = editedSignal
|
||||||
|> mapToSignal { result in
|
|> mapToSignal { result in
|
||||||
|
@ -58,7 +58,7 @@ final class MediaPickerManageNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .left)
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(15.0), textColor: theme.list.freeTextColor, paragraphAlignment: .left)
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0 - buttonWidth - 26.0, height: layout.size.height))
|
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0 - buttonWidth - 26.0, height: layout.size.height))
|
||||||
let panelHeight = max(64.0, textSize.height + 10.0)
|
let panelHeight = max(64.0, textSize.height + 24.0)
|
||||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: floorToScreenPixels((panelHeight - textSize.height) / 2.0) - 5.0), size: textSize))
|
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + 16.0, y: floorToScreenPixels((panelHeight - textSize.height) / 2.0) - 5.0), size: textSize))
|
||||||
|
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
|
@ -8,6 +8,7 @@ import SwiftSignalKit
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
|
import TelegramStringFormatting
|
||||||
import MergeLists
|
import MergeLists
|
||||||
import Photos
|
import Photos
|
||||||
import PhotosUI
|
import PhotosUI
|
||||||
@ -75,8 +76,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
|
|
||||||
|
fileprivate var interaction: MediaPickerInteraction?
|
||||||
|
|
||||||
private let peer: EnginePeer?
|
private let peer: EnginePeer?
|
||||||
private let chatLocation: ChatLocation?
|
private let chatLocation: ChatLocation?
|
||||||
|
private let bannedSendMedia: (Int32, Bool)?
|
||||||
|
|
||||||
private let titleView: MediaPickerTitleView
|
private let titleView: MediaPickerTitleView
|
||||||
private let moreButtonNode: MediaPickerMoreButtonNode
|
private let moreButtonNode: MediaPickerMoreButtonNode
|
||||||
@ -102,19 +106,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
case noAccess(cameraAccess: AVAuthorizationStatus?)
|
case noAccess(cameraAccess: AVAuthorizationStatus?)
|
||||||
case assets(fetchResult: PHFetchResult<PHAsset>?, mediaAccess: PHAuthorizationStatus, cameraAccess: AVAuthorizationStatus?)
|
case assets(fetchResult: PHFetchResult<PHAsset>?, preload: Bool, mediaAccess: PHAuthorizationStatus, cameraAccess: AVAuthorizationStatus?)
|
||||||
}
|
}
|
||||||
|
|
||||||
private weak var controller: MediaPickerScreen?
|
private weak var controller: MediaPickerScreen?
|
||||||
fileprivate var interaction: MediaPickerInteraction?
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let mediaAssetsContext: MediaAssetsContext
|
private let mediaAssetsContext: MediaAssetsContext
|
||||||
|
|
||||||
private let backgroundNode: NavigationBackgroundNode
|
private let backgroundNode: NavigationBackgroundNode
|
||||||
private let gridNode: GridNode
|
private let gridNode: GridNode
|
||||||
private var cameraView: TGAttachmentCameraView?
|
fileprivate var cameraView: TGAttachmentCameraView?
|
||||||
private var placeholderNode: MediaPickerPlaceholderNode?
|
private var placeholderNode: MediaPickerPlaceholderNode?
|
||||||
private var manageNode: MediaPickerManageNode?
|
private var manageNode: MediaPickerManageNode?
|
||||||
|
private var bannedTextNode: ImmediateTextNode?
|
||||||
|
|
||||||
private var selectionNode: MediaPickerSelectedListNode?
|
private var selectionNode: MediaPickerSelectedListNode?
|
||||||
|
|
||||||
@ -123,6 +127,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
private var enqueuedTransactions: [MediaPickerGridTransaction] = []
|
private var enqueuedTransactions: [MediaPickerGridTransaction] = []
|
||||||
private var state: State?
|
private var state: State?
|
||||||
|
|
||||||
|
private var preloadPromise = ValuePromise<Bool>(true)
|
||||||
|
|
||||||
private var itemsDisposable: Disposable?
|
private var itemsDisposable: Disposable?
|
||||||
private var selectionChangedDisposable: Disposable?
|
private var selectionChangedDisposable: Disposable?
|
||||||
private var itemsDimensionsUpdatedDisposable: Disposable?
|
private var itemsDimensionsUpdatedDisposable: Disposable?
|
||||||
@ -131,7 +137,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
private let hiddenMediaId = Promise<String?>(nil)
|
private let hiddenMediaId = Promise<String?>(nil)
|
||||||
|
|
||||||
private var didSetReady = false
|
private var didSetReady = false
|
||||||
private let _ready = Promise<Bool>()
|
private let _ready = Promise<Bool>(true)
|
||||||
var ready: Promise<Bool> {
|
var ready: Promise<Bool> {
|
||||||
return self._ready
|
return self._ready
|
||||||
}
|
}
|
||||||
@ -154,45 +160,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.gridNode)
|
self.addSubnode(self.gridNode)
|
||||||
|
|
||||||
self.interaction = MediaPickerInteraction(openMedia: { [weak self] fetchResult, index, immediateThumbnail in
|
let preloadPromise = self.preloadPromise
|
||||||
self?.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
|
|
||||||
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
|
||||||
self?.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
|
||||||
}, toggleSelection: { [weak self] item, value in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.interaction?.selectionState?.setItem(item, selected: value)
|
|
||||||
}
|
|
||||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
|
|
||||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
|
||||||
if let currentItem = currentItem {
|
|
||||||
selectionState.setItem(currentItem, selected: true)
|
|
||||||
}
|
|
||||||
strongSelf.send(silently: silently, scheduleTime: scheduleTime, animated: animated)
|
|
||||||
}
|
|
||||||
}, schedule: { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.controller?.presentSchedulePicker(false, { [weak self] time in
|
|
||||||
self?.interaction?.sendSelected(nil, false, time, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, dismissInput: { [weak self] in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.dismissInput()
|
|
||||||
}
|
|
||||||
}, selectionState: TGMediaSelectionContext(), editingState: TGMediaEditingContext())
|
|
||||||
self.interaction?.selectionState?.grouping = true
|
|
||||||
|
|
||||||
let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess())
|
let updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess())
|
||||||
|> mapToSignal { mediaAccess, cameraAccess -> Signal<State, NoError> in
|
|> mapToSignal { mediaAccess, cameraAccess -> Signal<State, NoError> in
|
||||||
if case .notDetermined = mediaAccess {
|
if case .notDetermined = mediaAccess {
|
||||||
return .single(.assets(fetchResult: nil, mediaAccess: mediaAccess, cameraAccess: cameraAccess))
|
return .single(.assets(fetchResult: nil, preload: false, mediaAccess: mediaAccess, cameraAccess: cameraAccess))
|
||||||
} else if [.restricted, .denied].contains(mediaAccess) {
|
} else if [.restricted, .denied].contains(mediaAccess) {
|
||||||
return .single(.noAccess(cameraAccess: cameraAccess))
|
return .single(.noAccess(cameraAccess: cameraAccess))
|
||||||
} else {
|
} else {
|
||||||
return mediaAssetsContext.recentAssets()
|
return combineLatest(mediaAssetsContext.recentAssets(), preloadPromise.get())
|
||||||
|> map { fetchResult in
|
|> map { fetchResult, preload in
|
||||||
return .assets(fetchResult: fetchResult, mediaAccess: mediaAccess, cameraAccess: cameraAccess)
|
return .assets(fetchResult: fetchResult, preload: preload, mediaAccess: mediaAccess, cameraAccess: cameraAccess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,7 +191,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
self.hiddenMediaDisposable = (self.hiddenMediaId.get()
|
self.hiddenMediaDisposable = (self.hiddenMediaId.get()
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] id in
|
|> deliverOnMainQueue).start(next: { [weak self] id in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.interaction?.hiddenMediaId = id
|
strongSelf.controller?.interaction?.hiddenMediaId = id
|
||||||
|
|
||||||
strongSelf.gridNode.forEachItemNode { itemNode in
|
strongSelf.gridNode.forEachItemNode { itemNode in
|
||||||
if let itemNode = itemNode as? MediaPickerGridItemNode {
|
if let itemNode = itemNode as? MediaPickerGridItemNode {
|
||||||
@ -224,7 +203,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if let selectionState = self.interaction?.selectionState {
|
if let selectionState = self.controller?.interaction?.selectionState {
|
||||||
func selectionChangedSignal(selectionState: TGMediaSelectionContext) -> Signal<Void, NoError> {
|
func selectionChangedSignal(selectionState: TGMediaSelectionContext) -> Signal<Void, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = selectionState.selectionChangedSignal()?.start(next: { next in
|
let disposable = selectionState.selectionChangedSignal()?.start(next: { next in
|
||||||
@ -244,7 +223,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if let editingState = self.interaction?.editingState {
|
if let editingState = self.controller?.interaction?.editingState {
|
||||||
func itemsDimensionsUpdatedSignal(editingState: TGMediaEditingContext) -> Signal<Void, NoError> {
|
func itemsDimensionsUpdatedSignal(editingState: TGMediaEditingContext) -> Signal<Void, NoError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = editingState.cropAdjustmentsUpdatedSignal()?.start(next: { next in
|
let disposable = editingState.cropAdjustmentsUpdatedSignal()?.start(next: { next in
|
||||||
@ -290,11 +269,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
cameraView.startPreview()
|
cameraView.startPreview()
|
||||||
|
|
||||||
self.gridNode.scrollView.addSubview(cameraView)
|
self.gridNode.scrollView.addSubview(cameraView)
|
||||||
|
|
||||||
// self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
// self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissInput() {
|
fileprivate func dismissInput() {
|
||||||
self.view.window?.endEditing(true)
|
self.view.window?.endEditing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +281,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
private var requestedCameraAccess = false
|
private var requestedCameraAccess = false
|
||||||
|
|
||||||
private func updateState(_ state: State) {
|
private func updateState(_ state: State) {
|
||||||
guard let interaction = self.interaction, let controller = self.controller else {
|
guard let controller = self.controller, let interaction = controller.interaction else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,14 +304,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
self.requestedCameraAccess = true
|
self.requestedCameraAccess = true
|
||||||
self.mediaAssetsContext.requestCameraAccess()
|
self.mediaAssetsContext.requestCameraAccess()
|
||||||
}
|
}
|
||||||
case let .assets(fetchResult, mediaAccess, cameraAccess):
|
case let .assets(fetchResult, preload, mediaAccess, cameraAccess):
|
||||||
if let fetchResult = fetchResult {
|
if let fetchResult = fetchResult {
|
||||||
for i in 0 ..< fetchResult.count {
|
let totalCount = fetchResult.count
|
||||||
entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, fetchResult.count - i - 1)))
|
let count = preload ? min(10, totalCount) : totalCount
|
||||||
|
|
||||||
|
for i in 0 ..< count {
|
||||||
|
entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, totalCount - i - 1)))
|
||||||
stableId += 1
|
stableId += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .assets(previousFetchResult, _, previousCameraAccess) = previousState, previousFetchResult == nil || previousCameraAccess != cameraAccess {
|
if case let .assets(previousFetchResult, _, _, previousCameraAccess) = previousState, previousFetchResult == nil || previousCameraAccess != cameraAccess {
|
||||||
updateLayout = true
|
updateLayout = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,7 +347,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
self.selectionNode?.updateSelectionState()
|
self.selectionNode?.updateSelectionState()
|
||||||
|
|
||||||
let count = Int32(self.interaction?.selectionState?.count() ?? 0)
|
let count = Int32(self.controller?.interaction?.selectionState?.count() ?? 0)
|
||||||
self.controller?.updateSelectionState(count: count)
|
self.controller?.updateSelectionState(count: count)
|
||||||
|
|
||||||
if let (layout, navigationBarHeight) = self.validLayout {
|
if let (layout, navigationBarHeight) = self.validLayout {
|
||||||
@ -387,7 +369,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
let selectionNode = MediaPickerSelectedListNode(context: controller.context)
|
let selectionNode = MediaPickerSelectedListNode(context: controller.context)
|
||||||
selectionNode.alpha = 0.0
|
selectionNode.alpha = 0.0
|
||||||
selectionNode.isUserInteractionEnabled = false
|
selectionNode.isUserInteractionEnabled = false
|
||||||
selectionNode.interaction = self.interaction
|
selectionNode.interaction = self.controller?.interaction
|
||||||
self.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
|
self.insertSubnode(selectionNode, aboveSubnode: self.gridNode)
|
||||||
self.selectionNode = selectionNode
|
self.selectionNode = selectionNode
|
||||||
|
|
||||||
@ -405,8 +387,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openMedia(fetchResult: PHFetchResult<PHAsset>, index: Int, immediateThumbnail: UIImage?) {
|
fileprivate func openMedia(fetchResult: PHFetchResult<PHAsset>, index: Int, immediateThumbnail: UIImage?) {
|
||||||
guard let controller = self.controller, let interaction = self.interaction, let (layout, _) = self.validLayout else {
|
guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
@ -422,15 +404,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
return self?.transitionView(for: identifier)
|
return self?.transitionView(for: identifier)
|
||||||
}, completed: { [weak self] result, silently, scheduleTime in
|
}, completed: { [weak self] result, silently, scheduleTime in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.interaction?.sendSelected(result, silently, scheduleTime, false)
|
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false)
|
||||||
}
|
}
|
||||||
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||||
self?.controller?.present(c, in: .window(.root), with: a)
|
self?.controller?.present(c, in: .window(.root), with: a)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openSelectedMedia(item: TGMediaSelectableItem, immediateThumbnail: UIImage?) {
|
fileprivate func openSelectedMedia(item: TGMediaSelectableItem, immediateThumbnail: UIImage?) {
|
||||||
guard let controller = self.controller, let interaction = self.interaction, let (layout, _) = self.validLayout else {
|
guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
@ -445,7 +427,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
return self?.transitionView(for: identifier)
|
return self?.transitionView(for: identifier)
|
||||||
}, completed: { [weak self] result, silently, scheduleTime in
|
}, completed: { [weak self] result, silently, scheduleTime in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.interaction?.sendSelected(result, silently, scheduleTime, false)
|
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false)
|
||||||
}
|
}
|
||||||
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||||
self?.controller?.present(c, in: .window(.root), with: a)
|
self?.controller?.present(c, in: .window(.root), with: a)
|
||||||
@ -453,7 +435,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool) {
|
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool) {
|
||||||
guard let signals = TGMediaAssetsController.resultSignals(for: self.interaction?.selectionState, editingContext: self.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
|
guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: false, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.controller?.legacyCompletion(signals, silently, scheduleTime)
|
self.controller?.legacyCompletion(signals, silently, scheduleTime)
|
||||||
@ -549,11 +531,42 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0))
|
var cameraRect: CGRect? = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: itemWidth, height: itemWidth * 2.0 + 1.0))
|
||||||
|
|
||||||
var manageHeight: CGFloat = 0.0
|
var manageHeight: CGFloat = 0.0
|
||||||
if case let .assets(_, mediaAccess, cameraAccess) = self.state {
|
if case let .assets(_, _, mediaAccess, cameraAccess) = self.state {
|
||||||
if cameraAccess == nil {
|
if cameraAccess == nil {
|
||||||
cameraRect = nil
|
cameraRect = nil
|
||||||
}
|
}
|
||||||
if case .notDetermined = mediaAccess {
|
if let (untilDate, personal) = self.controller?.bannedSendMedia {
|
||||||
|
self.gridNode.alpha = 0.3
|
||||||
|
self.gridNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
let banDescription: String
|
||||||
|
if untilDate != 0 && untilDate != Int32.max {
|
||||||
|
banDescription = self.presentationData.strings.Conversation_RestrictedMediaTimed(stringForFullDate(timestamp: untilDate, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat)).string
|
||||||
|
} else if personal {
|
||||||
|
banDescription = self.presentationData.strings.Conversation_RestrictedMedia
|
||||||
|
} else {
|
||||||
|
banDescription = self.presentationData.strings.Conversation_DefaultRestrictedMedia
|
||||||
|
}
|
||||||
|
|
||||||
|
let bannedTextNode: ImmediateTextNode
|
||||||
|
if let current = self.bannedTextNode {
|
||||||
|
bannedTextNode = current
|
||||||
|
} else {
|
||||||
|
bannedTextNode = ImmediateTextNode()
|
||||||
|
bannedTextNode.maximumNumberOfLines = 0
|
||||||
|
bannedTextNode.textAlignment = .center
|
||||||
|
self.bannedTextNode = bannedTextNode
|
||||||
|
self.addSubnode(bannedTextNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
bannedTextNode.attributedText = NSAttributedString(string: banDescription, font: Font.regular(15.0), textColor: self.presentationData.theme.list.freeTextColor, paragraphAlignment: .center)
|
||||||
|
|
||||||
|
let bannedTextSize = bannedTextNode.updateLayout(CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 16.0, height: layout.size.height))
|
||||||
|
|
||||||
|
manageHeight = max(44.0, bannedTextSize.height + 20.0)
|
||||||
|
|
||||||
|
transition.updateFrame(node: bannedTextNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - bannedTextSize.width) / 2.0), y: insets.top + floorToScreenPixels((manageHeight - bannedTextSize.height) / 2.0) - 4.0), size: bannedTextSize))
|
||||||
|
} else if case .notDetermined = mediaAccess {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if case .limited = mediaAccess {
|
if case .limited = mediaAccess {
|
||||||
@ -604,7 +617,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
transition.updateFrame(node: self.backgroundNode, frame: bounds)
|
transition.updateFrame(node: self.backgroundNode, frame: bounds)
|
||||||
self.backgroundNode.update(size: bounds.size, transition: transition)
|
self.backgroundNode.update(size: bounds.size, transition: transition)
|
||||||
|
|
||||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: 200.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in
|
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: itemWidth, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -612,12 +625,16 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
strongSelf.didSetReady = true
|
strongSelf.didSetReady = true
|
||||||
Queue.mainQueue().justDispatch {
|
Queue.mainQueue().justDispatch {
|
||||||
strongSelf._ready.set(.single(true))
|
strongSelf._ready.set(.single(true))
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.5, {
|
||||||
|
strongSelf.preloadPromise.set(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if let selectionNode = self.selectionNode {
|
if let selectionNode = self.selectionNode {
|
||||||
let selectedItems = self.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? []
|
let selectedItems = self.controller?.interaction?.selectionState?.selectedItems() as? [TGMediaSelectableItem] ?? []
|
||||||
let updateSelectionNode = {
|
let updateSelectionNode = {
|
||||||
selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: transition)
|
selectionNode.updateLayout(size: bounds.size, insets: cleanGridInsets, items: selectedItems, grouped: self.controller?.groupedValue ?? true, theme: self.presentationData.theme, wallpaper: self.presentationData.chatWallpaper, bubbleCorners: self.presentationData.chatBubbleCorners, transition: transition)
|
||||||
}
|
}
|
||||||
@ -690,7 +707,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
private var groupedValue: Bool = true {
|
private var groupedValue: Bool = true {
|
||||||
didSet {
|
didSet {
|
||||||
self.groupedPromise.set(self.groupedValue)
|
self.groupedPromise.set(self.groupedValue)
|
||||||
self.controllerNode.interaction?.selectionState?.grouping = self.groupedValue
|
self.interaction?.selectionState?.grouping = self.groupedValue
|
||||||
|
|
||||||
if let layout = self.validLayout {
|
if let layout = self.validLayout {
|
||||||
self.containerLayoutUpdated(layout, transition: .immediate)
|
self.containerLayoutUpdated(layout, transition: .immediate)
|
||||||
@ -699,11 +716,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
private let groupedPromise = ValuePromise<Bool>(true)
|
private let groupedPromise = ValuePromise<Bool>(true)
|
||||||
|
|
||||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?) {
|
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, bannedSendMedia: (Int32, Bool)?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.chatLocation = chatLocation
|
self.chatLocation = chatLocation
|
||||||
|
self.bannedSendMedia = bannedSendMedia
|
||||||
|
|
||||||
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
|
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
|
||||||
self.titleView.title = self.presentationData.strings.Attachment_Gallery
|
self.titleView.title = self.presentationData.strings.Attachment_Gallery
|
||||||
@ -763,6 +781,34 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.interaction = MediaPickerInteraction(openMedia: { [weak self] fetchResult, index, immediateThumbnail in
|
||||||
|
self?.controllerNode.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
|
||||||
|
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||||
|
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
||||||
|
}, toggleSelection: { [weak self] item, value in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.interaction?.selectionState?.setItem(item, selected: value)
|
||||||
|
}
|
||||||
|
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
|
||||||
|
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
||||||
|
if let currentItem = currentItem {
|
||||||
|
selectionState.setItem(currentItem, selected: true)
|
||||||
|
}
|
||||||
|
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated)
|
||||||
|
}
|
||||||
|
}, schedule: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.presentSchedulePicker(false, { [weak self] time in
|
||||||
|
self?.interaction?.sendSelected(nil, false, time, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, dismissInput: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.controllerNode.dismissInput()
|
||||||
|
}
|
||||||
|
}, selectionState: TGMediaSelectionContext(), editingState: TGMediaEditingContext())
|
||||||
|
self.interaction?.selectionState?.grouping = true
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(coder aDecoder: NSCoder) {
|
required init(coder aDecoder: NSCoder) {
|
||||||
@ -811,12 +857,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
self.dismiss()
|
self.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func prepareForReuse() {
|
public func resetForReuse() {
|
||||||
if let webSearchController = self.webSearchController {
|
if let webSearchController = self.webSearchController {
|
||||||
self.webSearchController = nil
|
self.webSearchController = nil
|
||||||
webSearchController.dismiss()
|
webSearchController.dismiss()
|
||||||
}
|
}
|
||||||
self.scrollToTop?()
|
self.scrollToTop?()
|
||||||
|
|
||||||
|
self.controllerNode.cameraView?.pausePreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func prepareForReuse() {
|
||||||
|
self.controllerNode.cameraView?.resumePreview()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
|
@objc private func searchOrMorePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
|
||||||
@ -881,7 +933,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||||
if let interaction = self.controllerNode.interaction {
|
if let interaction = self.interaction {
|
||||||
return MediaPickerContext(interaction: interaction)
|
return MediaPickerContext(interaction: interaction)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -189,7 +189,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
|
||||||
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
|
fileprivate let wallpaperBackgroundNode: WallpaperBackgroundNode
|
||||||
@ -226,9 +226,11 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.scrollNode.view.delegate = self
|
self.scrollNode.view.delegate = self
|
||||||
|
self.scrollNode.view.panGestureRecognizer.cancelsTouchesInView = true
|
||||||
|
|
||||||
self.view.addGestureRecognizer(ReorderingGestureRecognizer(shouldBegin: { [weak self] point in
|
self.view.addGestureRecognizer(ReorderingGestureRecognizer(shouldBegin: { [weak self] point in
|
||||||
if let strongSelf = self, !strongSelf.scrollNode.view.isTracking {
|
if let strongSelf = self, !strongSelf.scrollNode.view.isDragging {
|
||||||
|
let point = strongSelf.view.convert(point, to: strongSelf.scrollNode.view)
|
||||||
for (_, itemNode) in strongSelf.itemNodes {
|
for (_, itemNode) in strongSelf.itemNodes {
|
||||||
if itemNode.frame.contains(point) {
|
if itemNode.frame.contains(point) {
|
||||||
return (true, true, itemNode)
|
return (true, true, itemNode)
|
||||||
@ -242,20 +244,54 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}, began: { [weak self] itemNode in
|
}, began: { [weak self] itemNode in
|
||||||
self?.beginReordering(itemNode: itemNode)
|
self?.beginReordering(itemNode: itemNode)
|
||||||
}, ended: { [weak self] point in
|
}, ended: { [weak self] point in
|
||||||
self?.endReordering(point: point)
|
if let strongSelf = self {
|
||||||
|
if var point = point {
|
||||||
|
point = strongSelf.view.convert(point, to: strongSelf.scrollNode.view)
|
||||||
|
strongSelf.endReordering(point: point)
|
||||||
|
} else {
|
||||||
|
strongSelf.endReordering(point: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}, moved: { [weak self] offset in
|
}, moved: { [weak self] offset in
|
||||||
self?.updateReordering(offset: offset)
|
self?.updateReordering(offset: offset)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.1, {
|
||||||
|
self.updateAbsoluteRects()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
self.interaction?.dismissInput()
|
self.interaction?.dismissInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
|
self.updateAbsoluteRects()
|
||||||
|
}
|
||||||
|
|
||||||
func scrollToTop(animated: Bool) {
|
func scrollToTop(animated: Bool) {
|
||||||
self.scrollNode.view.setContentOffset(CGPoint(), animated: animated)
|
self.scrollNode.view.setContentOffset(CGPoint(), animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAbsoluteRects() {
|
||||||
|
guard let messageNodes = self.messageNodes, let (size, _, _, _, _, _, _) = self.validLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for itemNode in messageNodes {
|
||||||
|
var absoluteRect = itemNode.frame
|
||||||
|
if let supernode = self.supernode {
|
||||||
|
absoluteRect = supernode.convert(itemNode.bounds, from: itemNode)
|
||||||
|
}
|
||||||
|
absoluteRect.origin.y = size.height - absoluteRect.origin.y - absoluteRect.size.height
|
||||||
|
itemNode.updateAbsoluteRect(absoluteRect, within: self.bounds.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func beginReordering(itemNode: MediaPickerSelectedItemNode) {
|
private func beginReordering(itemNode: MediaPickerSelectedItemNode) {
|
||||||
self.isReordering = true
|
self.isReordering = true
|
||||||
|
|
||||||
@ -265,7 +301,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
let reorderNode = ReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin)
|
let reorderNode = ReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin)
|
||||||
self.reorderNode = reorderNode
|
self.reorderNode = reorderNode
|
||||||
self.addSubnode(reorderNode)
|
self.scrollNode.addSubnode(reorderNode)
|
||||||
|
|
||||||
itemNode.isHidden = true
|
itemNode.isHidden = true
|
||||||
|
|
||||||
@ -549,6 +585,8 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.updateAbsoluteRects()
|
||||||
|
|
||||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,12 +134,19 @@ public class SearchBarPlaceholderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ constrainedSize: CGSize, _ expansionProgress: CGFloat, _ iconColor: UIColor, _ foregroundColor: UIColor, _ backgroundColor: UIColor, _ transition: ContainedViewLayoutTransition) -> (CGFloat, () -> Void) {
|
public func asyncLayout() -> (_ placeholderString: NSAttributedString?, _ compactPlaceholderString: NSAttributedString?, _ constrainedSize: CGSize, _ expansionProgress: CGFloat, _ iconColor: UIColor, _ foregroundColor: UIColor, _ backgroundColor: UIColor, _ transition: ContainedViewLayoutTransition) -> (CGFloat, () -> Void) {
|
||||||
let labelLayout = TextNode.asyncLayout(self.labelNode)
|
let labelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
let currentForegroundColor = self.foregroundColor
|
let currentForegroundColor = self.foregroundColor
|
||||||
let currentIconColor = self.iconColor
|
let currentIconColor = self.iconColor
|
||||||
|
|
||||||
return { placeholderString, constrainedSize, expansionProgress, iconColor, foregroundColor, backgroundColor, transition in
|
return { fullPlaceholderString, compactPlaceholderString, constrainedSize, expansionProgress, iconColor, foregroundColor, backgroundColor, transition in
|
||||||
|
let placeholderString: NSAttributedString?
|
||||||
|
if constrainedSize.width < 350.0 {
|
||||||
|
placeholderString = compactPlaceholderString
|
||||||
|
} else {
|
||||||
|
placeholderString = fullPlaceholderString
|
||||||
|
}
|
||||||
|
|
||||||
let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (labelLayoutResult, labelApply) = labelLayout(TextNodeLayoutArguments(attributedString: placeholderString, backgroundColor: .clear, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
var updatedColor: UIColor?
|
var updatedColor: UIColor?
|
||||||
|
@ -11,6 +11,7 @@ public let navigationBarSearchContentHeight: CGFloat = 54.0
|
|||||||
public class NavigationBarSearchContentNode: NavigationBarContentNode {
|
public class NavigationBarSearchContentNode: NavigationBarContentNode {
|
||||||
public var theme: PresentationTheme?
|
public var theme: PresentationTheme?
|
||||||
public var placeholder: String
|
public var placeholder: String
|
||||||
|
public var compactPlaceholder: String
|
||||||
|
|
||||||
public let placeholderNode: SearchBarPlaceholderNode
|
public let placeholderNode: SearchBarPlaceholderNode
|
||||||
public var placeholderHeight: CGFloat?
|
public var placeholderHeight: CGFloat?
|
||||||
@ -20,9 +21,10 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
|
|||||||
|
|
||||||
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||||
|
|
||||||
public init(theme: PresentationTheme, placeholder: String, activate: @escaping () -> Void) {
|
public init(theme: PresentationTheme, placeholder: String, compactPlaceholder: String? = nil, activate: @escaping () -> Void) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.placeholder = placeholder
|
self.placeholder = placeholder
|
||||||
|
self.compactPlaceholder = compactPlaceholder ?? placeholder
|
||||||
self.placeholderNode = SearchBarPlaceholderNode(fieldStyle: .modern)
|
self.placeholderNode = SearchBarPlaceholderNode(fieldStyle: .modern)
|
||||||
self.placeholderNode.labelNode.displaysAsynchronously = false
|
self.placeholderNode.labelNode.displaysAsynchronously = false
|
||||||
|
|
||||||
@ -36,9 +38,10 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
|
|||||||
self.placeholderNode.activate = activate
|
self.placeholderNode.activate = activate
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateThemeAndPlaceholder(theme: PresentationTheme, placeholder: String) {
|
public func updateThemeAndPlaceholder(theme: PresentationTheme, placeholder: String, compactPlaceholder: String? = nil) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.placeholder = placeholder
|
self.placeholder = placeholder
|
||||||
|
self.compactPlaceholder = compactPlaceholder ?? placeholder
|
||||||
self.placeholderNode.accessibilityLabel = placeholder
|
self.placeholderNode.accessibilityLabel = placeholder
|
||||||
if let disabledOverlay = self.disabledOverlay {
|
if let disabledOverlay = self.disabledOverlay {
|
||||||
disabledOverlay.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor.withAlphaComponent(0.5)
|
disabledOverlay.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor.withAlphaComponent(0.5)
|
||||||
@ -105,7 +108,11 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode {
|
|||||||
let overscrollProgress = max(0.0, max(0.0, self.expansionProgress - 1.0 + fraction) / fraction - visibleProgress)
|
let overscrollProgress = max(0.0, max(0.0, self.expansionProgress - 1.0 + fraction) / fraction - visibleProgress)
|
||||||
|
|
||||||
let searchBarNodeLayout = self.placeholderNode.asyncLayout()
|
let searchBarNodeLayout = self.placeholderNode.asyncLayout()
|
||||||
let (searchBarHeight, searchBarApply) = searchBarNodeLayout(NSAttributedString(string: self.placeholder, font: searchBarFont, textColor: self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93)), CGSize(width: baseWidth, height: fieldHeight), visibleProgress, self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93), self.theme?.rootController.navigationSearchBar.inputFillColor ?? .clear, self.theme?.rootController.navigationBar.opaqueBackgroundColor ?? .clear, transition)
|
|
||||||
|
let placeholderString = NSAttributedString(string: self.placeholder, font: searchBarFont, textColor: self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93))
|
||||||
|
let compactPlaceholderString = NSAttributedString(string: self.compactPlaceholder, font: searchBarFont, textColor: self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93))
|
||||||
|
|
||||||
|
let (searchBarHeight, searchBarApply) = searchBarNodeLayout(placeholderString, compactPlaceholderString, CGSize(width: baseWidth, height: fieldHeight), visibleProgress, self.theme?.rootController.navigationSearchBar.inputPlaceholderTextColor ?? UIColor(rgb: 0x8e8e93), self.theme?.rootController.navigationSearchBar.inputFillColor ?? .clear, self.theme?.rootController.navigationBar.opaqueBackgroundColor ?? .clear, transition)
|
||||||
searchBarApply()
|
searchBarApply()
|
||||||
|
|
||||||
let searchBarFrame = CGRect(origin: CGPoint(x: padding + leftInset, y: 8.0 + overscrollProgress * fieldHeight), size: CGSize(width: baseWidth, height: fieldHeight))
|
let searchBarFrame = CGRect(origin: CGPoint(x: padding + leftInset, y: 8.0 + overscrollProgress * fieldHeight), size: CGSize(width: baseWidth, height: fieldHeight))
|
||||||
|
@ -156,7 +156,7 @@ public final class SearchDisplayController {
|
|||||||
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: safeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition)
|
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: safeInsets, additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarHeight, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func activate(insertSubnode: @escaping (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?) {
|
public func activate(insertSubnode: @escaping (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?, focus: Bool = true) {
|
||||||
guard let (layout, navigationBarHeight) = self.containerLayout else {
|
guard let (layout, navigationBarHeight) = self.containerLayout else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -231,7 +231,9 @@ public final class SearchDisplayController {
|
|||||||
insertSubnode(self.searchBar, true)
|
insertSubnode(self.searchBar, true)
|
||||||
self.searchBar.layout()
|
self.searchBar.layout()
|
||||||
|
|
||||||
self.searchBar.activate()
|
if focus {
|
||||||
|
self.searchBar.activate()
|
||||||
|
}
|
||||||
if let placeholder = placeholder {
|
if let placeholder = placeholder {
|
||||||
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
if self.contentNode.hasDim {
|
if self.contentNode.hasDim {
|
||||||
|
@ -112,7 +112,8 @@ class NotificationSearchItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
let backgroundColor = item.theme.chatList.itemBackgroundColor
|
let backgroundColor = item.theme.chatList.itemBackgroundColor
|
||||||
|
|
||||||
let (_, searchBarApply) = searchBarNodeLayout(NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93)), CGSize(width: baseWidth - 16.0, height: 28.0), 1.0, UIColor(rgb: 0x8e8e93), item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
|
let placeholderString = NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93))
|
||||||
|
let (_, searchBarApply) = searchBarNodeLayout(placeholderString, placeholderString, CGSize(width: baseWidth - 16.0, height: 28.0), 1.0, UIColor(rgb: 0x8e8e93), item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
|
||||||
|
|
||||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets())
|
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets())
|
||||||
|
|
||||||
|
@ -1511,12 +1511,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
switch joinCallResult.connectionMode {
|
switch joinCallResult.connectionMode {
|
||||||
case .rtc:
|
case .rtc:
|
||||||
strongSelf.currentConnectionMode = .rtc
|
strongSelf.currentConnectionMode = .rtc
|
||||||
strongSelf.genericCallContext?.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false)
|
strongSelf.genericCallContext?.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: false)
|
||||||
strongSelf.genericCallContext?.setJoinResponse(payload: clientParams)
|
strongSelf.genericCallContext?.setJoinResponse(payload: clientParams)
|
||||||
case let .broadcast(isExternalStream):
|
case let .broadcast(isExternalStream):
|
||||||
strongSelf.currentConnectionMode = .broadcast
|
strongSelf.currentConnectionMode = .broadcast
|
||||||
strongSelf.genericCallContext?.setAudioStreamData(audioStreamData: OngoingGroupCallContext.AudioStreamData(engine: strongSelf.accountContext.engine, callId: callInfo.id, accessHash: callInfo.accessHash, isExternalStream: isExternalStream))
|
strongSelf.genericCallContext?.setAudioStreamData(audioStreamData: OngoingGroupCallContext.AudioStreamData(engine: strongSelf.accountContext.engine, callId: callInfo.id, accessHash: callInfo.accessHash, isExternalStream: isExternalStream))
|
||||||
strongSelf.genericCallContext?.setConnectionMode(.broadcast, keepBroadcastConnectedIfWasEnabled: false)
|
strongSelf.genericCallContext?.setConnectionMode(.broadcast, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: isExternalStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl)
|
strongSelf.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl)
|
||||||
@ -1784,15 +1784,25 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let chatPeer = self.accountContext.account.postbox.peerView(id: self.peerId)
|
||||||
|
|> map { view -> Peer? in
|
||||||
|
if let peer = peerViewMainPeer(view) {
|
||||||
|
return peer
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||||
participantsContext.state,
|
participantsContext.state,
|
||||||
participantsContext.activeSpeakers,
|
participantsContext.activeSpeakers,
|
||||||
self.speakingParticipantsContext.get(),
|
self.speakingParticipantsContext.get(),
|
||||||
adminIds,
|
adminIds,
|
||||||
myPeer,
|
myPeer,
|
||||||
|
chatPeer,
|
||||||
accountContext.account.postbox.peerView(id: peerId),
|
accountContext.account.postbox.peerView(id: peerId),
|
||||||
self.isReconnectingAsSpeakerPromise.get()
|
self.isReconnectingAsSpeakerPromise.get()
|
||||||
).start(next: { [weak self] state, activeSpeakers, speakingParticipants, adminIds, myPeerAndCachedData, view, isReconnectingAsSpeaker in
|
).start(next: { [weak self] state, activeSpeakers, speakingParticipants, adminIds, myPeerAndCachedData, chatPeer, view, isReconnectingAsSpeaker in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1874,6 +1884,30 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
|
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let chatPeer = chatPeer, !participants.contains(where: { $0.peer.id == chatPeer.id }) {
|
||||||
|
participants.append(GroupCallParticipantsContext.Participant(
|
||||||
|
peer: chatPeer,
|
||||||
|
ssrc: 100,
|
||||||
|
videoDescription: GroupCallParticipantsContext.Participant.VideoDescription(
|
||||||
|
endpointId: "unified",
|
||||||
|
ssrcGroups: [],
|
||||||
|
audioSsrc: 100,
|
||||||
|
isPaused: false
|
||||||
|
),
|
||||||
|
presentationDescription: nil,
|
||||||
|
joinTimestamp: strongSelf.temporaryJoinTimestamp,
|
||||||
|
raiseHandRating: nil,
|
||||||
|
hasRaiseHand: false,
|
||||||
|
activityTimestamp: nil,
|
||||||
|
activityRank: nil,
|
||||||
|
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: false, mutedByYou: false),
|
||||||
|
volume: nil,
|
||||||
|
about: nil,
|
||||||
|
joinedVideo: false
|
||||||
|
))
|
||||||
|
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
|
||||||
|
}
|
||||||
|
|
||||||
var otherParticipantsWithVideo = 0
|
var otherParticipantsWithVideo = 0
|
||||||
var videoWatchingParticipants = 0
|
var videoWatchingParticipants = 0
|
||||||
@ -2691,7 +2725,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
let clientParams = joinCallResult.jsonParams
|
let clientParams = joinCallResult.jsonParams
|
||||||
|
|
||||||
screencastCallContext.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false)
|
screencastCallContext.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: false)
|
||||||
screencastCallContext.setJoinResponse(payload: clientParams)
|
screencastCallContext.setJoinResponse(payload: clientParams)
|
||||||
}, error: { error in
|
}, error: { error in
|
||||||
guard let _ = self else {
|
guard let _ = self else {
|
||||||
@ -2885,7 +2919,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
if !self.didInitializeConnectionMode || self.currentConnectionMode != .none {
|
if !self.didInitializeConnectionMode || self.currentConnectionMode != .none {
|
||||||
self.didInitializeConnectionMode = true
|
self.didInitializeConnectionMode = true
|
||||||
self.currentConnectionMode = .none
|
self.currentConnectionMode = .none
|
||||||
self.genericCallContext?.setConnectionMode(.none, keepBroadcastConnectedIfWasEnabled: movingFromBroadcastToRtc)
|
self.genericCallContext?.setConnectionMode(.none, keepBroadcastConnectedIfWasEnabled: movingFromBroadcastToRtc, isUnifiedBroadcast: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.internalState = .requesting
|
self.internalState = .requesting
|
||||||
|
@ -190,6 +190,13 @@ public func addRecentDownloadItem(postbox: Postbox, item: RecentDownloadItem) ->
|
|||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func clearRecentDownloadList(postbox: Postbox) -> Signal<Never, NoError> {
|
||||||
|
return postbox.transaction { transaction -> Void in
|
||||||
|
transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.RecentDownloads, items: [])
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|
||||||
public func markRecentDownloadItemsAsSeen(postbox: Postbox, items: [(messageId: MessageId, resourceId: String)]) -> Signal<Never, NoError> {
|
public func markRecentDownloadItemsAsSeen(postbox: Postbox, items: [(messageId: MessageId, resourceId: String)]) -> Signal<Never, NoError> {
|
||||||
return postbox.transaction { transaction -> Void in
|
return postbox.transaction { transaction -> Void in
|
||||||
var unseenIds: [(messageId: MessageId, resourceId: String)] = []
|
var unseenIds: [(messageId: MessageId, resourceId: String)] = []
|
||||||
|
@ -863,6 +863,13 @@ public final class GroupCallParticipantsContext {
|
|||||||
public var ssrcGroups: [SsrcGroup]
|
public var ssrcGroups: [SsrcGroup]
|
||||||
public var audioSsrc: UInt32?
|
public var audioSsrc: UInt32?
|
||||||
public var isPaused: Bool
|
public var isPaused: Bool
|
||||||
|
|
||||||
|
public init(endpointId: String, ssrcGroups: [SsrcGroup], audioSsrc: UInt32?, isPaused: Bool) {
|
||||||
|
self.endpointId = endpointId
|
||||||
|
self.ssrcGroups = ssrcGroups
|
||||||
|
self.audioSsrc = audioSsrc
|
||||||
|
self.isPaused = isPaused
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var peer: Peer
|
public var peer: Peer
|
||||||
|
@ -164,7 +164,7 @@ private class AttachmentFileControllerImpl: ItemListController, AttachmentContai
|
|||||||
public var requestAttachmentMenuExpansion: () -> Void = {}
|
public var requestAttachmentMenuExpansion: () -> Void = {}
|
||||||
|
|
||||||
var prepareForReuseImpl: () -> Void = {}
|
var prepareForReuseImpl: () -> Void = {}
|
||||||
public func prepareForReuse() {
|
public func resetForReuse() {
|
||||||
self.prepareForReuseImpl()
|
self.prepareForReuseImpl()
|
||||||
self.scrollToTop?()
|
self.scrollToTop?()
|
||||||
}
|
}
|
||||||
|
@ -9121,6 +9121,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var layout = layout
|
||||||
|
if let _ = self.attachmentController {
|
||||||
|
layout = layout.withUpdatedInputHeight(nil)
|
||||||
|
}
|
||||||
|
|
||||||
var navigationBarTransition = transition
|
var navigationBarTransition = transition
|
||||||
self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
|
self.chatDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, listViewTransaction: { updateSizeAndInsets, additionalScrollDistance, scrollToTop, completion in
|
||||||
self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
|
self.chatDisplayNode.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, additionalScrollDistance: additionalScrollDistance, scrollToTop: scrollToTop, completion: completion)
|
||||||
@ -10320,18 +10325,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.chatDisplayNode.dismissInput()
|
self.chatDisplayNode.dismissInput()
|
||||||
|
|
||||||
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
var bannedSendMedia: (Int32, Bool)?
|
||||||
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
|
||||||
|
|
||||||
var canSendPolls = true
|
var canSendPolls = true
|
||||||
if peer is TelegramUser || peer is TelegramSecretChat {
|
if peer is TelegramUser || peer is TelegramSecretChat {
|
||||||
canSendPolls = false
|
canSendPolls = false
|
||||||
} else if let channel = peer as? TelegramChannel {
|
} else if let channel = peer as? TelegramChannel {
|
||||||
|
if let value = channel.hasBannedPermission(.banSendMedia) {
|
||||||
|
bannedSendMedia = value
|
||||||
|
}
|
||||||
if channel.hasBannedPermission(.banSendPolls) != nil {
|
if channel.hasBannedPermission(.banSendPolls) != nil {
|
||||||
canSendPolls = false
|
canSendPolls = false
|
||||||
}
|
}
|
||||||
} else if let group = peer as? TelegramGroup {
|
} else if let group = peer as? TelegramGroup {
|
||||||
|
if group.hasBannedPermission(.banSendMedia) {
|
||||||
|
bannedSendMedia = (Int32.max, false)
|
||||||
|
}
|
||||||
if group.hasBannedPermission(.banSendPolls) {
|
if group.hasBannedPermission(.banSendPolls) {
|
||||||
canSendPolls = false
|
canSendPolls = false
|
||||||
}
|
}
|
||||||
@ -10344,6 +10353,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
|
|
||||||
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText
|
||||||
|
|
||||||
|
let currentMediaController = Atomic<AttachmentContainable?>(value: nil)
|
||||||
|
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
||||||
|
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
||||||
|
|
||||||
let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, buttons: availableTabs)
|
let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, buttons: availableTabs)
|
||||||
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
|
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -10351,7 +10364,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
switch type {
|
switch type {
|
||||||
case .gallery:
|
case .gallery:
|
||||||
strongSelf.presentMediaPicker(present: { controller, mediaPickerContext in
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
|
let existingController = currentMediaController.with { $0 }
|
||||||
|
if let controller = existingController {
|
||||||
|
controller.prepareForReuse()
|
||||||
|
completion(controller, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.presentMediaPicker(bannedSendMedia: bannedSendMedia, present: { controller, mediaPickerContext in
|
||||||
|
let _ = currentMediaController.swap(controller)
|
||||||
completion(controller, mediaPickerContext)
|
completion(controller, mediaPickerContext)
|
||||||
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
||||||
attachmentController?.mediaPickerContext = mediaPickerContext
|
attachmentController?.mediaPickerContext = mediaPickerContext
|
||||||
@ -10361,7 +10382,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime)
|
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime)
|
||||||
})
|
})
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
|
||||||
case .file:
|
case .file:
|
||||||
strongSelf.controllerNavigationDisposable.set(nil)
|
strongSelf.controllerNavigationDisposable.set(nil)
|
||||||
let existingController = currentFilesController.with { $0 }
|
let existingController = currentFilesController.with { $0 }
|
||||||
@ -10978,11 +10998,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.present(actionSheet, in: .window(.root))
|
self.present(actionSheet, in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func presentMediaPicker(present: @escaping (AttachmentContainable, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) {
|
private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (AttachmentContainable, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) {
|
||||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation)
|
let controller = MediaPickerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peer: EnginePeer(peer), chatLocation: self.chatLocation, bannedSendMedia: bannedSendMedia)
|
||||||
let mediaPickerContext = controller.mediaPickerContext
|
let mediaPickerContext = controller.mediaPickerContext
|
||||||
controller.openCamera = { [weak self] cameraView in
|
controller.openCamera = { [weak self] cameraView in
|
||||||
self?.openCamera(cameraView: cameraView)
|
self?.openCamera(cameraView: cameraView)
|
||||||
|
@ -20,7 +20,7 @@ import ContextUI
|
|||||||
import ChatPresentationInterfaceState
|
import ChatPresentationInterfaceState
|
||||||
|
|
||||||
private struct FetchControls {
|
private struct FetchControls {
|
||||||
let fetch: () -> Void
|
let fetch: (Bool) -> Void
|
||||||
let cancel: () -> Void
|
let cancel: () -> Void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +228,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
case .Remote, .Paused:
|
case .Remote, .Paused:
|
||||||
if let fetch = self.fetchControls.with({ return $0?.fetch }) {
|
if let fetch = self.fetchControls.with({ return $0?.fetch }) {
|
||||||
fetch()
|
fetch(true)
|
||||||
}
|
}
|
||||||
case .Local:
|
case .Local:
|
||||||
break
|
break
|
||||||
@ -251,7 +251,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
case .Remote, .Paused:
|
case .Remote, .Paused:
|
||||||
if let fetch = self.fetchControls.with({ return $0?.fetch }) {
|
if let fetch = self.fetchControls.with({ return $0?.fetch }) {
|
||||||
fetch()
|
fetch(true)
|
||||||
}
|
}
|
||||||
case .Local:
|
case .Local:
|
||||||
self.activateLocalContent()
|
self.activateLocalContent()
|
||||||
@ -316,9 +316,9 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
updateImageSignal = chatMessageImageFile(account: arguments.context.account, fileReference: .message(message: MessageReference(arguments.message), media: arguments.file), thumbnail: true)
|
updateImageSignal = chatMessageImageFile(account: arguments.context.account, fileReference: .message(message: MessageReference(arguments.message), media: arguments.file), thumbnail: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedFetchControls = FetchControls(fetch: { [weak self] in
|
updatedFetchControls = FetchControls(fetch: { [weak self] userInitiated in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: arguments.context, message: arguments.message, file: arguments.file, userInitiated: true).start())
|
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: arguments.context, message: arguments.message, file: arguments.file, userInitiated: userInitiated).start())
|
||||||
}
|
}
|
||||||
}, cancel: {
|
}, cancel: {
|
||||||
messageMediaFileCancelInteractiveFetch(context: arguments.context, messageId: arguments.message.id, file: arguments.file)
|
messageMediaFileCancelInteractiveFetch(context: arguments.context, messageId: arguments.message.id, file: arguments.file)
|
||||||
@ -331,15 +331,15 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
|> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in
|
|> map { resourceStatus, actualFetchStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in
|
||||||
return (resourceStatus, actualFetchStatus)
|
return (resourceStatus, actualFetchStatus)
|
||||||
}
|
}
|
||||||
updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false)
|
updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false)
|
||||||
} else {
|
} else {
|
||||||
updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions)
|
updatedStatusSignal = messageFileMediaResourceStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions)
|
||||||
|> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in
|
|> map { resourceStatus -> (FileMediaResourceStatus, MediaResourceStatus?) in
|
||||||
return (resourceStatus, nil)
|
return (resourceStatus, nil)
|
||||||
}
|
}
|
||||||
updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false)
|
updatedAudioLevelEventsSignal = messageFileMediaPlaybackAudioLevelEvents(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false)
|
||||||
}
|
}
|
||||||
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false)
|
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: arguments.context, file: arguments.file, message: arguments.message, isRecentActions: arguments.isRecentActions, isGlobalSearch: false, isDownloadList: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
var consumableContentIcon: UIImage?
|
var consumableContentIcon: UIImage?
|
||||||
@ -765,7 +765,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
if let updatedFetchControls = updatedFetchControls {
|
if let updatedFetchControls = updatedFetchControls {
|
||||||
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
|
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
|
||||||
if arguments.automaticDownload {
|
if arguments.automaticDownload {
|
||||||
updatedFetchControls.fetch()
|
updatedFetchControls.fetch(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -720,7 +720,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5)
|
playbackStatusNode.frame = videoFrame.insetBy(dx: 1.5, dy: 1.5)
|
||||||
|
|
||||||
let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false)
|
let status = messageFileMediaPlaybackStatus(context: item.context, file: file, message: item.message, isRecentActions: item.associatedData.isRecentActions, isGlobalSearch: false, isDownloadList: false)
|
||||||
playbackStatusNode.status = status
|
playbackStatusNode.status = status
|
||||||
self.durationNode?.status = status
|
self.durationNode?.status = status
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|
@ -58,6 +58,8 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource {
|
|||||||
return self.engine.calls.requestStreamState(callId: self.callId, accessHash: self.accessHash).start(next: { result in
|
return self.engine.calls.requestStreamState(callId: self.callId, accessHash: self.accessHash).start(next: { result in
|
||||||
if let channel = result?.channels.first {
|
if let channel = result?.channels.first {
|
||||||
completion(channel.latestTimestamp)
|
completion(channel.latestTimestamp)
|
||||||
|
} else {
|
||||||
|
completion(0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -124,6 +126,11 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource {
|
|||||||
let part: OngoingGroupCallBroadcastPart
|
let part: OngoingGroupCallBroadcastPart
|
||||||
switch result.status {
|
switch result.status {
|
||||||
case let .data(dataValue):
|
case let .data(dataValue):
|
||||||
|
/*#if DEBUG
|
||||||
|
let tempFile = EngineTempBox.shared.tempFile(fileName: "part.mp4")
|
||||||
|
let _ = try? dataValue.write(to: URL(fileURLWithPath: tempFile.path))
|
||||||
|
print("Dump stream part: \(tempFile.path)")
|
||||||
|
#endif*/
|
||||||
part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .success, oggData: dataValue)
|
part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .success, oggData: dataValue)
|
||||||
case .notReady:
|
case .notReady:
|
||||||
part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .notReady, oggData: Data())
|
part = OngoingGroupCallBroadcastPart(timestampMilliseconds: timestampIdMilliseconds, responseTimestamp: result.responseTimestamp, status: .notReady, oggData: Data())
|
||||||
@ -606,7 +613,7 @@ public final class OngoingGroupCallContext {
|
|||||||
self.context.stop()
|
self.context.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool) {
|
func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool, isUnifiedBroadcast: Bool) {
|
||||||
let mappedConnectionMode: OngoingCallConnectionMode
|
let mappedConnectionMode: OngoingCallConnectionMode
|
||||||
switch connectionMode {
|
switch connectionMode {
|
||||||
case .none:
|
case .none:
|
||||||
@ -616,7 +623,7 @@ public final class OngoingGroupCallContext {
|
|||||||
case .broadcast:
|
case .broadcast:
|
||||||
mappedConnectionMode = .broadcast
|
mappedConnectionMode = .broadcast
|
||||||
}
|
}
|
||||||
self.context.setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled)
|
self.context.setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled, isUnifiedBroadcast: isUnifiedBroadcast)
|
||||||
|
|
||||||
if (mappedConnectionMode != .rtc) {
|
if (mappedConnectionMode != .rtc) {
|
||||||
self.joinPayload.set(.never())
|
self.joinPayload.set(.never())
|
||||||
@ -900,9 +907,9 @@ public final class OngoingGroupCallContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool) {
|
public func setConnectionMode(_ connectionMode: ConnectionMode, keepBroadcastConnectedIfWasEnabled: Bool, isUnifiedBroadcast: Bool) {
|
||||||
self.impl.with { impl in
|
self.impl.with { impl in
|
||||||
impl.setConnectionMode(connectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled)
|
impl.setConnectionMode(connectionMode, keepBroadcastConnectedIfWasEnabled: keepBroadcastConnectedIfWasEnabled, isUnifiedBroadcast: isUnifiedBroadcast)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,7 +354,7 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
|
|||||||
|
|
||||||
- (void)stop;
|
- (void)stop;
|
||||||
|
|
||||||
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled;
|
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast;
|
||||||
|
|
||||||
- (void)emitJoinPayload:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion;
|
- (void)emitJoinPayload:(void (^ _Nonnull)(NSString * _Nonnull, uint32_t))completion;
|
||||||
- (void)setJoinResponsePayload:(NSString * _Nonnull)payload;
|
- (void)setJoinResponsePayload:(NSString * _Nonnull)payload;
|
||||||
|
@ -1580,7 +1580,7 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled {
|
- (void)setConnectionMode:(OngoingCallConnectionMode)connectionMode keepBroadcastConnectedIfWasEnabled:(bool)keepBroadcastConnectedIfWasEnabled isUnifiedBroadcast:(bool)isUnifiedBroadcast {
|
||||||
if (_instance) {
|
if (_instance) {
|
||||||
tgcalls::GroupConnectionMode mappedConnectionMode;
|
tgcalls::GroupConnectionMode mappedConnectionMode;
|
||||||
switch (connectionMode) {
|
switch (connectionMode) {
|
||||||
@ -1601,7 +1601,7 @@ private:
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_instance->setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled);
|
_instance->setConnectionMode(mappedConnectionMode, keepBroadcastConnectedIfWasEnabled, isUnifiedBroadcast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 20860ca29147b4faa4f0b75a0da58517d9d4856c
|
Subproject commit 382d1b6756768021274cb1edfc1e144cfb101fb8
|
@ -38,6 +38,7 @@ public enum UndoOverlayContent {
|
|||||||
case mediaSaved(text: String)
|
case mediaSaved(text: String)
|
||||||
case paymentSent(currencyValue: String, itemTitle: String)
|
case paymentSent(currencyValue: String, itemTitle: String)
|
||||||
case inviteRequestSent(title: String, text: String)
|
case inviteRequestSent(title: String, text: String)
|
||||||
|
case image(image: UIImage, text: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum UndoOverlayAction {
|
public enum UndoOverlayAction {
|
||||||
|
@ -749,6 +749,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.maximumNumberOfLines = 2
|
self.textNode.maximumNumberOfLines = 2
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 5
|
self.originalRemainingSeconds = 5
|
||||||
|
case let .image(image, text):
|
||||||
|
self.avatarNode = nil
|
||||||
|
self.iconNode = ASImageNode()
|
||||||
|
self.iconNode?.image = image
|
||||||
|
self.iconCheckNode = nil
|
||||||
|
self.animationNode = nil
|
||||||
|
self.animatedStickerNode = nil
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
|
||||||
|
displayUndo = true
|
||||||
|
self.originalRemainingSeconds = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
self.remainingSeconds = self.originalRemainingSeconds
|
self.remainingSeconds = self.originalRemainingSeconds
|
||||||
@ -777,7 +787,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
switch content {
|
switch content {
|
||||||
case .removedChat:
|
case .removedChat:
|
||||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .inviteRequestSent:
|
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent:
|
||||||
break
|
break
|
||||||
case .dice:
|
case .dice:
|
||||||
self.panelWrapperNode.clipsToBounds = true
|
self.panelWrapperNode.clipsToBounds = true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user