Shared media improvements

This commit is contained in:
Ali 2021-10-25 21:20:29 +04:00
parent e7c626004c
commit 1a5876f90e
7 changed files with 342 additions and 240 deletions

View File

@ -268,6 +268,8 @@ private final class DayComponent: Component {
private var action: (() -> Void)? private var action: (() -> Void)?
private var currentMedia: DayMedia? private var currentMedia: DayMedia?
private(set) var index: MessageIndex?
init() { init() {
self.button = HighlightableButton() self.button = HighlightableButton()
self.highlightView = UIImageView() self.highlightView = UIImageView()
@ -295,6 +297,7 @@ private final class DayComponent: Component {
func update(component: DayComponent, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize { func update(component: DayComponent, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
self.action = component.action self.action = component.action
self.index = component.media?.message.index
let diameter = min(availableSize.width, availableSize.height) 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)) 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 final class Node: ViewControllerTracingNode, UIScrollViewDelegate {
private let context: AccountContext private let context: AccountContext
private let peerId: PeerId private let peerId: PeerId
private let initialTimestamp: Int32
private let navigateToDay: (Int32) -> Void private let navigateToDay: (Int32) -> Void
private let previewDay: (MessageIndex, ASDisplayNode, CGRect, ContextGesture) -> Void
private var presentationData: PresentationData private var presentationData: PresentationData
private var scrollView: Scroller private var scrollView: Scroller
private let calendarSource: SparseMessageCalendar private let calendarSource: SparseMessageCalendar
private var initialMonthIndex: Int = 0
private var months: [MonthModel] = [] private var months: [MonthModel] = []
private var monthViews: [Int: ComponentHostView<ImageCache>] = [:] private var monthViews: [Int: ComponentHostView<ImageCache>] = [:]
private let contextGestureContainerNode: ContextControllerSourceNode
private let imageCache = ImageCache() private let imageCache = ImageCache()
@ -622,14 +627,20 @@ public final class CalendarMessageScreen: ViewController {
private var isLoadingMoreDisposable: Disposable? private var isLoadingMoreDisposable: Disposable?
private var stateDisposable: 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.context = context
self.peerId = peerId self.peerId = peerId
self.initialTimestamp = initialTimestamp
self.calendarSource = calendarSource self.calendarSource = calendarSource
self.navigateToDay = navigateToDay self.navigateToDay = navigateToDay
self.previewDay = previewDay
self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.contextGestureContainerNode = ContextControllerSourceNode()
self.scrollView = Scroller() self.scrollView = Scroller()
self.scrollView.showsVerticalScrollIndicator = true self.scrollView.showsVerticalScrollIndicator = true
self.scrollView.showsHorizontalScrollIndicator = false self.scrollView.showsHorizontalScrollIndicator = false
@ -644,6 +655,75 @@ public final class CalendarMessageScreen: ViewController {
super.init() 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 calendar = Calendar(identifier: .gregorian)
let baseDate = Date() let baseDate = Date()
@ -651,8 +731,6 @@ public final class CalendarMessageScreen: ViewController {
let currentMonth = calendar.component(.month, from: baseDate) let currentMonth = calendar.component(.month, from: baseDate)
let currentDayOfMonth = calendar.component(.day, from: baseDate) let currentDayOfMonth = calendar.component(.day, from: baseDate)
let initialDate = Date(timeIntervalSince1970: TimeInterval(initialTimestamp))
for i in 0 ..< 12 * 20 { for i in 0 ..< 12 * 20 {
guard let firstDayOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: baseDate)) else { guard let firstDayOfMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: baseDate)) else {
break break
@ -660,10 +738,18 @@ public final class CalendarMessageScreen: ViewController {
guard let monthBaseDate = calendar.date(byAdding: .month, value: -i, to: firstDayOfMonth) else { guard let monthBaseDate = calendar.date(byAdding: .month, value: -i, to: firstDayOfMonth) else {
break break
} }
guard let monthModel = monthMetadata(calendar: calendar, for: monthBaseDate, currentYear: currentYear, currentMonth: currentMonth, currentDayOfMonth: currentDayOfMonth) else { guard let monthModel = monthMetadata(calendar: calendar, for: monthBaseDate, currentYear: currentYear, currentMonth: currentMonth, currentDayOfMonth: currentDayOfMonth) else {
break 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 { if monthModel.year < 2013 {
break break
} }
@ -676,19 +762,11 @@ public final class CalendarMessageScreen: ViewController {
self.months.append(monthModel) 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.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.scrollView.delegate = self self.scrollView.delegate = self
self.view.addSubview(self.scrollView) self.addSubnode(self.contextGestureContainerNode)
self.contextGestureContainerNode.view.addSubview(self.scrollView)
self.isLoadingMoreDisposable = (self.calendarSource.isLoadingMore self.isLoadingMoreDisposable = (self.calendarSource.isLoadingMore
|> distinctUntilChanged |> distinctUntilChanged
@ -722,20 +800,38 @@ public final class CalendarMessageScreen: ViewController {
if self.updateScrollLayoutIfNeeded() { if self.updateScrollLayoutIfNeeded() {
} }
if isFirstLayout, let frame = self.scrollLayout?.frames[self.initialMonthIndex] { if isFirstLayout {
var contentOffset = floor(frame.midY - self.scrollView.bounds.height / 2.0) let initialDate = Date(timeIntervalSince1970: TimeInterval(self.initialTimestamp))
if contentOffset < 0 { var initialMonthIndex: Int?
contentOffset = 0
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() updateMonthViews()
} }
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.contextGestureContainerNode.cancelGesture()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let indicator = scrollView.value(forKey: "_verticalScrollIndicator") as? UIView { if let indicator = scrollView.value(forKey: "_verticalScrollIndicator") as? UIView {
indicator.transform = CGAffineTransform(scaleX: -1.0, y: 1.0) 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.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.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) 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) messageMap.append(message)
} }
let _ = messageMap
var updatedMedia: [Int: [Int: DayMedia]] = [:] var updatedMedia: [Int: [Int: DayMedia]] = [:]
var removeMonths: [Int] = []
for i in 0 ..< self.months.count { 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 { 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 dayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day)
let nextDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day - 1) let nextDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day - 1)
@ -901,57 +990,7 @@ public final class CalendarMessageScreen: ViewController {
self.months[monthIndex].mediaByDay = mediaByDay 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() 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 calendarSource: SparseMessageCalendar
private let initialTimestamp: Int32 private let initialTimestamp: Int32
private let navigateToDay: (CalendarMessageScreen, Int32) -> Void 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.context = context
self.peerId = peerId self.peerId = peerId
self.calendarSource = calendarSource self.calendarSource = calendarSource
self.initialTimestamp = initialTimestamp self.initialTimestamp = initialTimestamp
self.navigateToDay = navigateToDay self.navigateToDay = navigateToDay
self.previewDay = previewDay
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
@ -997,7 +1038,7 @@ public final class CalendarMessageScreen: ViewController {
return return
} }
strongSelf.navigateToDay(strongSelf, timestamp) strongSelf.navigateToDay(strongSelf, timestamp)
}) }, previewDay: self.previewDay)
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }

View File

@ -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
}
}

View File

@ -32,6 +32,7 @@ public enum PostboxViewKey: Hashable {
case historyTagInfo(peerId: PeerId, tag: MessageTags) case historyTagInfo(peerId: PeerId, tag: MessageTags)
case topChatMessage(peerIds: [PeerId]) case topChatMessage(peerIds: [PeerId])
case contacts(accountPeerId: PeerId?, includePresences: Bool) case contacts(accountPeerId: PeerId?, includePresences: Bool)
case deletedMessages(peerId: PeerId)
public func hash(into hasher: inout Hasher) { public func hash(into hasher: inout Hasher) {
switch self { switch self {
@ -106,6 +107,8 @@ public enum PostboxViewKey: Hashable {
hasher.combine(peerIds) hasher.combine(peerIds)
case .contacts: case .contacts:
hasher.combine(16) hasher.combine(16)
case let .deletedMessages(peerId):
hasher.combine(peerId)
} }
} }
@ -297,6 +300,12 @@ public enum PostboxViewKey: Hashable {
} else { } else {
return false 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)) return MutableTopChatMessageView(postbox: postbox, peerIds: Set(peerIds))
case let .contacts(accountPeerId, includePresences): case let .contacts(accountPeerId, includePresences):
return MutableContactPeersView(postbox: postbox, accountPeerId: accountPeerId, includePresences: includePresences) return MutableContactPeersView(postbox: postbox, accountPeerId: accountPeerId, includePresences: includePresences)
case let .deletedMessages(peerId):
return MutableDeletedMessagesView(peerId: peerId)
} }
} }

View File

@ -596,6 +596,23 @@ public final class SparseItemGrid: ASDisplayNode {
return nil 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? { func item(at point: CGPoint) -> Item? {
guard let items = self.items, !items.items.isEmpty else { guard let items = self.items, !items.items.isEmpty else {
return nil return nil
@ -1468,6 +1485,13 @@ public final class SparseItemGrid: ASDisplayNode {
return currentViewport.visualItem(at: point) 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) { public func scrollToItem(at index: Int) {
guard let currentViewport = self.currentViewport else { guard let currentViewport = self.currentViewport else {
return return

View File

@ -74,6 +74,8 @@ public final class SparseMessageList {
private var topSection: TopSection? private var topSection: TopSection?
private var topItemsDisposable = MetaDisposable() private var topItemsDisposable = MetaDisposable()
private var deletedMessagesDisposable: Disposable?
private var sparseItems: SparseItems? private var sparseItems: SparseItems?
private var sparseItemsDisposable: Disposable? private var sparseItemsDisposable: Disposable?
@ -159,12 +161,24 @@ public final class SparseMessageList {
strongSelf.updateState() 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 { deinit {
self.topItemsDisposable.dispose() self.topItemsDisposable.dispose()
self.sparseItemsDisposable?.dispose() self.sparseItemsDisposable?.dispose()
self.loadHoleDisposable.dispose() self.loadHoleDisposable.dispose()
self.deletedMessagesDisposable?.dispose()
} }
private func resetTopSection() { 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() { func loadMoreFromTopSection() {
self.topSectionItemRequestCount += 100 self.topSectionItemRequestCount += 100
self.resetTopSection() 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) { func loadHole(anchor: MessageId, direction: LoadHoleDirection, completion: @escaping () -> Void) {
let loadingHole = LoadingHole(anchor: anchor, direction: direction) let loadingHole = LoadingHole(anchor: anchor, direction: direction)
if self.loadingHole == loadingHole { if self.loadingHole == loadingHole {
@ -811,12 +700,23 @@ public final class SparseMessageCalendar {
private let queue: Queue private let queue: Queue
private let impl: QueueLocalObject<Impl> private let impl: QueueLocalObject<Impl>
public var minTimestamp: Int32?
private var disposable: Disposable?
init(account: Account, peerId: PeerId, messageTag: MessageTags) { init(account: Account, peerId: PeerId, messageTag: MessageTags) {
let queue = Queue() let queue = Queue()
self.queue = queue self.queue = queue
self.impl = QueueLocalObject(queue: queue, generate: { self.impl = QueueLocalObject(queue: queue, generate: {
return Impl(queue: queue, account: account, peerId: peerId, messageTag: messageTag) 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<State, NoError> { public var state: Signal<State, NoError> {

View File

@ -2201,6 +2201,24 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
} }
if let index = previousIndex { if let index = previousIndex {
self.itemGrid.scrollToItem(at: index) 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()
})
})
}
}
} }
} }
} }

View File

@ -6027,6 +6027,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let _ = (context.account.postbox.combinedView(keys: summaryTags.map { tag in let _ = (context.account.postbox.combinedView(keys: summaryTags.map { tag in
return PostboxViewKey.historyTagSummaryView(tag: tag, peerId: peerId, namespace: Namespaces.Message.Cloud) return PostboxViewKey.historyTagSummaryView(tag: tag, peerId: peerId, namespace: Namespaces.Message.Cloud)
}) })
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] views in |> deliverOnMainQueue).start(next: { [weak self] views in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -6194,7 +6195,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
initialTimestamp = timestamp 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 { guard let strongSelf = self else {
c.dismiss() c.dismiss()
return return
@ -6207,7 +6210,60 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
pane.scrollToTimestamp(timestamp: timestamp) pane.scrollToTimestamp(timestamp: timestamp)
c.dismiss() 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) { func updatePresentationData(_ presentationData: PresentationData) {
@ -7427,21 +7483,26 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
private final class ContextControllerContentSourceImpl: ContextControllerContentSource { private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
let controller: ViewController let controller: ViewController
weak var sourceNode: ASDisplayNode? weak var sourceNode: ASDisplayNode?
let sourceRect: CGRect
let navigationController: NavigationController? = nil 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.controller = controller
self.sourceNode = sourceNode self.sourceNode = sourceNode
self.sourceRect = sourceRect
self.passthroughTouches = passthroughTouches
} }
func transitionInfo() -> ContextControllerTakeControllerInfo? { func transitionInfo() -> ContextControllerTakeControllerInfo? {
let sourceNode = self.sourceNode 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 return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
if let sourceNode = sourceNode { if let sourceNode = sourceNode {
return (sourceNode, sourceNode.bounds) let rect = sourceRect.isEmpty ? sourceNode.bounds : sourceRect
return (sourceNode, rect)
} else { } else {
return nil return nil
} }