From 1a5876f90e7289059c73d46b085ea4faab898b70 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Mon, 25 Oct 2021 21:20:29 +0400 Subject: [PATCH] Shared media improvements --- .../Sources/CalendarMessageScreen.swift | 209 +++++++++++------- .../Postbox/Sources/DeletedMessagesView.swift | 47 ++++ submodules/Postbox/Sources/Views.swift | 11 + .../Sources/SparseItemGrid.swift | 24 ++ .../Messages/SparseMessageList.swift | 202 +++++------------ .../Panes/PeerInfoVisualMediaPaneNode.swift | 18 ++ .../Sources/PeerInfo/PeerInfoScreen.swift | 71 +++++- 7 files changed, 342 insertions(+), 240 deletions(-) create mode 100644 submodules/Postbox/Sources/DeletedMessagesView.swift diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index 22b2c9fb59..467627a1bc 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -268,6 +268,8 @@ private final class DayComponent: Component { private var action: (() -> Void)? private var currentMedia: DayMedia? + private(set) var index: MessageIndex? + init() { self.button = HighlightableButton() self.highlightView = UIImageView() @@ -295,6 +297,7 @@ private final class DayComponent: Component { func update(component: DayComponent, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { self.action = component.action + self.index = component.media?.message.index let diameter = min(availableSize.width, availableSize.height) let contentFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - diameter) / 2.0), y: floor((availableSize.height - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter)) @@ -601,16 +604,18 @@ public final class CalendarMessageScreen: ViewController { private final class Node: ViewControllerTracingNode, UIScrollViewDelegate { private let context: AccountContext private let peerId: PeerId + private let initialTimestamp: Int32 private let navigateToDay: (Int32) -> Void + private let previewDay: (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void private var presentationData: PresentationData private var scrollView: Scroller private let calendarSource: SparseMessageCalendar - private var initialMonthIndex: Int = 0 private var months: [MonthModel] = [] private var monthViews: [Int: ComponentHostView] = [:] + private let contextGestureContainerNode: ContextControllerSourceNode private let imageCache = ImageCache() @@ -622,14 +627,20 @@ public final class CalendarMessageScreen: ViewController { private var isLoadingMoreDisposable: Disposable? private var stateDisposable: Disposable? - init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (Int32) -> Void) { + private weak var currentGestureDayView: DayComponent.View? + + init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (Int32) -> Void, previewDay: @escaping (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void) { self.context = context self.peerId = peerId + self.initialTimestamp = initialTimestamp self.calendarSource = calendarSource self.navigateToDay = navigateToDay + self.previewDay = previewDay self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.contextGestureContainerNode = ContextControllerSourceNode() + self.scrollView = Scroller() self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsHorizontalScrollIndicator = false @@ -644,6 +655,75 @@ public final class CalendarMessageScreen: ViewController { super.init() + self.contextGestureContainerNode.shouldBegin = { [weak self] point in + guard let strongSelf = self else { + return false + } + + guard let result = strongSelf.contextGestureContainerNode.view.hitTest(point, with: nil) as? HighlightableButton else { + return false + } + + guard let dayView = result.superview as? DayComponent.View else { + return false + } + + strongSelf.currentGestureDayView = dayView + + return true + } + + self.contextGestureContainerNode.customActivationProgress = { [weak self] progress, update in + guard let strongSelf = self, let currentGestureDayView = strongSelf.currentGestureDayView else { + return + } + let itemLayer = currentGestureDayView.layer + + let targetContentRect = CGRect(origin: CGPoint(), size: itemLayer.bounds.size) + + let scaleSide = itemLayer.bounds.width + let minScale: CGFloat = max(0.7, (scaleSide - 15.0) / scaleSide) + let currentScale = 1.0 * (1.0 - progress) + minScale * progress + + let originalCenterOffsetX: CGFloat = itemLayer.bounds.width / 2.0 - targetContentRect.midX + let scaledCenterOffsetX: CGFloat = originalCenterOffsetX * currentScale + + let originalCenterOffsetY: CGFloat = itemLayer.bounds.height / 2.0 - targetContentRect.midY + let scaledCenterOffsetY: CGFloat = originalCenterOffsetY * currentScale + + let scaleMidX: CGFloat = scaledCenterOffsetX - originalCenterOffsetX + let scaleMidY: CGFloat = scaledCenterOffsetY - originalCenterOffsetY + + switch update { + case .update: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + itemLayer.sublayerTransform = sublayerTransform + case .begin: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + itemLayer.sublayerTransform = sublayerTransform + case .ended: + let sublayerTransform = CATransform3DTranslate(CATransform3DScale(CATransform3DIdentity, currentScale, currentScale, 1.0), scaleMidX, scaleMidY, 0.0) + let previousTransform = itemLayer.sublayerTransform + itemLayer.sublayerTransform = sublayerTransform + + itemLayer.animate(from: NSValue(caTransform3D: previousTransform), to: NSValue(caTransform3D: sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.2) + } + } + + self.contextGestureContainerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self, let currentGestureDayView = strongSelf.currentGestureDayView else { + return + } + strongSelf.currentGestureDayView = nil + + currentGestureDayView.isUserInteractionEnabled = false + currentGestureDayView.isUserInteractionEnabled = true + + if let index = currentGestureDayView.index { + strongSelf.previewDay(index, strongSelf, currentGestureDayView.convert(currentGestureDayView.bounds, to: strongSelf.view), gesture) + } + } + let calendar = Calendar(identifier: .gregorian) let baseDate = Date() @@ -651,8 +731,6 @@ public final class CalendarMessageScreen: ViewController { let currentMonth = calendar.component(.month, from: baseDate) let currentDayOfMonth = calendar.component(.day, from: baseDate) - let initialDate = Date(timeIntervalSince1970: TimeInterval(initialTimestamp)) - for i in 0 ..< 12 * 20 { guard let firstDayOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: baseDate)) else { break @@ -660,10 +738,18 @@ public final class CalendarMessageScreen: ViewController { guard let monthBaseDate = calendar.date(byAdding: .month, value: -i, to: firstDayOfMonth) else { break } + guard let monthModel = monthMetadata(calendar: calendar, for: monthBaseDate, currentYear: currentYear, currentMonth: currentMonth, currentDayOfMonth: currentDayOfMonth) else { break } + let firstDayTimestamp = Int32(monthModel.firstDay.timeIntervalSince1970) + let lastDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(monthModel.numberOfDays) + + if let minTimestamp = calendarSource.minTimestamp, minTimestamp > lastDayTimestamp { + break + } + if monthModel.year < 2013 { break } @@ -676,19 +762,11 @@ public final class CalendarMessageScreen: ViewController { self.months.append(monthModel) } - if self.months.count > 1 { - for i in 0 ..< self.months.count - 1 { - if initialDate >= self.months[i].firstDay { - self.initialMonthIndex = i - break - } - } - } - self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.scrollView.delegate = self - self.view.addSubview(self.scrollView) + self.addSubnode(self.contextGestureContainerNode) + self.contextGestureContainerNode.view.addSubview(self.scrollView) self.isLoadingMoreDisposable = (self.calendarSource.isLoadingMore |> distinctUntilChanged @@ -722,20 +800,38 @@ public final class CalendarMessageScreen: ViewController { if self.updateScrollLayoutIfNeeded() { } - if isFirstLayout, let frame = self.scrollLayout?.frames[self.initialMonthIndex] { - var contentOffset = floor(frame.midY - self.scrollView.bounds.height / 2.0) - if contentOffset < 0 { - contentOffset = 0 + if isFirstLayout { + let initialDate = Date(timeIntervalSince1970: TimeInterval(self.initialTimestamp)) + var initialMonthIndex: Int? + + if self.months.count > 1 { + for i in 0 ..< self.months.count - 1 { + if initialDate >= self.months[i].firstDay { + initialMonthIndex = i + break + } + } } - if contentOffset > self.scrollView.contentSize.height - self.scrollView.bounds.height { - contentOffset = self.scrollView.contentSize.height - self.scrollView.bounds.height + + if isFirstLayout, let initialMonthIndex = initialMonthIndex, let frame = self.scrollLayout?.frames[initialMonthIndex] { + var contentOffset = floor(frame.midY - self.scrollView.bounds.height / 2.0) + if contentOffset < 0 { + contentOffset = 0 + } + if contentOffset > self.scrollView.contentSize.height - self.scrollView.bounds.height { + contentOffset = self.scrollView.contentSize.height - self.scrollView.bounds.height + } + self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffset), animated: false) } - self.scrollView.setContentOffset(CGPoint(x: 0.0, y: contentOffset), animated: false) } updateMonthViews() } + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.contextGestureContainerNode.cancelGesture() + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { if let indicator = scrollView.value(forKey: "_verticalScrollIndicator") as? UIView { indicator.transform = CGAffineTransform(scaleX: -1.0, y: 1.0) @@ -784,7 +880,8 @@ public final class CalendarMessageScreen: ViewController { self.scrollLayout = (layout.size.width, contentHeight, frames) - self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationHeight)) + self.contextGestureContainerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationHeight)) + self.scrollView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: layout.size.height - navigationHeight)) self.scrollView.contentSize = CGSize(width: layout.size.width, height: contentHeight) self.scrollView.scrollIndicatorInsets = UIEdgeInsets(top: layout.intrinsicInsets.bottom, left: 0.0, bottom: 0.0, right: layout.size.width - 3.0 - 6.0) @@ -861,19 +958,11 @@ public final class CalendarMessageScreen: ViewController { messageMap.append(message) } - let _ = messageMap - var updatedMedia: [Int: [Int: DayMedia]] = [:] - var removeMonths: [Int] = [] for i in 0 ..< self.months.count { - let firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970) - let lastDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(self.months[i].numberOfDays) - - if let minTimestamp = calendarState.minTimestamp, minTimestamp > lastDayTimestamp { - removeMonths.append(i) - } - for day in 0 ..< self.months[i].numberOfDays { + let firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970) + let dayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day) let nextDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day - 1) @@ -901,57 +990,7 @@ public final class CalendarMessageScreen: ViewController { self.months[monthIndex].mediaByDay = mediaByDay } - for i in removeMonths.reversed() { - self.months.remove(at: i) - } - - if !removeMonths.isEmpty { - self.scrollLayout = nil - let _ = self.updateScrollLayoutIfNeeded() - } - self.updateMonthViews() - - /*let peerId = self.peerId - let months = self.months - let _ = (self.context.account.postbox.transaction { transaction -> [Int: [Int: DayMedia]] in - var updatedMedia: [Int: [Int: DayMedia]] = [:] - - for i in 0 ..< months.count { - for day in 0 ..< months[i].numberOfDays { - let dayTimestamp = Int32(months[i].firstDay.timeIntervalSince1970) + 24 * 60 * 60 * Int32(day) - let nextDayTimestamp = Int32(months[i].firstDay.timeIntervalSince1970) + 24 * 60 * 60 * Int32(day - 1) - if let message = transaction.firstMessageInRange(peerId: peerId, namespace: Namespaces.Message.Cloud, tag: .photoOrVideo, timestampMax: dayTimestamp, timestampMin: nextDayTimestamp - 1) { - /*if message.timestamp < nextDayTimestamp { - continue - }*/ - if updatedMedia[i] == nil { - updatedMedia[i] = [:] - } - mediaLoop: for media in message.media { - switch media { - case _ as TelegramMediaImage, _ as TelegramMediaFile: - updatedMedia[i]![day] = DayMedia(message: EngineMessage(message), media: EngineMedia(media)) - break mediaLoop - default: - break - } - } - } - } - } - - return updatedMedia - } - |> deliverOnMainQueue).start(next: { [weak self] updatedMedia in - guard let strongSelf = self else { - return - } - for (monthIndex, mediaByDay) in updatedMedia { - strongSelf.months[monthIndex].mediaByDay = mediaByDay - } - strongSelf.updateMonthViews() - })*/ } } @@ -964,13 +1003,15 @@ public final class CalendarMessageScreen: ViewController { private let calendarSource: SparseMessageCalendar private let initialTimestamp: Int32 private let navigateToDay: (CalendarMessageScreen, Int32) -> Void + private let previewDay: (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void - public init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (CalendarMessageScreen, Int32) -> Void) { + public init(context: AccountContext, peerId: PeerId, calendarSource: SparseMessageCalendar, initialTimestamp: Int32, navigateToDay: @escaping (CalendarMessageScreen, Int32) -> Void, previewDay: @escaping (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void) { self.context = context self.peerId = peerId self.calendarSource = calendarSource self.initialTimestamp = initialTimestamp self.navigateToDay = navigateToDay + self.previewDay = previewDay let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } @@ -997,7 +1038,7 @@ public final class CalendarMessageScreen: ViewController { return } strongSelf.navigateToDay(strongSelf, timestamp) - }) + }, previewDay: self.previewDay) self.displayNodeDidLoad() } diff --git a/submodules/Postbox/Sources/DeletedMessagesView.swift b/submodules/Postbox/Sources/DeletedMessagesView.swift new file mode 100644 index 0000000000..95c8e2d528 --- /dev/null +++ b/submodules/Postbox/Sources/DeletedMessagesView.swift @@ -0,0 +1,47 @@ +import Foundation + +final class MutableDeletedMessagesView: MutablePostboxView { + let peerId: PeerId + var currentDeletedMessages: [MessageId] = [] + + init(peerId: PeerId) { + self.peerId = peerId + } + + func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool { + var updated = false + if let operations = transaction.currentOperationsByPeerId[self.peerId] { + var testMessageIds: [MessageId] = [] + for operation in operations { + switch operation { + case let .Remove(indices): + for (index, _) in indices { + testMessageIds.append(index.id) + } + default: + break + } + } + self.currentDeletedMessages.removeAll() + for id in testMessageIds { + if !postbox.messageHistoryIndexTable.exists(id) { + self.currentDeletedMessages.append(id) + updated = true + } + } + } + return updated + } + + func immutableView() -> PostboxView { + return DeletedMessagesView(self) + } +} + +public final class DeletedMessagesView: PostboxView { + public let currentDeletedMessages: [MessageId] + + init(_ view: MutableDeletedMessagesView) { + self.currentDeletedMessages = view.currentDeletedMessages + } +} diff --git a/submodules/Postbox/Sources/Views.swift b/submodules/Postbox/Sources/Views.swift index 3f47094ab6..9b7dbea1e9 100644 --- a/submodules/Postbox/Sources/Views.swift +++ b/submodules/Postbox/Sources/Views.swift @@ -32,6 +32,7 @@ public enum PostboxViewKey: Hashable { case historyTagInfo(peerId: PeerId, tag: MessageTags) case topChatMessage(peerIds: [PeerId]) case contacts(accountPeerId: PeerId?, includePresences: Bool) + case deletedMessages(peerId: PeerId) public func hash(into hasher: inout Hasher) { switch self { @@ -106,6 +107,8 @@ public enum PostboxViewKey: Hashable { hasher.combine(peerIds) case .contacts: hasher.combine(16) + case let .deletedMessages(peerId): + hasher.combine(peerId) } } @@ -297,6 +300,12 @@ public enum PostboxViewKey: Hashable { } else { return false } + case let .deletedMessages(peerId): + if case .deletedMessages(peerId) = rhs { + return true + } else { + return false + } } } } @@ -365,5 +374,7 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost return MutableTopChatMessageView(postbox: postbox, peerIds: Set(peerIds)) case let .contacts(accountPeerId, includePresences): return MutableContactPeersView(postbox: postbox, accountPeerId: accountPeerId, includePresences: includePresences) + case let .deletedMessages(peerId): + return MutableDeletedMessagesView(peerId: peerId) } } diff --git a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift index 64349311b9..2488ebe95b 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift @@ -596,6 +596,23 @@ public final class SparseItemGrid: ASDisplayNode { return nil } + func visualItem(at index: Int) -> SparseItemGridDisplayItem? { + guard let items = self.items, !items.items.isEmpty else { + return nil + } + + guard let item = items.item(at: index) else { + return nil + } + for (id, visibleItem) in self.visibleItems { + if id == item.id { + return visibleItem + } + } + + return nil + } + func item(at point: CGPoint) -> Item? { guard let items = self.items, !items.items.isEmpty else { return nil @@ -1468,6 +1485,13 @@ public final class SparseItemGrid: ASDisplayNode { return currentViewport.visualItem(at: point) } + public func item(at index: Int) -> SparseItemGridDisplayItem? { + guard let currentViewport = self.currentViewport else { + return nil + } + return currentViewport.visualItem(at: index) + } + public func scrollToItem(at index: Int) { guard let currentViewport = self.currentViewport else { return diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift index d066374fb5..48da3da24d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift @@ -74,6 +74,8 @@ public final class SparseMessageList { private var topSection: TopSection? private var topItemsDisposable = MetaDisposable() + private var deletedMessagesDisposable: Disposable? + private var sparseItems: SparseItems? private var sparseItemsDisposable: Disposable? @@ -159,12 +161,24 @@ public final class SparseMessageList { strongSelf.updateState() } }) + + self.deletedMessagesDisposable = (account.postbox.combinedView(keys: [.deletedMessages(peerId: peerId)]) + |> deliverOn(self.queue)).start(next: { [weak self] views in + guard let strongSelf = self else { + return + } + guard let view = views.views[.deletedMessages(peerId: peerId)] as? DeletedMessagesView else { + return + } + strongSelf.processDeletedMessages(ids: view.currentDeletedMessages) + }) } deinit { self.topItemsDisposable.dispose() self.sparseItemsDisposable?.dispose() self.loadHoleDisposable.dispose() + self.deletedMessagesDisposable?.dispose() } private func resetTopSection() { @@ -188,162 +202,37 @@ public final class SparseMessageList { })) } + private func processDeletedMessages(ids: [MessageId]) { + if let sparseItems = self.sparseItems { + let idsSet = Set(ids) + + var removeIndices: [Int] = [] + for i in 0 ..< sparseItems.items.count { + switch sparseItems.items[i] { + case let .anchor(id, _, message): + if message != nil, idsSet.contains(id) { + removeIndices.append(i) + } + default: + break + } + } + + if !removeIndices.isEmpty { + for index in removeIndices.reversed() { + self.sparseItems?.items.remove(at: index) + } + + self.updateState() + } + } + } + func loadMoreFromTopSection() { self.topSectionItemRequestCount += 100 self.resetTopSection() } - func loadPlaceholders(ids: [MessageId]) { - var loadGlobalIds: [MessageId] = [] - var loadChannelIds: [PeerId: [MessageId]] = [:] - for id in ids { - if self.loadingPlaceholders[id] != nil { - continue - } - self.loadingPlaceholders[id] = MetaDisposable() - if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup { - loadGlobalIds.append(id) - } else if id.peerId.namespace == Namespaces.Peer.CloudChannel { - if loadChannelIds[id.peerId] == nil { - loadChannelIds[id.peerId] = [] - } - loadChannelIds[id.peerId]!.append(id) - } - } - - var loadSignals: [Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError>] = [] - let account = self.account - - if !loadGlobalIds.isEmpty { - loadSignals.append(self.account.postbox.transaction { transaction -> [Api.InputMessage] in - var result: [Api.InputMessage] = [] - for id in loadGlobalIds { - let inputMessage: Api.InputMessage = .inputMessageID(id: id.id) - result.append(inputMessage) - } - return result - } - |> mapToSignal { inputMessages -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in - return account.network.request(Api.functions.messages.getMessages(id: inputMessages)) - |> map { result -> (messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) in - switch result { - case let .messages(messages, chats, users): - return (messages, chats, users) - case let .messagesSlice(_, _, _, _, messages, chats, users): - return (messages, chats, users) - case let .channelMessages(_, _, _, _, messages, chats, users): - return (messages, chats, users) - case .messagesNotModified: - return ([], [], []) - } - } - |> `catch` { _ -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in - return .single(([], [], [])) - } - }) - } - - if !loadChannelIds.isEmpty { - for (channelId, ids) in loadChannelIds { - loadSignals.append(self.account.postbox.transaction { transaction -> Api.InputChannel? in - return transaction.getPeer(channelId).flatMap(apiInputChannel) - } - |> mapToSignal { inputChannel -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in - guard let inputChannel = inputChannel else { - return .single(([], [], [])) - } - - return account.network.request(Api.functions.channels.getMessages(channel: inputChannel, id: ids.map { Api.InputMessage.inputMessageID(id: $0.id) })) - |> map { result -> (messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) in - switch result { - case let .messages(messages, chats, users): - return (messages, chats, users) - case let .messagesSlice(_, _, _, _, messages, chats, users): - return (messages, chats, users) - case let .channelMessages(_, _, _, _, messages, chats, users): - return (messages, chats, users) - case .messagesNotModified: - return ([], [], []) - } - } - |> `catch` { _ -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in - return .single(([], [], [])) - } - }) - } - } - - let _ = (combineLatest(queue: self.queue, loadSignals) - |> mapToSignal { messageLists -> Signal<[Message], NoError> in - return account.postbox.transaction { transaction -> [Message] in - var parsedMessages: [StoreMessage] = [] - var peers: [Peer] = [] - var peerPresences: [PeerId: PeerPresence] = [:] - - for (messages, chats, users) in messageLists { - for chat in chats { - if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { - peers.append(groupOrChannel) - } - } - for user in users { - let telegramUser = TelegramUser(user: user) - peers.append(telegramUser) - if let presence = TelegramUserPresence(apiUser: user) { - peerPresences[telegramUser.id] = presence - } - } - - for message in messages { - if let parsedMessage = StoreMessage(apiMessage: message) { - parsedMessages.append(parsedMessage) - } - } - } - - updatePeers(transaction: transaction, peers: peers, update: { _, updated in updated }) - updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences) - let _ = transaction.addMessages(parsedMessages, location: .Random) - - var result: [Message] = [] - for parsedMessage in parsedMessages { - switch parsedMessage.id { - case let .Id(id): - if let message = transaction.getMessage(id) { - result.append(message) - } - case .Partial: - break - } - } - - return result - } - } - |> deliverOn(self.queue)).start(next: { [weak self] messages in - guard let strongSelf = self else { - return - } - for message in messages { - strongSelf.loadedPlaceholders[message.id] = message - } - if strongSelf.sparseItems != nil { - for i in 0 ..< strongSelf.sparseItems!.items.count { - switch strongSelf.sparseItems!.items[i] { - case let .anchor(id, timestamp, _): - if let message = strongSelf.loadedPlaceholders[id] { - strongSelf.sparseItems!.items[i] = .anchor(id: id, timestamp: timestamp, message: message) - } - case .range: - break - } - } - } - - strongSelf.updateState() - }) - } - func loadHole(anchor: MessageId, direction: LoadHoleDirection, completion: @escaping () -> Void) { let loadingHole = LoadingHole(anchor: anchor, direction: direction) if self.loadingHole == loadingHole { @@ -811,12 +700,23 @@ public final class SparseMessageCalendar { private let queue: Queue private let impl: QueueLocalObject + public var minTimestamp: Int32? + private var disposable: Disposable? + init(account: Account, peerId: PeerId, messageTag: MessageTags) { let queue = Queue() self.queue = queue self.impl = QueueLocalObject(queue: queue, generate: { return Impl(queue: queue, account: account, peerId: peerId, messageTag: messageTag) }) + + self.disposable = self.state.start(next: { [weak self] state in + self?.minTimestamp = state.minTimestamp + }) + } + + deinit { + self.disposable?.dispose() } public var state: Signal { diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index ca61755cd7..d526686739 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -2201,6 +2201,24 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro } if let index = previousIndex { self.itemGrid.scrollToItem(at: index) + + if let item = self.itemGrid.item(at: index) { + if let layer = item.layer as? ItemLayer { + Queue.mainQueue().after(0.1, { [weak layer] in + guard let layer = layer else { + return + } + + let overlayLayer = ListShimmerLayer.OverlayLayer() + overlayLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.6).cgColor + overlayLayer.frame = layer.bounds + layer.addSublayer(overlayLayer) + overlayLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.8, delay: 0.3, removeOnCompletion: false, completion: { [weak overlayLayer] _ in + overlayLayer?.removeFromSuperlayer() + }) + }) + } + } } } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 89274b4a8e..85573010c4 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -6027,6 +6027,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let _ = (context.account.postbox.combinedView(keys: summaryTags.map { tag in return PostboxViewKey.historyTagSummaryView(tag: tag, peerId: peerId, namespace: Namespaces.Message.Cloud) }) + |> take(1) |> deliverOnMainQueue).start(next: { [weak self] views in guard let strongSelf = self else { return @@ -6194,7 +6195,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } initialTimestamp = timestamp - self.controller?.push(CalendarMessageScreen(context: self.context, peerId: self.peerId, calendarSource: calendarSource, initialTimestamp: initialTimestamp, navigateToDay: { [weak self] c, timestamp in + var dismissCalendarScreen: (() -> Void)? + + let calendarScreen = CalendarMessageScreen(context: self.context, peerId: self.peerId, calendarSource: calendarSource, initialTimestamp: initialTimestamp, navigateToDay: { [weak self] c, timestamp in guard let strongSelf = self else { c.dismiss() return @@ -6207,7 +6210,60 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate pane.scrollToTimestamp(timestamp: timestamp) c.dismiss() - })) + }, previewDay: { [weak self] index, sourceNode, sourceRect, gesture in + guard let strongSelf = self else { + return + } + + var items: [ContextMenuItem] = [] + + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) + dismissCalendarScreen?() + + guard let strongSelf = self, let controller = strongSelf.controller, let navigationController = controller.navigationController as? NavigationController else { + return + } + + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams( + navigationController: navigationController, + chatController: nil, + context: strongSelf.context, + chatLocation: .peer(strongSelf.peerId), + subject: .message(id: index.id, highlight: false, timecode: nil), + botStart: nil, + updateTextInputState: nil, + activateInput: false, + keepStack: .never, + useExisting: true, + purposefulAction: nil, + scrollToEndIfExists: false, + activateMessageSearch: nil, + peekData: nil, + peerNearbyData: nil, + reportReason: nil, + animated: true, + options: [], + parentGroupId: nil, + chatListFilter: nil, + changeColors: false, + completion: { _ in + } + )) + }))) + + let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(id: index.id, highlight: false, timecode: nil), botStart: nil, mode: .standard(previewing: true)) + chatController.canReadHistory.set(false) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: sourceNode, sourceRect: sourceRect, passthroughTouches: true)), items: .single(ContextController.Items(items: items)), gesture: gesture) + strongSelf.controller?.presentInGlobalOverlay(contextController) + }) + + self.controller?.push(calendarScreen) + dismissCalendarScreen = { [weak calendarScreen] in + calendarScreen?.dismiss(completion: nil) + } } func updatePresentationData(_ presentationData: PresentationData) { @@ -7427,21 +7483,26 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig private final class ContextControllerContentSourceImpl: ContextControllerContentSource { let controller: ViewController weak var sourceNode: ASDisplayNode? + let sourceRect: CGRect let navigationController: NavigationController? = nil - let passthroughTouches: Bool = false + let passthroughTouches: Bool - init(controller: ViewController, sourceNode: ASDisplayNode?) { + init(controller: ViewController, sourceNode: ASDisplayNode?, sourceRect: CGRect = CGRect(origin: CGPoint(), size: CGSize()), passthroughTouches: Bool = false) { self.controller = controller self.sourceNode = sourceNode + self.sourceRect = sourceRect + self.passthroughTouches = passthroughTouches } func transitionInfo() -> ContextControllerTakeControllerInfo? { let sourceNode = self.sourceNode + let sourceRect = self.sourceRect return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in if let sourceNode = sourceNode { - return (sourceNode, sourceNode.bounds) + let rect = sourceRect.isEmpty ? sourceNode.bounds : sourceRect + return (sourceNode, rect) } else { return nil }