diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index 3cc96206d7..5c1f0738cf 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ D0E7A1BF1D8C24B900C37A6F /* ChatHistoryViewForLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E7A1BE1D8C24B900C37A6F /* ChatHistoryViewForLocation.swift */; }; D0E7A1C11D8C258D00C37A6F /* ChatHistoryEntriesForView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E7A1C01D8C258D00C37A6F /* ChatHistoryEntriesForView.swift */; }; D0E7A1C31D8C25D600C37A6F /* PreparedChatHistoryViewTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E7A1C21D8C25D600C37A6F /* PreparedChatHistoryViewTransition.swift */; }; + D0ED5D4B1DC806D7007CBB15 /* ApplicationSpecificData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ED5D4A1DC806D7007CBB15 /* ApplicationSpecificData.swift */; }; D0EE971A1D88BCA0006C18E1 /* ChatInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EE97191D88BCA0006C18E1 /* ChatInfo.swift */; }; D0F69D231D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69CD31D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift */; }; D0F69D241D6B87D30046BCD6 /* MediaPlayerAudioRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F69CD41D6B87D30046BCD6 /* MediaPlayerAudioRenderer.swift */; }; @@ -304,6 +305,7 @@ D0E7A1BE1D8C24B900C37A6F /* ChatHistoryViewForLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryViewForLocation.swift; sourceTree = ""; }; D0E7A1C01D8C258D00C37A6F /* ChatHistoryEntriesForView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatHistoryEntriesForView.swift; sourceTree = ""; }; D0E7A1C21D8C25D600C37A6F /* PreparedChatHistoryViewTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreparedChatHistoryViewTransition.swift; sourceTree = ""; }; + D0ED5D4A1DC806D7007CBB15 /* ApplicationSpecificData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationSpecificData.swift; sourceTree = ""; }; D0EE97191D88BCA0006C18E1 /* ChatInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInfo.swift; sourceTree = ""; }; D0F69CD31D6B87D30046BCD6 /* FFMpegMediaFrameSourceContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FFMpegMediaFrameSourceContext.swift; sourceTree = ""; }; D0F69CD41D6B87D30046BCD6 /* MediaPlayerAudioRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPlayerAudioRenderer.swift; sourceTree = ""; }; @@ -1061,6 +1063,7 @@ D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */, D0F69E941D6B8C9B0046BCD6 /* WebP.swift */, D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */, + D0ED5D4A1DC806D7007CBB15 /* ApplicationSpecificData.swift */, ); name = Utils; sourceTree = ""; @@ -1344,6 +1347,7 @@ D0F69D771D6B87DF0046BCD6 /* FFMpegMediaPassthroughVideoFrameDecoder.swift in Sources */, D0F69DFE1D6B8A880046BCD6 /* AvatarNode.swift in Sources */, D0F69E9B1D6B8D200046BCD6 /* UIImage+WebP.m in Sources */, + D0ED5D4B1DC806D7007CBB15 /* ApplicationSpecificData.swift in Sources */, D0F69E581D6B8BDA0046BCD6 /* GalleryItemNode.swift in Sources */, D0F69E971D6B8C9B0046BCD6 /* WebP.swift in Sources */, D0F69E2F1D6B8B030046BCD6 /* ChatMessageBubbleContentCalclulateImageCorners.swift in Sources */, diff --git a/TelegramUI/ApplicationSpecificData.swift b/TelegramUI/ApplicationSpecificData.swift new file mode 100644 index 0000000000..8595777add --- /dev/null +++ b/TelegramUI/ApplicationSpecificData.swift @@ -0,0 +1,6 @@ +import Foundation +import SwiftSignalKit + +final class ApplicationSpecificData { + let sharedChatMediaInputNode = Atomic(value: nil) +} diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 7d894519a9..0d29202b48 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -163,6 +163,7 @@ public class ChatController: ViewController { } }, sendSticker: { [weak self] file in if let strongSelf = self { + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({}) enqueueMessage(account: strongSelf.account, peerId: strongSelf.peerId, text: "", replyMessageId: nil, media: file).start() } }) @@ -258,6 +259,7 @@ public class ChatController: ViewController { let _ = options.insert(.Synchronous) let _ = options.insert(.LowLatency) options.remove(.AnimateInsertion) + options.insert(.RequestItemInsertionAnimations) let deleteItems = transition.deleteItems.map({ item in return ListViewDeleteItem(index: item.index, directionHint: nil) @@ -306,6 +308,7 @@ public class ChatController: ViewController { let scaledSize = size.aspectFitted(CGSize(width: 1280.0, height: 1280.0)) let resource = PhotoLibraryMediaResource(localIdentifier: asset.localIdentifier) let media = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: randomId), representations: [TelegramMediaImageRepresentation(dimensions: scaledSize, resource: resource)]) + strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({}) enqueueMessage(account: strongSelf.account, peerId: strongSelf.peerId, text: "", replyMessageId: nil, media: media).start() } } @@ -380,7 +383,7 @@ public class ChatController: ViewController { super.viewDidAppear(animated) self.chatDisplayNode.historyNode.preloadPages = true - self.chatDisplayNode.historyNode.canReadHistory.set(.single(true)) + self.chatDisplayNode.historyNode.canReadHistory.set(true) self.chatDisplayNode.loadInputPanels() } diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index c223f7eab3..d8358114b5 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -506,10 +506,11 @@ class ChatControllerNode: ASDisplayNode { } func loadInputPanels() { - /*if self.inputMediaNode == nil { + if self.inputMediaNode == nil { let inputNode = ChatMediaInputNode(account: self.account, controllerInteraction: self.controllerInteraction) inputNode.interfaceInteraction = interfaceInteraction self.inputMediaNode = inputNode - }*/ + inputNode.updateLayout(width: self.bounds.size.width, transition: .immediate, interfaceState: self.chatPresentationInterfaceState) + } } } diff --git a/TelegramUI/ChatHistoryGridNode.swift b/TelegramUI/ChatHistoryGridNode.swift index aaeb695b10..f12b08791a 100644 --- a/TelegramUI/ChatHistoryGridNode.swift +++ b/TelegramUI/ChatHistoryGridNode.swift @@ -54,7 +54,25 @@ private func mappedChatHistoryViewListTransition(account: Account, peerId: PeerI case .Bottom: mappedPosition = .bottom } - mappedScrollToItem = GridNodeScrollToItem(index: scrollToItem.index, position: mappedPosition) + let scrollTransition: ContainedViewLayoutTransition + if scrollToItem.animated { + switch scrollToItem.curve { + case .Default: + scrollTransition = .animated(duration: 0.3, curve: .easeInOut) + case let .Spring(duration): + scrollTransition = .animated(duration: duration, curve: .spring) + } + } else { + scrollTransition = .immediate + } + let directionHint: GridNodePreviousItemsTransitionDirectionHint + switch scrollToItem.directionHint { + case .Up: + directionHint = .up + case .Down: + directionHint = .down + } + mappedScrollToItem = GridNodeScrollToItem(index: scrollToItem.index, position: mappedPosition, transition: scrollTransition, directionHint: directionHint, adjustForSection: true) } return ChatHistoryGridViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems.map { $0.index }, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, entries: transition.updateEntries), scrollToItem: mappedScrollToItem, stationaryItemRange: transition.stationaryItemRange) diff --git a/TelegramUI/ChatHistoryListNode.swift b/TelegramUI/ChatHistoryListNode.swift index a614db0374..88bea0c2fb 100644 --- a/TelegramUI/ChatHistoryListNode.swift +++ b/TelegramUI/ChatHistoryListNode.swift @@ -124,6 +124,14 @@ private func mappedChatHistoryViewListTransition(account: Account, peerId: PeerI return ChatHistoryListViewTransition(historyView: transition.historyView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(account: account, peerId: peerId, controllerInteraction: controllerInteraction, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange) } +private final class ChatHistoryTransactionOpaqueState { + let historyView: ChatHistoryView + + init(historyView: ChatHistoryView) { + self.historyView = historyView + } +} + public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let account: Account private let peerId: PeerId @@ -145,10 +153,10 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { public let historyReady = Promise() private var didSetHistoryReady = false - private let maxVisibleIncomingMessageId = Promise() - let canReadHistory = Promise() + private let maxVisibleIncomingMessageId = ValuePromise() + let canReadHistory = ValuePromise() - private let _chatHistoryLocation = Promise() + private let _chatHistoryLocation = ValuePromise() private var chatHistoryLocation: Signal { return self._chatHistoryLocation.get() } @@ -164,6 +172,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { super.init() + //self.debugInfo = true + self.preloadPages = false switch self.mode { case .bubbles: @@ -263,32 +273,26 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.readHistoryDisposable.set(readHistory.start()) if let messageId = messageId { - self._chatHistoryLocation.set(.single(ChatHistoryLocation.InitialSearch(messageId: messageId, count: 60))) + self._chatHistoryLocation.set(ChatHistoryLocation.InitialSearch(messageId: messageId, count: 60)) } else { - self._chatHistoryLocation.set(.single(ChatHistoryLocation.Initial(count: 60))) + self._chatHistoryLocation.set(ChatHistoryLocation.Initial(count: 60)) } - self.displayedItemRangeChanged = { [weak self] displayedRange in + self.displayedItemRangeChanged = { [weak self] displayedRange, opaqueTransactionState in if let strongSelf = self { - /*if let transactionTag = strongSelf.listViewTransactionTag { - strongSelf.messageViewQueue.dispatch { - if transactionTag == strongSelf.historyViewTransactionTag { - if let range = range, historyView = strongSelf.historyView, firstEntry = historyView.filteredEntries.first, lastEntry = historyView.filteredEntries.last { - if range.firstIndex < 5 && historyView.originalView.laterId != nil { - strongSelf._chatHistoryLocation.set(.single(ChatHistoryLocation.Navigation(index: lastEntry.index, anchorIndex: historyView.originalView.anchorIndex))) - } else if range.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { - strongSelf._chatHistoryLocation.set(.single(ChatHistoryLocation.Navigation(index: firstEntry.index, anchorIndex: historyView.originalView.anchorIndex))) - } else { - //strongSelf.account.postbox.updateMessageHistoryViewVisibleRange(messageView.id, earliestVisibleIndex: viewEntries[viewEntries.count - 1 - range.lastIndex].index, latestVisibleIndex: viewEntries[viewEntries.count - 1 - range.firstIndex].index) - } - } - } - } - }*/ - - if let visible = displayedRange.visibleRange, let historyView = strongSelf.historyView { - if let messageId = maxIncomingMessageIdForEntries(historyView.filteredEntries, indexRange: (historyView.filteredEntries.count - 1 - visible.lastIndex, historyView.filteredEntries.count - 1 - visible.firstIndex)) { - strongSelf.updateMaxVisibleReadIncomingMessageId(messageId) + if let historyView = (opaqueTransactionState as? ChatHistoryTransactionOpaqueState)?.historyView { + if let visible = displayedRange.visibleRange { + if let messageId = maxIncomingMessageIdForEntries(historyView.filteredEntries, indexRange: (historyView.filteredEntries.count - 1 - visible.lastIndex, historyView.filteredEntries.count - 1 - visible.firstIndex)) { + strongSelf.updateMaxVisibleReadIncomingMessageId(messageId) + } + } + + if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last { + if loaded.firstIndex < 5 && historyView.originalView.laterId != nil { + strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: lastEntry.index, anchorIndex: historyView.originalView.anchorIndex)) + } else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil { + strongSelf._chatHistoryLocation.set(ChatHistoryLocation.Navigation(index: firstEntry.index, anchorIndex: historyView.originalView.anchorIndex)) + } } } } @@ -301,15 +305,15 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } public func scrollToStartOfHistory() { - self._chatHistoryLocation.set(.single(ChatHistoryLocation.Scroll(index: MessageIndex.lowerBound(peerId: self.peerId), anchorIndex: MessageIndex.lowerBound(peerId: self.peerId), sourceIndex: MessageIndex.upperBound(peerId: self.peerId), scrollPosition: .Bottom, animated: true))) + self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: MessageIndex.lowerBound(peerId: self.peerId), anchorIndex: MessageIndex.lowerBound(peerId: self.peerId), sourceIndex: MessageIndex.upperBound(peerId: self.peerId), scrollPosition: .Bottom, animated: true)) } public func scrollToEndOfHistory() { - self._chatHistoryLocation.set(.single(ChatHistoryLocation.Scroll(index: MessageIndex.upperBound(peerId: self.peerId), anchorIndex: MessageIndex.upperBound(peerId: self.peerId), sourceIndex: MessageIndex.lowerBound(peerId: self.peerId), scrollPosition: .Top, animated: true))) + self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: MessageIndex.upperBound(peerId: self.peerId), anchorIndex: MessageIndex.upperBound(peerId: self.peerId), sourceIndex: MessageIndex.lowerBound(peerId: self.peerId), scrollPosition: .Top, animated: true)) } public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex) { - self._chatHistoryLocation.set(.single(ChatHistoryLocation.Scroll(index: toIndex, anchorIndex: toIndex, sourceIndex: fromIndex, scrollPosition: .Center(.Bottom), animated: true))) + self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: toIndex, anchorIndex: toIndex, sourceIndex: fromIndex, scrollPosition: .Center(.Bottom), animated: true)) } public func messageInCurrentHistoryView(_ id: MessageId) -> Message? { @@ -323,7 +327,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } private func updateMaxVisibleReadIncomingMessageId(_ id: MessageId) { - self.maxVisibleIncomingMessageId.set(.single(id)) + self.maxVisibleIncomingMessageId.set(id) } private func enqueueHistoryViewTransition(_ transition: ChatHistoryListViewTransition) -> Signal { @@ -384,15 +388,15 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.layoutActionOnViewTransition = nil let (mappedTransition, updateSizeAndInsets) = layoutActionOnViewTransition(transition) - self.deleteAndInsertItems(deleteIndices: mappedTransition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: mappedTransition.options, scrollToItem: mappedTransition.scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: mappedTransition.stationaryItemRange, completion: completion) + self.transaction(deleteIndices: mappedTransition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: mappedTransition.options, scrollToItem: mappedTransition.scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: mappedTransition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: completion) } else { - self.deleteAndInsertItems(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, completion: completion) + self.transaction(deleteIndices: transition.deleteItems, insertIndicesAndItems: transition.insertItems, updateIndicesAndItems: transition.updateItems, options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, updateOpaqueState: ChatHistoryTransactionOpaqueState(historyView: transition.historyView), completion: completion) } } } public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { - self.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if !self.dequeuedInitialTransitionOnLayout { self.dequeuedInitialTransitionOnLayout = true diff --git a/TelegramUI/ChatImageGalleryItem.swift b/TelegramUI/ChatImageGalleryItem.swift index e1868e81a7..d7b62f976e 100644 --- a/TelegramUI/ChatImageGalleryItem.swift +++ b/TelegramUI/ChatImageGalleryItem.swift @@ -85,7 +85,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { fileprivate func setImage(account: Account, image: TelegramMediaImage) { if self.accountAndMedia == nil || !self.accountAndMedia!.1.isEqual(image) { if let largestSize = largestRepresentationForPhoto(image) { - let displaySize = largestSize.dimensions.dividedByScreenScale() + let displaySize = largestSize.dimensions.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor self.imageNode.alphaTransitionOnFirstUpdate = false self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))() self.imageNode.setSignal(account: account, signal: chatMessagePhoto(account: account, photo: image), dispatchOnDisplayLink: false) diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index d385d31a3f..13d4972712 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -173,6 +173,14 @@ extension ChatListEntry: Identifiable { } } +private final class ChatListOpaqueTransactionState { + let chatListViewAndEntries: (ChatListView, [ChatListControllerEntry]) + + init(chatListViewAndEntries: (ChatListView, [ChatListControllerEntry])) { + self.chatListViewAndEntries = chatListViewAndEntries + } +} + public class ChatListController: ViewController { let account: Account @@ -209,7 +217,7 @@ public class ChatListController: ViewController { self.scrollToTop = { [weak self] in if let strongSelf = self { if let (view, _) = strongSelf.chatListViewAndEntries, view.laterIndex == nil { - strongSelf.chatListDisplayNode.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in }) + strongSelf.chatListDisplayNode.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else { strongSelf.setMessageViewPosition(.Around(index: MessageIndex.absoluteUpperBound(), anchorIndex: MessageIndex.absoluteUpperBound(), scrollPosition: .Top), hint: "later", force: true) } @@ -231,9 +239,9 @@ public class ChatListController: ViewController { override public func loadDisplayNode() { self.displayNode = ChatListControllerNode(account: self.account) - self.chatListDisplayNode.listView.displayedItemRangeChanged = { [weak self] range in + self.chatListDisplayNode.listView.displayedItemRangeChanged = { [weak self] range, transactionOpaqueState in if let strongSelf = self, !strongSelf.settingView { - if let range = range.loadedRange, let (view, _) = strongSelf.chatListViewAndEntries { + if let range = range.loadedRange, let (view, _) = (transactionOpaqueState as? ChatListOpaqueTransactionState)?.chatListViewAndEntries { if range.firstIndex < 5 && view.laterIndex != nil { strongSelf.setMessageViewPosition(.Around(index: view.entries[view.entries.count - 1].index, anchorIndex: MessageIndex.absoluteUpperBound(), scrollPosition: nil), hint: "later", force: false) } else if range.firstIndex >= 5 && range.lastIndex >= view.entries.count - 5 && view.earlierIndex != nil { @@ -477,7 +485,7 @@ public class ChatListController: ViewController { } } - strongSelf.chatListDisplayNode.listView.deleteAndInsertItems(deleteIndices: adjustedDeleteIndices, insertIndicesAndItems: adjustedIndicesAndItems, updateIndicesAndItems: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, completion: { [weak self] _ in + strongSelf.chatListDisplayNode.listView.transaction(deleteIndices: adjustedDeleteIndices, insertIndicesAndItems: adjustedIndicesAndItems, updateIndicesAndItems: adjustedUpdateItems, options: options, scrollToItem: scrollToItem, updateOpaqueState: ChatListOpaqueTransactionState(chatListViewAndEntries: (view, viewEntries)), completion: { [weak self] _ in if let strongSelf = self { strongSelf.ready.set(single(true, NoError.self)) strongSelf.settingView = false diff --git a/TelegramUI/ChatListControllerNode.swift b/TelegramUI/ChatListControllerNode.swift index 6a77aebc89..3d4767baa8 100644 --- a/TelegramUI/ChatListControllerNode.swift +++ b/TelegramUI/ChatListControllerNode.swift @@ -63,7 +63,7 @@ class ChatListControllerNode: ASDisplayNode { let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve) - self.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, completion: { _ in }) + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if let searchDisplayController = self.searchDisplayController { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index f22f32de02..086c11e417 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -68,7 +68,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } } - strongSelf.listNode.deleteAndInsertItems(deleteIndices: (0 ..< previousItems.count).map({ ListViewDeleteItem(index: $0, directionHint: nil) }), insertIndicesAndItems: (0 ..< listItems.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: listItems[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: []) + strongSelf.listNode.transaction(deleteIndices: (0 ..< previousItems.count).map({ ListViewDeleteItem(index: $0, directionHint: nil) }), insertIndicesAndItems: (0 ..< listItems.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: listItems[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: [], updateOpaqueState: nil) } })) } @@ -120,6 +120,6 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { } self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.listNode.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.insets(options: [.input]).bottom, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, completion: { _ in }) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: layout.insets(options: [.input]).bottom, right: 0.0), duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } diff --git a/TelegramUI/ChatListSearchRecentPeersNode.swift b/TelegramUI/ChatListSearchRecentPeersNode.swift index 8234c5865a..6afde9817b 100644 --- a/TelegramUI/ChatListSearchRecentPeersNode.swift +++ b/TelegramUI/ChatListSearchRecentPeersNode.swift @@ -29,7 +29,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode { for peer in peers { items.append(HorizontalPeerItem(account: account, peer: peer, action: peerSelected)) } - strongSelf.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: (0 ..< items.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: items[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: []) + strongSelf.listView.transaction(deleteIndices: [], insertIndicesAndItems: (0 ..< items.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: items[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: [], updateOpaqueState: nil) } })) } @@ -52,6 +52,6 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode { self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 92.0, height: bounds.size.width) self.listView.position = CGPoint(x: bounds.size.width / 2.0, y: 92.0 / 2.0 + 29.0) - self.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: 92.0, height: bounds.size.width), insets: UIEdgeInsets(), duration: 0.0, curve: .Default), stationaryItemRange: nil, completion: { _ in }) + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: 92.0, height: bounds.size.width), insets: UIEdgeInsets(), duration: 0.0, curve: .Default), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } diff --git a/TelegramUI/ChatMediaActionSheetRollItem.swift b/TelegramUI/ChatMediaActionSheetRollItem.swift index fd69b44694..fe9e3b2576 100644 --- a/TelegramUI/ChatMediaActionSheetRollItem.swift +++ b/TelegramUI/ChatMediaActionSheetRollItem.swift @@ -83,7 +83,7 @@ private final class ChatMediaActionSheetRollItemNode: ActionSheetItemNode, PHPho } if !items.isEmpty { - self.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: (0 ..< items.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: items[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: []) + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: (0 ..< items.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: items[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: [], updateOpaqueState: nil) } //PHPhotoLibrary.shared().register(self) diff --git a/TelegramUI/ChatMediaInputNode.swift b/TelegramUI/ChatMediaInputNode.swift index 034d01ebfe..85153e572b 100644 --- a/TelegramUI/ChatMediaInputNode.swift +++ b/TelegramUI/ChatMediaInputNode.swift @@ -17,6 +17,7 @@ private struct ChatMediaInputGridTransition { let updates: [GridNodeUpdateItem] let updateFirstIndexInSectionOffset: Int? let stationaryItems: GridNodeStationaryItems + let scrollToItem: GridNodeScrollToItem? } private func preparedChatMediaInputPanelEntryTransition(account: Account, from fromEntries: [ChatMediaInputPanelEntry], to toEntries: [ChatMediaInputPanelEntry], inputNodeInteraction: ChatMediaInputNodeInteraction) -> ChatMediaInputPanelTransition { @@ -31,6 +32,7 @@ private func preparedChatMediaInputPanelEntryTransition(account: Account, from f private func preparedChatMediaInputGridEntryTransition(account: Account, from fromEntries: [ChatMediaInputGridEntry], to toEntries: [ChatMediaInputGridEntry], update: StickerPacksCollectionUpdate, interfaceInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction) -> ChatMediaInputGridTransition { var stationaryItems: GridNodeStationaryItems = .none + var scrollToItem: GridNodeScrollToItem? switch update { case .generic: break @@ -48,6 +50,17 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, from fr index += 1 } stationaryItems = .indices(indices) + case let .navigate(index): + for i in 0 ..< toEntries.count { + if toEntries[i].index >= index { + var directionHint: GridNodePreviousItemsTransitionDirectionHint = .up + if !fromEntries.isEmpty && fromEntries[0].index < toEntries[i].index { + directionHint = .down + } + scrollToItem = GridNodeScrollToItem(index: i, position: .top, transition: .animated(duration: 0.45, curve: .spring), directionHint: directionHint, adjustForSection: true) + break + } + } } let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) @@ -61,7 +74,7 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, from fr firstIndexInSectionOffset = Int(toEntries[0].index.itemIndex.index) } - return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems) + return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem) } private func chatMediaInputPanelEntries(view: ItemCollectionsView) -> [ChatMediaInputPanelEntry] { @@ -97,6 +110,7 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView) -> [ChatMediaI private enum StickerPacksCollectionPosition: Equatable { case initial case scroll(aroundIndex: ItemCollectionViewEntryIndex) + case navigate(index: ItemCollectionViewEntryIndex) static func ==(lhs: StickerPacksCollectionPosition, rhs: StickerPacksCollectionPosition) -> Bool { switch lhs { @@ -112,6 +126,8 @@ private enum StickerPacksCollectionPosition: Equatable { } else { return false } + case .navigate: + return false } } } @@ -119,12 +135,22 @@ private enum StickerPacksCollectionPosition: Equatable { private enum StickerPacksCollectionUpdate { case generic case scroll + case navigate(ItemCollectionViewEntryIndex) } final class ChatMediaInputNodeInteraction { + let navigateToCollectionId: (ItemCollectionId) -> Void + var highlightedItemCollectionId: ItemCollectionId? + + init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void) { + self.navigateToCollectionId = navigateToCollectionId + } } +private let defaultPortraitPanelHeight: CGFloat = UIScreenScale.isEqual(to: 3.0) ? 271.0 : 258.0 +private let defaultLandscapePanelHeight: CGFloat = UIScreenScale.isEqual(to: 3.0) ? 194.0 : 194.0 + final class ChatMediaInputNode: ChatInputNode { private let account: Account private let controllerInteraction: ChatControllerInteraction @@ -161,7 +187,21 @@ final class ChatMediaInputNode: ChatInputNode { super.init() - self.inputNodeInteraction = ChatMediaInputNodeInteraction() + self.inputNodeInteraction = ChatMediaInputNodeInteraction(navigateToCollectionId: { [weak self] collectionId in + if let strongSelf = self, let currentView = strongSelf.currentView, (collectionId != strongSelf.inputNodeInteraction.highlightedItemCollectionId || true) { + var index: Int32 = 0 + for (id, _, _) in currentView.collectionInfos { + if id.namespace == collectionId.namespace { + if id == collectionId { + let itemIndex = ItemCollectionViewEntryIndex.lowerBound(collectionIndex: index, collectionId: id) + strongSelf.itemCollectionsViewPosition.set(.single(.navigate(index: itemIndex))) + break + } + index += 1 + } + } + } + }) self.clipsToBounds = true self.backgroundColor = UIColor(0xE8EBF0) @@ -193,6 +233,19 @@ final class ChatMediaInputNode: ChatInputNode { } return (view, update) } + case let .navigate(index): + var firstTime = true + return account.postbox.itemCollectionsView(namespaces: [Namespaces.ItemCollection.CloudStickerPacks], aroundIndex: index, count: 140) + |> map { view -> (ItemCollectionsView, StickerPacksCollectionUpdate) in + let update: StickerPacksCollectionUpdate + if firstTime { + firstTime = false + update = .navigate(index) + } else { + update = .generic + } + return (view, update) + } } } @@ -218,19 +271,37 @@ final class ChatMediaInputNode: ChatInputNode { self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in if let strongSelf = self { - if let topVisible = visibleItems.topVisible { - if let item = topVisible.1 as? ChatMediaInputStickerGridItem { - let collectionId = item.index.collectionId - if strongSelf.inputNodeInteraction.highlightedItemCollectionId != collectionId { - strongSelf.inputNodeInteraction.highlightedItemCollectionId = collectionId - var selectedItemNode: ChatMediaInputStickerPackItemNode? - strongSelf.listView.forEachItemNode { itemNode in - if let itemNode = itemNode as? ChatMediaInputStickerPackItemNode { - itemNode.updateIsHighlighted() - if itemNode.currentCollectionId == collectionId { - strongSelf.listView.ensureItemNodeVisible(itemNode) - } + var topVisibleCollectionId: ItemCollectionId? + + if let topVisibleSection = visibleItems.topSectionVisible as? ChatMediaInputStickerGridSection { + topVisibleCollectionId = topVisibleSection.collectionId + } else if let topVisible = visibleItems.topVisible, let item = topVisible.1 as? ChatMediaInputStickerGridItem { + topVisibleCollectionId = item.index.collectionId + } + if let collectionId = topVisibleCollectionId { + if strongSelf.inputNodeInteraction.highlightedItemCollectionId != collectionId { + strongSelf.inputNodeInteraction.highlightedItemCollectionId = collectionId + var selectedItemNode: ChatMediaInputStickerPackItemNode? + var ensuredNodeVisible = false + var firstVisibleCollectionId: ItemCollectionId? + strongSelf.listView.forEachItemNode { itemNode in + if let itemNode = itemNode as? ChatMediaInputStickerPackItemNode { + if firstVisibleCollectionId == nil { + firstVisibleCollectionId = itemNode.currentCollectionId } + itemNode.updateIsHighlighted() + if itemNode.currentCollectionId == collectionId { + strongSelf.listView.ensureItemNodeVisible(itemNode) + ensuredNodeVisible = true + } + } + } + if let currentView = strongSelf.currentView, let firstVisibleCollectionId = firstVisibleCollectionId, !ensuredNodeVisible { + let targetIndex = currentView.collectionInfos.index(where: { id, _, _ in return id == collectionId }) + let firstVisibleIndex = currentView.collectionInfos.index(where: { id, _, _ in return id == firstVisibleCollectionId }) + if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex { + let toRight = targetIndex > firstVisibleIndex + strongSelf.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .Bottom : .Top, animated: true, curve: .Default, directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil) } } } @@ -262,8 +333,13 @@ final class ChatMediaInputNode: ChatInputNode { self.disposable.dispose() } + private func heightForWidth(width: CGFloat) -> CGFloat { + return defaultPortraitPanelHeight + } + override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { let separatorHeight = UIScreenPixel + let panelHeight = self.heightForWidth(width: width) transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: 41.0))) transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0), size: CGSize(width: width, height: separatorHeight))) @@ -293,15 +369,15 @@ final class ChatMediaInputNode: ChatInputNode { listViewCurve = .Default } - let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0, height: width), insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), duration: duration, curve: listViewCurve) + let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0, height: width), insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), duration: duration, curve: listViewCurve) - self.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, completion: { _ in }) + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - self.gridNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 41.0), size: CGSize(width: width, height: 258.0 - 41.0)) + self.gridNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 41.0), size: CGSize(width: width, height: panelHeight - 41.0)) - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: width, height: 258.0 - 41.0), insets: UIEdgeInsets(), preloadSize: 300.0, itemSize: CGSize(width: 75.0, height: 75.0)), transition: .immediate), stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: CGSize(width: width, height: panelHeight - 41.0), insets: UIEdgeInsets(), preloadSize: 300.0, itemSize: CGSize(width: 75.0, height: 75.0)), transition: .immediate), stationaryItems: .all, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - return 258.0 + return panelHeight } private func enqueuePanelTransition(_ transition: ChatMediaInputPanelTransition, firstTime: Bool) { @@ -312,11 +388,11 @@ final class ChatMediaInputNode: ChatInputNode { } else { options.insert(.AnimateInsertion) } - self.listView.deleteAndInsertItems(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, completion: { [weak self] _ in + self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in }) } private func enqueueGridTransition(_ transition: ChatMediaInputGridTransition, firstTime: Bool) { - self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: nil, updateLayout: nil, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, stationaryItems: transition.stationaryItems, updateFirstIndexInSectionOffset: transition.updateFirstIndexInSectionOffset), completion: { _ in }) } } diff --git a/TelegramUI/ChatMediaInputPanelEntries.swift b/TelegramUI/ChatMediaInputPanelEntries.swift index 8dfe129523..b8791e8426 100644 --- a/TelegramUI/ChatMediaInputPanelEntries.swift +++ b/TelegramUI/ChatMediaInputPanelEntries.swift @@ -64,6 +64,7 @@ enum ChatMediaInputPanelEntry: Comparable, Identifiable { switch self { case let .stickerPack(index, info, topItem): return ChatMediaInputStickerPackItem(account: account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, stickerPackItem: topItem, index: index, selected: { + inputNodeInteraction.navigateToCollectionId(info.id) }) } } diff --git a/TelegramUI/ChatMediaInputStickerGridItem.swift b/TelegramUI/ChatMediaInputStickerGridItem.swift index 2d36520cc5..0144c454f1 100644 --- a/TelegramUI/ChatMediaInputStickerGridItem.swift +++ b/TelegramUI/ChatMediaInputStickerGridItem.swift @@ -135,12 +135,12 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { override func layout() { super.layout() - let imageFrame = self.bounds.insetBy(dx: 6.0, dy: 6.0) - self.imageNode.frame = imageFrame + let boundingSize = self.bounds.insetBy(dx: 6.0, dy: 6.0).size if let (_, _, mediaDimensions) = self.currentState { - let imageSize = mediaDimensions.aspectFitted(imageFrame.size) - self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets()))() + let imageSize = mediaDimensions.aspectFitted(boundingSize) + self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))() + self.imageNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: imageSize) } } diff --git a/TelegramUI/ChatMediaInputStickerPackItem.swift b/TelegramUI/ChatMediaInputStickerPackItem.swift index fd109c6dc5..bea91d9da6 100644 --- a/TelegramUI/ChatMediaInputStickerPackItem.swift +++ b/TelegramUI/ChatMediaInputStickerPackItem.swift @@ -49,7 +49,7 @@ final class ChatMediaInputStickerPackItem: ListViewItem { } private let boundingSize = CGSize(width: 41.0, height: 41.0) -private let imageSize = CGSize(width: 30.0, height: 30.0) +private let boundingImageSize = CGSize(width: 30.0, height: 30.0) private let highlightSize = CGSize(width: 35.0, height: 35.0) private let verticalOffset: CGFloat = 3.0 @@ -76,7 +76,6 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize) - self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) self.imageNode.transform = CATransform3DMakeRotation(CGFloat(M_PI / 2.0), 0.0, 0.0, 1.0) self.imageNode.alphaTransitionOnFirstUpdate = true @@ -97,10 +96,12 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { self.currentItem = item if let item = item, let dimensions = item.file.dimensions { - let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.aspectFitted(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) + let imageSize = dimensions.aspectFitted(boundingImageSize) + let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets())) imageApply() self.imageNode.setSignal(account: account, signal: chatMessageSticker(account: account, file: item.file, small: true)) self.stickerFetchedDisposable.set(fileInteractiveFetched(account: account, file: item.file).start()) + self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) } self.updateIsHighlighted() diff --git a/TelegramUI/ChatTextInputPanelNode.swift b/TelegramUI/ChatTextInputPanelNode.swift index adbcd44844..26ef251f85 100644 --- a/TelegramUI/ChatTextInputPanelNode.swift +++ b/TelegramUI/ChatTextInputPanelNode.swift @@ -134,6 +134,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { let textFieldInsets = UIEdgeInsets(top: 6.0, left: 42.0, bottom: 6.0, right: 42.0) let textInputViewInternalInsets = UIEdgeInsets(top: 6.5, left: 13.0, bottom: 7.5, right: 13.0) + let accessoryButtonSpacing: CGFloat = 0.0 + let accessoryButtonInset: CGFloat = 4.0 + UIScreenPixel override init() { self.textInputBackgroundView = UIImageView(image: textInputViewBackground) @@ -204,6 +206,36 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { textInputNode.view.addGestureRecognizer(recognizer) } + private func calculateTextFieldMetrics(width: CGFloat) -> (accessoryButtonsWidth: CGFloat, textFieldHeight: CGFloat) { + let accessoryButtonInset = self.accessoryButtonInset + let accessoryButtonSpacing = self.accessoryButtonSpacing + + var accessoryButtonsWidth: CGFloat = 0.0 + var firstButton = true + for (_, button) in self.accessoryItemButtons { + if firstButton { + firstButton = false + accessoryButtonsWidth += accessoryButtonInset + } else { + accessoryButtonsWidth += accessoryButtonSpacing + } + accessoryButtonsWidth += button.buttonWidth + } + + let textFieldHeight: CGFloat + if let textInputNode = self.textInputNode { + textFieldHeight = min(115.0, max(21.0, ceil(textInputNode.measure(CGSize(width: width - self.textFieldInsets.left - self.textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height))) + } else { + textFieldHeight = 21.0 + } + + return (accessoryButtonsWidth, textFieldHeight) + } + + private func panelHeight(textFieldHeight: CGFloat) -> CGFloat { + return textFieldHeight + self.textFieldInsets.top + self.textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom + } + override func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat { if self.presentationInterfaceState != interfaceState { let previousState = self.presentationInterfaceState @@ -228,8 +260,6 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { let minimalHeight: CGFloat = 47.0 let minimalInputHeight: CGFloat = 35.0 - let accessoryButtonSpacing: CGFloat = 0.0 - let accessoryButtonInset: CGFloat = 4.0 + UIScreenPixel var animatedTransition = true if case .immediate = transition { @@ -280,26 +310,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { self.accessoryItemButtons = updatedButtons } - var accessoryButtonsWidth: CGFloat = 0.0 - var firstButton = true - for (_, button) in self.accessoryItemButtons { - if firstButton { - firstButton = false - accessoryButtonsWidth += accessoryButtonInset - } else { - accessoryButtonsWidth += accessoryButtonSpacing - } - accessoryButtonsWidth += button.buttonWidth - } - - let textFieldHeight: CGFloat - if let textInputNode = self.textInputNode { - textFieldHeight = min(115.0, max(21.0, ceil(textInputNode.measure(CGSize(width: width - self.textFieldInsets.left - self.textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height))) - } else { - textFieldHeight = 21.0 - } - - let panelHeight = textFieldHeight + self.textFieldInsets.top + self.textFieldInsets.bottom + self.textInputViewInternalInsets.top + self.textInputViewInternalInsets.bottom + let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: width) + let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight) transition.updateFrame(layer: self.attachmentButton.layer, frame: CGRect(origin: CGPoint(x: 2.0 - UIScreenPixel, y: panelHeight - minimalHeight), size: CGSize(width: 40.0, height: minimalHeight))) transition.updateFrame(layer: self.micButton.layer, frame: CGRect(origin: CGPoint(x: width - 43.0 - UIScreenPixel, y: panelHeight - minimalHeight - UIScreenPixel), size: CGSize(width: 44.0, height: minimalHeight))) @@ -369,6 +381,12 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } self.interfaceInteraction?.updateTextInputState(self.inputTextState) + + let (accessoryButtonsWidth, textFieldHeight) = self.calculateTextFieldMetrics(width: self.bounds.size.width) + let panelHeight = self.panelHeight(textFieldHeight: textFieldHeight) + if !self.bounds.size.height.isEqual(to: panelHeight) { + self.updateHeight() + } } } diff --git a/TelegramUI/ContactsController.swift b/TelegramUI/ContactsController.swift index 03536db8b3..53d38743bf 100644 --- a/TelegramUI/ContactsController.swift +++ b/TelegramUI/ContactsController.swift @@ -227,7 +227,7 @@ public class ContactsController: ViewController { self.scrollToTop = { [weak self] in if let strongSelf = self { - strongSelf.contactsNode.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in }) + strongSelf.contactsNode.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } } @@ -306,7 +306,7 @@ public class ContactsController: ViewController { } else if animated { options.insert(.AnimateInsertion) } - self.contactsNode.listView.deleteAndInsertItems(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, completion: { [weak self] _ in + self.contactsNode.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in if let strongSelf = self { if !strongSelf.didSetReady { strongSelf.didSetReady = true diff --git a/TelegramUI/ContactsControllerNode.swift b/TelegramUI/ContactsControllerNode.swift index 40ab354738..ddb228b487 100644 --- a/TelegramUI/ContactsControllerNode.swift +++ b/TelegramUI/ContactsControllerNode.swift @@ -61,7 +61,7 @@ final class ContactsControllerNode: ASDisplayNode { let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve) - self.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, completion: { _ in }) + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if let searchDisplayController = self.searchDisplayController { searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) diff --git a/TelegramUI/ContactsSearchContainerNode.swift b/TelegramUI/ContactsSearchContainerNode.swift index 4f1b5cf23c..1affbc5331 100644 --- a/TelegramUI/ContactsSearchContainerNode.swift +++ b/TelegramUI/ContactsSearchContainerNode.swift @@ -64,7 +64,7 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode { } } - strongSelf.listNode.deleteAndInsertItems(deleteIndices: (0 ..< previousItems.count).map({ ListViewDeleteItem(index: $0, directionHint: nil) }), insertIndicesAndItems: (0 ..< listItems.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: listItems[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: []) + strongSelf.listNode.transaction(deleteIndices: (0 ..< previousItems.count).map({ ListViewDeleteItem(index: $0, directionHint: nil) }), insertIndicesAndItems: (0 ..< listItems.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: listItems[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: [], updateOpaqueState: nil) } })) } @@ -87,6 +87,6 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) self.listNode.frame = CGRect(origin: CGPoint(), size: layout.size) - self.listNode.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: 0.0, right: 0.0), duration: 0.0, curve: .Default), stationaryItemRange: nil, completion: { _ in }) + self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: UIEdgeInsets(top: navigationBarHeight, left: 0.0, bottom: 0.0, right: 0.0), duration: 0.0, curve: .Default), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } diff --git a/TelegramUI/ListController.swift b/TelegramUI/ListController.swift index 7532ff7682..250dadfba2 100644 --- a/TelegramUI/ListController.swift +++ b/TelegramUI/ListController.swift @@ -18,7 +18,7 @@ public class ListController: ViewController { self.displayNode.backgroundColor = UIColor(0xefeff4) if !self.items.isEmpty { - self.listDisplayNode.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: (0 ..< self.items.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: self.items[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: [.LowLatency, .Synchronous]) + self.listDisplayNode.listView.transaction(deleteIndices: [], insertIndicesAndItems: (0 ..< self.items.count).map({ ListViewInsertItem(index: $0, previousIndex: nil, item: self.items[$0], directionHint: .Down) }), updateIndicesAndItems: [], options: [.LowLatency, .Synchronous], updateOpaqueState: nil) } self.displayNodeDidLoad() diff --git a/TelegramUI/ListControllerNode.swift b/TelegramUI/ListControllerNode.swift index d354939e72..a35d11f0a9 100644 --- a/TelegramUI/ListControllerNode.swift +++ b/TelegramUI/ListControllerNode.swift @@ -44,6 +44,6 @@ public class ListControllerNode: ASDisplayNode { self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) self.listView.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0) - self.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve), stationaryItemRange: nil, completion: { _ in }) + self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, duration: duration, curve: listViewCurve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } diff --git a/TelegramUI/PeerInfoController.swift b/TelegramUI/PeerInfoController.swift index 1e356b8386..3f6fb51ea3 100644 --- a/TelegramUI/PeerInfoController.swift +++ b/TelegramUI/PeerInfoController.swift @@ -270,7 +270,7 @@ public final class PeerInfoController: ListController { } else if animated { options.insert(.AnimateInsertion) } - self.listDisplayNode.listView.deleteAndInsertItems(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, completion: { [weak self] _ in + self.listDisplayNode.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in if let strongSelf = self { if !strongSelf.didSetReady { strongSelf.didSetReady = true diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 0f3e4064eb..aff780ec25 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -18,7 +18,6 @@ private func chatMessagePhotoDatas(account: Account, photo: TelegramMediaImage, let signal = maybeFullSize |> take(1) |> mapToSignal { maybeData -> Signal<(Data?, Data?, Bool), NoError> in if maybeData.complete { let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((nil, loadedData, true)) } else { let fetchedThumbnail = account.postbox.mediaBox.fetchedResource(smallestRepresentation.resource) @@ -371,9 +370,22 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage) -> Signal<(Tr return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return { arguments in - assertNotOnMainThread() + var debugTiming = false + var startTime = 0.0 + if arguments.imageSize.equalTo(CGSize(width: 640.0, height: 853.0)) { + print("begin draw \(CFAbsoluteTimeGetCurrent() * 1000.0)") + debugTiming = true + startTime = CFAbsoluteTimeGetCurrent() + } + let context = DrawingContext(size: arguments.drawingSize, clear: true) + if debugTiming { + let currentTime = CFAbsoluteTimeGetCurrent() + print("create context: \((currentTime - startTime) * 1000.0) ms") + startTime = currentTime + } + let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) @@ -381,11 +393,16 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage) -> Signal<(Tr var fullSizeImage: CGImage? if let fullSizeData = fullSizeData { if fullSizeComplete { - let options = NSMutableDictionary() + /*let options = NSMutableDictionary() options.setValue(max(fittedSize.width * context.scale, fittedSize.height * context.scale) as NSNumber, forKey: kCGImageSourceThumbnailMaxPixelSize as String) options.setValue(true as NSNumber, forKey: kCGImageSourceCreateThumbnailFromImageAlways as String) if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) { fullSizeImage = image + }*/ + let options = NSMutableDictionary() + options[kCGImageSourceShouldCache as NSString] = false as NSNumber + if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { + fullSizeImage = image } } else { let imageSource = CGImageSourceCreateIncremental(nil) @@ -399,6 +416,12 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage) -> Signal<(Tr } } + if debugTiming { + let currentTime = CFAbsoluteTimeGetCurrent() + print("decode full: \((currentTime - startTime) * 1000.0) ms") + startTime = currentTime + } + var thumbnailImage: CGImage? if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { thumbnailImage = image @@ -418,6 +441,12 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage) -> Signal<(Tr blurredThumbnailImage = thumbnailContext.generateImage() } + if debugTiming { + let currentTime = CFAbsoluteTimeGetCurrent() + print("decode thumbnail: \((currentTime - startTime) * 1000.0) ms") + startTime = currentTime + } + context.withFlippedContext { c in c.setBlendMode(.copy) if arguments.boundingSize != arguments.imageSize { @@ -437,8 +466,20 @@ func chatMessagePhoto(account: Account, photo: TelegramMediaImage) -> Signal<(Tr } } + if debugTiming { + let currentTime = CFAbsoluteTimeGetCurrent() + print("draw: \((currentTime - startTime) * 1000.0) ms") + startTime = currentTime + } + addCorners(context, arguments: arguments) + if debugTiming { + let currentTime = CFAbsoluteTimeGetCurrent() + print("add corners: \((currentTime - startTime) * 1000.0) ms") + startTime = currentTime + } + return context } } diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index c5896d03cc..67625535d8 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -49,7 +49,7 @@ public class SettingsController: ListController { let item = SettingsAccountInfoItem(account: account, peer: peer, connectionStatus: connectionStatus) strongSelf.items[0] = item if strongSelf.isNodeLoaded { - strongSelf.listDisplayNode.listView.deleteAndInsertItems(deleteIndices: [ListViewDeleteItem(index: 0, directionHint: nil)], insertIndicesAndItems: [ListViewInsertItem(index: 0, previousIndex: 0, item: item, directionHint: .Down)], updateIndicesAndItems: [], options: [.AnimateInsertion]) + strongSelf.listDisplayNode.listView.transaction(deleteIndices: [ListViewDeleteItem(index: 0, directionHint: nil)], insertIndicesAndItems: [ListViewInsertItem(index: 0, previousIndex: 0, item: item, directionHint: .Down)], updateIndicesAndItems: [], options: [.AnimateInsertion], updateOpaqueState: nil) } } } diff --git a/TelegramUI/StickerResources.swift b/TelegramUI/StickerResources.swift index 5a5e942bb4..7d888b27d8 100644 --- a/TelegramUI/StickerResources.swift +++ b/TelegramUI/StickerResources.swift @@ -62,8 +62,6 @@ func chatMessageSticker(account: Account, file: TelegramMediaFile, small: Bool) return signal |> map { (thumbnailData, fullSizeData, fullSizeComplete) in return { arguments in - assertNotOnMainThread() - let context = DrawingContext(size: arguments.drawingSize, clear: true) var fullSizeImage: (UIImage, UIImage)? diff --git a/TelegramUI/TransformImageNode.swift b/TelegramUI/TransformImageNode.swift index f847c72451..b62793c8a0 100644 --- a/TelegramUI/TransformImageNode.swift +++ b/TelegramUI/TransformImageNode.swift @@ -36,7 +36,7 @@ public class TransformImageNode: ASDisplayNode { public var alphaTransitionOnFirstUpdate = false private var disposable = MetaDisposable() - private var argumentsPromise = Promise() + private var argumentsPromise = ValuePromise(ignoreRepeated: true) deinit { self.disposable.dispose() @@ -45,7 +45,7 @@ public class TransformImageNode: ASDisplayNode { func setSignal(account: Account, signal: Signal<(TransformImageArguments) -> DrawingContext, NoError>, dispatchOnDisplayLink: Bool = true) { let argumentsPromise = self.argumentsPromise - let result = combineLatest(signal, argumentsPromise.get()) |> deliverOn(account.graphicsThreadPool) |> mapToThrottled { transform, arguments -> Signal in + let result = combineLatest(signal, argumentsPromise.get()) |> deliverOn(Queue.concurrentDefaultQueue() /*account.graphicsThreadPool*/) |> mapToThrottled { transform, arguments -> Signal in return deferred { return Signal.single(transform(arguments).generateImage()) } @@ -80,7 +80,7 @@ public class TransformImageNode: ASDisplayNode { public func asyncLayout() -> (TransformImageArguments) -> (() -> Void) { return { arguments in - self.argumentsPromise.set(.single(arguments)) + self.argumentsPromise.set(arguments) return {