diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 4a5014ca18..d777c61eca 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -411,7 +411,7 @@ private func peerAvatar(mediaBox: MediaBox, accountPeerId: PeerId, peer: Peer) - } } - let cachedPath = mediaBox.cachedRepresentationPathForId("lettersAvatar-\(peer.displayLetters.joined(separator: ","))", representationId: "intents.png", keepDuration: .shortLived) + let cachedPath = mediaBox.cachedRepresentationPathForId("lettersAvatar2-\(peer.displayLetters.joined(separator: ","))", representationId: "intents.png", keepDuration: .shortLived) if let _ = fileSize(cachedPath) { return INImage(url: URL(fileURLWithPath: storeTemporaryImage(path: cachedPath))) } else { diff --git a/submodules/CalendarMessageScreen/BUILD b/submodules/CalendarMessageScreen/BUILD index c6c2445155..1ba7060388 100644 --- a/submodules/CalendarMessageScreen/BUILD +++ b/submodules/CalendarMessageScreen/BUILD @@ -19,6 +19,7 @@ swift_library( "//submodules/AccountContext:AccountContext", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/PhotoResources:PhotoResources", + "//submodules/DirectMediaImageCache:DirectMediaImageCache" ], visibility = [ "//visibility:public", diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index f448550b5c..30148e9ae2 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -9,24 +9,27 @@ import AccountContext import TelegramPresentationData import ComponentFlow import PhotoResources +import DirectMediaImageCache private final class MediaPreviewView: UIView { private let context: AccountContext private let message: EngineMessage private let media: EngineMedia + private let imageCache: DirectMediaImageCache - private let imageView: TransformImageView + private let imageView: UIImageView private var requestedImage: Bool = false private var disposable: Disposable? - init(context: AccountContext, message: EngineMessage, media: EngineMedia) { + init(context: AccountContext, message: EngineMessage, media: EngineMedia, imageCache: DirectMediaImageCache) { self.context = context self.message = message self.media = media + self.imageCache = imageCache - self.imageView = TransformImageView() - self.imageView.contentAnimations = .subsequentUpdates + self.imageView = UIImageView() + self.imageView.contentMode = .scaleToFill super.init(frame: CGRect()) @@ -42,7 +45,53 @@ private final class MediaPreviewView: UIView { } func updateLayout(size: CGSize, synchronousLoads: Bool) { - var dimensions = CGSize(width: 100.0, height: 100.0) + let processImage: (UIImage) -> UIImage = { image in + return generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.addEllipse(in: CGRect(origin: CGPoint(), size: size)) + context.clip() + + UIGraphicsPushContext(context) + image.draw(in: CGRect(origin: CGPoint(), size: size)) + UIGraphicsPopContext() + })! + } + + if !self.requestedImage { + self.requestedImage = true + if let result = self.imageCache.getImage(message: self.message._asMessage(), media: self.media._asMedia(), width: 100, possibleWidths: [100], synchronous: false) { + if let image = result.image { + self.imageView.image = processImage(image) + } + if let signal = result.loadSignal { + self.disposable = (signal + |> map { image in + return image.flatMap(processImage) + } + |> deliverOnMainQueue).start(next: { [weak self] image in + guard let strongSelf = self else { + return + } + if let image = image { + if strongSelf.imageView.image != nil { + let tempView = UIImageView() + tempView.image = strongSelf.imageView.image + tempView.frame = strongSelf.imageView.frame + tempView.contentMode = strongSelf.imageView.contentMode + strongSelf.addSubview(tempView) + tempView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempView] _ in + tempView?.removeFromSuperview() + }) + } + strongSelf.imageView.image = image + } + }) + } + } + } + + self.imageView.frame = CGRect(origin: CGPoint(), size: size) + /*var dimensions = CGSize(width: 100.0, height: 100.0) if case let .image(image) = self.media { if let largest = largestImageRepresentation(image.representations) { dimensions = largest.dimensions.cgSize @@ -66,7 +115,7 @@ private final class MediaPreviewView: UIView { let makeLayout = self.imageView.asyncLayout() self.imageView.frame = CGRect(origin: CGPoint(), size: size) let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets())) - apply() + apply()*/ } } @@ -250,6 +299,20 @@ private final class ImageCache: Equatable { } } +private final class DayEnvironment: Equatable { + let imageCache: ImageCache + let directImageCache: DirectMediaImageCache + + init(imageCache: ImageCache, directImageCache: DirectMediaImageCache) { + self.imageCache = imageCache + self.directImageCache = directImageCache + } + + static func ==(lhs: DayEnvironment, rhs: DayEnvironment) -> Bool { + return lhs === rhs + } +} + private final class ImageComponent: Component { let image: UIImage? @@ -292,7 +355,7 @@ private final class ImageComponent: Component { } private final class DayComponent: Component { - typealias EnvironmentType = ImageCache + typealias EnvironmentType = DayEnvironment enum DaySelection { case none @@ -410,7 +473,9 @@ private final class DayComponent: Component { self.action?() } - func update(component: DayComponent, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { + func update(component: DayComponent, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { + let isFirstTime = self.action == nil + self.action = component.action self.index = component.media?.message.index self.isHighlightingEnabled = component.isEnabled && component.media != nil && !component.isSelecting @@ -418,23 +483,26 @@ private final class DayComponent: Component { 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 imageCache = environment[ImageCache.self] + let dayEnvironment = environment[DayEnvironment.self].value if component.media != nil { - self.highlightView.image = imageCache.value.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2)) + self.highlightView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2)) } else { self.highlightView.image = nil } + var animateMediaIn = false if self.currentMedia != component.media { self.currentMedia = component.media if let mediaPreviewView = self.mediaPreviewView { self.mediaPreviewView = nil mediaPreviewView.removeFromSuperview() + } else { + animateMediaIn = !isFirstTime } if let media = component.media { - let mediaPreviewView = MediaPreviewView(context: component.context, message: media.message, media: media.media) + let mediaPreviewView = MediaPreviewView(context: component.context, message: media.message, media: media.media, imageCache: dayEnvironment.directImageCache) mediaPreviewView.isUserInteractionEnabled = false self.mediaPreviewView = mediaPreviewView self.button.insertSubview(mediaPreviewView, belowSubview: self.highlightView) @@ -490,9 +558,9 @@ private final class DayComponent: Component { } selectionView.frame = contentFrame if self.mediaPreviewView != nil { - selectionView.image = imageCache.value.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor) + selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor) } else { - selectionView.image = imageCache.value.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor) + selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor) } case .middle, .none: if let selectionView = self.selectionView { @@ -509,7 +577,7 @@ private final class DayComponent: Component { contentScale = 1.0 } - let titleImage = imageCache.value.text(fontSize: titleFontSize, isSemibold: titleFontIsSemibold, color: titleColor, string: component.title) + let titleImage = dayEnvironment.imageCache.text(fontSize: titleFontSize, isSemibold: titleFontIsSemibold, color: titleColor, string: component.title) self.titleView.image = titleImage let titleSize = titleImage.size @@ -524,6 +592,10 @@ private final class DayComponent: Component { mediaPreviewView.updateLayout(size: contentFrame.size, synchronousLoads: false) mediaPreviewView.layer.sublayerTransform = CATransform3DMakeScale(contentScale, contentScale, 1.0) + + if animateMediaIn { + mediaPreviewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } } return availableSize @@ -534,13 +606,13 @@ private final class DayComponent: Component { return View() } - func update(view: View, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { + func update(view: View, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition) } } private final class MonthComponent: CombinedComponent { - typealias EnvironmentType = ImageCache + typealias EnvironmentType = DayEnvironment let context: AccountContext let model: MonthModel @@ -593,7 +665,7 @@ private final class MonthComponent: CombinedComponent { static var body: Body { let title = Child(Text.self) let weekdayTitles = ChildMap(environment: Empty.self, keyedBy: Int.self) - let days = ChildMap(environment: ImageCache.self, keyedBy: Int.self) + let days = ChildMap(environment: DayEnvironment.self, keyedBy: Int.self) let selections = ChildMap(environment: Empty.self, keyedBy: Int.self) return { context in @@ -673,7 +745,7 @@ private final class MonthComponent: CombinedComponent { } )), environment: { - context.environment[ImageCache.self] + context.environment[DayEnvironment.self] }, availableSize: CGSize(width: usableWeekdayWidth, height: weekdaySize), transition: .immediate @@ -739,7 +811,7 @@ private final class MonthComponent: CombinedComponent { if let selectedDays = context.component.selectedDays { for (lineIndex, selection) in selectionsByLine { - let imageCache = context.environment[ImageCache.self] + let dayEnvironment = context.environment[DayEnvironment.self].value let dayItemSize = updatedDays[0].size let deltaWidth = floor((weekdayWidth - dayItemSize.width) / 2.0) @@ -766,7 +838,7 @@ private final class MonthComponent: CombinedComponent { let selectionRect = CGRect(origin: CGPoint(x: minX, y: minY), size: CGSize(width: maxX - minX, height: maxY - minY)) let selection = selections[lineIndex].update( - component: AnyComponent(ImageComponent(image: imageCache.value.monthSelection(leftRadius: leftRadius, rightRadius: rightRadius, maxRadius: dayItemSize.width, color: monthSelectionColor))), + component: AnyComponent(ImageComponent(image: dayEnvironment.imageCache.monthSelection(leftRadius: leftRadius, rightRadius: rightRadius, maxRadius: dayItemSize.width, color: monthSelectionColor))), availableSize: selectionRect.size, transition: .immediate ) @@ -883,10 +955,10 @@ public final class CalendarMessageScreen: ViewController { private let calendarSource: SparseMessageCalendar private var months: [MonthModel] = [] - private var monthViews: [Int: ComponentHostView] = [:] + private var monthViews: [Int: ComponentHostView] = [:] private let contextGestureContainerNode: ContextControllerSourceNode - private let imageCache = ImageCache() + private let dayEnvironment: DayEnvironment private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? private var scrollLayout: (width: CGFloat, contentHeight: CGFloat, frames: [Int: CGRect])? @@ -933,6 +1005,8 @@ public final class CalendarMessageScreen: ViewController { self.scrollView.indicatorStyle = .black } + self.dayEnvironment = DayEnvironment(imageCache: ImageCache(), directImageCache: DirectMediaImageCache(account: context.account)) + super.init() self.contextGestureContainerNode.shouldBegin = { [weak self] point in @@ -1403,8 +1477,7 @@ public final class CalendarMessageScreen: ViewController { var contentHeight: CGFloat = layout.intrinsicInsets.bottom var frames: [Int: CGRect] = [:] - let measureView = ComponentHostView() - let imageCache = ImageCache() + let measureView = ComponentHostView() for i in 0 ..< self.months.count { let monthSize = measureView.update( transition: .immediate, @@ -1419,7 +1492,7 @@ public final class CalendarMessageScreen: ViewController { selectedDays: nil )), environment: { - imageCache + self.dayEnvironment }, containerSize: CGSize(width: layout.size.width, height: 10000.0 )) @@ -1458,7 +1531,7 @@ public final class CalendarMessageScreen: ViewController { } validMonths.insert(i) - let monthView: ComponentHostView + let monthView: ComponentHostView if let current = self.monthViews[i] { monthView = current } else { @@ -1516,7 +1589,7 @@ public final class CalendarMessageScreen: ViewController { selectedDays: self.selectionState?.dayRange )), environment: { - self.imageCache + self.dayEnvironment }, containerSize: CGSize(width: width, height: 10000.0 )) @@ -1582,14 +1655,14 @@ public final class CalendarMessageScreen: ViewController { updatedMedia[i] = [:] } - for day in 0 ..< self.months[i].numberOfDays { - let firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970) + let firstDayTimestamp = Int32(self.months[i].firstDay.timeIntervalSince1970) + for day in 0 ..< self.months[i].numberOfDays { let dayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day) - let nextDayTimestamp = firstDayTimestamp + 24 * 60 * 60 * Int32(day - 1) + let nextDayTimestamp = dayTimestamp + 24 * 60 * 60 for message in messageMap { - if message.timestamp <= dayTimestamp && message.timestamp >= nextDayTimestamp { + if message.timestamp >= dayTimestamp && message.timestamp < nextDayTimestamp { mediaLoop: for media in message.media { switch media { case _ as TelegramMediaImage, _ as TelegramMediaFile: @@ -1644,9 +1717,9 @@ public final class CalendarMessageScreen: ViewController { //TODO:localize self.navigationItem.setTitle("Calendar", animated: false) - if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { + /*if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat { self.navigationItem.setRightBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Select, style: .plain, target: self, action: #selector(self.toggleSelectPressed)), animated: false) - } + }*/ } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index a3c36d3250..c30268eb55 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -1111,7 +1111,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi self.currentItems = items self.currentActionsMinHeight = minHeight - + let previousActionsContainerNode = self.actionsContainerNode let previousActionsContainerFrame = previousActionsContainerNode.view.convert(previousActionsContainerNode.bounds, to: self.view) self.actionsContainerNode = ContextActionsContainerNode(presentationData: self.presentationData, items: items, getController: { [weak self] in @@ -1690,9 +1690,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi switch maybePassthrough { case .ignore: break - case let .dismiss(consume): + case let .dismiss(consume, hitTestResult): self.getController()?.dismiss(completion: nil) + if let hitTestResult = hitTestResult { + return hitTestResult + } if !consume { return nil } @@ -1862,7 +1865,7 @@ public final class ContextController: ViewController, StandalonePresentableContr public enum HandledTouchEvent { case ignore - case dismiss(consume: Bool) + case dismiss(consume: Bool, result: UIView?) } public var passthroughTouchEvent: ((UIView, CGPoint) -> HandledTouchEvent)? diff --git a/submodules/Display/Source/DisplayLinkAnimator.swift b/submodules/Display/Source/DisplayLinkAnimator.swift index 9b6bacae22..2aa9fd0299 100644 --- a/submodules/Display/Source/DisplayLinkAnimator.swift +++ b/submodules/Display/Source/DisplayLinkAnimator.swift @@ -101,6 +101,9 @@ public final class ConstantDisplayLinkAnimator { self.displayLink = CADisplayLink(target: DisplayLinkTarget({ [weak self] in self?.tick() }), selector: #selector(DisplayLinkTarget.event)) + if #available(iOS 15.0, *) { + self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 120.0, preferred: 120.0) + } self.displayLink.isPaused = true self.displayLink.add(to: RunLoop.main, forMode: .common) } diff --git a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift index fe99a9197f..d23b20ad8e 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift @@ -426,7 +426,7 @@ public final class SparseItemGrid: ASDisplayNode { let zoomLevel: ZoomLevel - private let scrollView: UIScrollView + let scrollView: UIScrollView private let shimmer: Shimmer var theme: PresentationTheme @@ -448,6 +448,8 @@ public final class SparseItemGrid: ASDisplayNode { let coveringOffsetUpdated: (Viewport, ContainedViewLayoutTransition) -> Void + private var decelerationAnimator: ConstantDisplayLinkAnimator? + init(theme: PresentationTheme, zoomLevel: ZoomLevel, maybeLoadHoleAnchor: @escaping (HoleAnchor, HoleLocation) -> Void, coveringOffsetUpdated: @escaping (Viewport, ContainedViewLayoutTransition) -> Void) { self.theme = theme self.zoomLevel = zoomLevel @@ -487,6 +489,10 @@ public final class SparseItemGrid: ASDisplayNode { @objc func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { self.items?.itemBinding.didScroll() + if let decelerationAnimator = self.decelerationAnimator { + self.decelerationAnimator = nil + decelerationAnimator.invalidate() + } } @objc func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -638,10 +644,16 @@ public final class SparseItemGrid: ASDisplayNode { } func anchorItem(at point: CGPoint) -> Item? { - guard let items = self.items, !items.items.isEmpty else { + guard let items = self.items, !items.items.isEmpty, let layout = self.layout else { return nil } + if layout.containerLayout.lockScrollingAtTop { + if let item = items.item(at: 0) { + return item + } + } + let localPoint = self.scrollView.convert(point, from: self.view) var closestItem: (CGFloat, AnyHashable)? @@ -714,6 +726,49 @@ public final class SparseItemGrid: ASDisplayNode { self.scrollView.setContentOffset(self.scrollView.contentOffset, animated: false) } + func transferVelocity(_ velocity: CGFloat) { + if velocity <= 0.0 { + return + } + self.decelerationAnimator?.isPaused = true + let startTime = CACurrentMediaTime() + var currentOffset = self.scrollView.contentOffset + let decelerationRate: CGFloat = 0.998 + self.scrollViewDidEndDragging(self.scrollView, willDecelerate: true) + self.decelerationAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in + guard let strongSelf = self else { + return + } + let t = CACurrentMediaTime() - startTime + var currentVelocity = velocity * 15.0 * CGFloat(pow(Double(decelerationRate), 1000.0 * t)) + currentOffset.y += currentVelocity + let maxOffset = strongSelf.scrollView.contentSize.height - strongSelf.scrollView.bounds.height + if currentOffset.y >= maxOffset { + currentOffset.y = maxOffset + currentVelocity = 0.0 + } + if currentOffset.y < 0.0 { + currentOffset.y = 0.0 + currentVelocity = 0.0 + } + + var didEnd = false + if abs(currentVelocity) < 0.1 { + strongSelf.decelerationAnimator?.isPaused = true + strongSelf.decelerationAnimator = nil + didEnd = true + } + var contentOffset = strongSelf.scrollView.contentOffset + contentOffset.y = floorToScreenPixels(currentOffset.y) + strongSelf.scrollView.setContentOffset(contentOffset, animated: false) + strongSelf.scrollViewDidScroll(strongSelf.scrollView) + if didEnd { + strongSelf.scrollViewDidEndDecelerating(strongSelf.scrollView) + } + }) + self.decelerationAnimator?.isPaused = false + } + private func updateVisibleItems(resetScrolling: Bool, synchronous: Bool, restoreScrollPosition: (y: CGFloat, index: Int)?) { guard let layout = self.layout, let items = self.items else { return @@ -951,7 +1006,7 @@ public final class SparseItemGrid: ASDisplayNode { containerInsets: layout.containerLayout.insets, contentHeight: contentHeight, contentOffset: self.scrollView.bounds.minY, - isScrolling: self.scrollView.isDragging || self.scrollView.isDecelerating, + isScrolling: self.scrollView.isDragging || self.scrollView.isDecelerating || self.decelerationAnimator != nil, dateString: dateString ?? "", theme: self.theme, transition: .immediate @@ -1006,13 +1061,15 @@ public final class SparseItemGrid: ASDisplayNode { let previousProgress = self.currentProgress self.currentProgress = progress - if let fromItem = self.fromViewport.anchorItem(at: fromAnchorFrame.center), let fromFrame = self.fromViewport.frameForItem(at: fromItem.index) { + let fixedAnchorPoint = CGPoint(x: fromAnchorFrame.minX + 1.0, y: fromAnchorFrame.minY + 1.0) + + if let fromItem = self.fromViewport.anchorItem(at: fixedAnchorPoint), let fromFrame = self.fromViewport.frameForItem(at: fromItem.index) { fromAnchorFrame.origin.y = fromFrame.midY fromAnchorFrame.origin.x = fromFrame.midX fromAnchorFrame.size.width = 0.0 } - if let toItem = self.toViewport.anchorItem(at: fromAnchorFrame.center), let toFrame = self.toViewport.frameForItem(at: toItem.index) { + if let toItem = self.toViewport.anchorItem(at: fixedAnchorPoint), let toFrame = self.toViewport.frameForItem(at: toItem.index) { toAnchorFrame.origin.y = toFrame.midY toAnchorFrame.origin.x = toFrame.midX toAnchorFrame.size.width = 0.0 @@ -1056,7 +1113,7 @@ public final class SparseItemGrid: ASDisplayNode { completion() }) - let fromAlphaStartProgress: CGFloat = 0.6 + let fromAlphaStartProgress: CGFloat = 0.7 let fromAlphaEndProgress: CGFloat = 1.0 let fromAlphaProgress = max(0.0, progress - fromAlphaStartProgress) / (fromAlphaEndProgress - fromAlphaStartProgress) @@ -1105,6 +1162,7 @@ public final class SparseItemGrid: ASDisplayNode { } public var cancelExternalContentGestures: (() -> Void)? + public var zoomLevelUpdated: ((ZoomLevel) -> Void)? public init(theme: PresentationTheme) { self.theme = theme @@ -1288,7 +1346,9 @@ public final class SparseItemGrid: ASDisplayNode { let previousViewport = strongSelf.currentViewport - strongSelf.currentViewport = progress < 0.5 ? currentViewportTransition.fromViewport : currentViewportTransition.toViewport + let updatedViewport = progress < 0.5 ? currentViewportTransition.fromViewport : currentViewportTransition.toViewport + strongSelf.currentViewport = updatedViewport + strongSelf.zoomLevelUpdated?(updatedViewport.zoomLevel) if let previousViewport = previousViewport, previousViewport !== strongSelf.currentViewport { previousViewport.removeFromSupernode() @@ -1319,7 +1379,7 @@ public final class SparseItemGrid: ASDisplayNode { self.scrollingArea.isHidden = lockScrollingAtTop self.tapRecognizer?.isEnabled = fixedItemHeight == nil - self.pinchRecognizer?.isEnabled = fixedItemHeight == nil && !lockScrollingAtTop + self.pinchRecognizer?.isEnabled = fixedItemHeight == nil if self.currentViewport == nil { let currentViewport = Viewport(theme: self.theme, zoomLevel: self.initialZoomLevel ?? ZoomLevel(rawValue: 3), maybeLoadHoleAnchor: { [weak self] holeAnchor, location in @@ -1544,4 +1604,30 @@ public final class SparseItemGrid: ASDisplayNode { itemShimmerLayer.removeFromSuperlayer() } } + + public func hitTestResultForScrolling() -> UIView? { + if let _ = self.currentViewportTransition { + return nil + } else if let currentViewport = self.currentViewport { + return currentViewport.scrollView + } else { + return nil + } + } + + public func brieflyDisableTouchActions() { + let tapEnabled = self.tapRecognizer?.isEnabled ?? true + self.tapRecognizer?.isEnabled = false + let pinchEnabled = self.pinchRecognizer?.isEnabled ?? true + self.pinchRecognizer?.isEnabled = false + + DispatchQueue.main.async { + self.tapRecognizer?.isEnabled = tapEnabled + self.pinchRecognizer?.isEnabled = pinchEnabled + } + } + + public func transferVelocity(_ velocity: CGFloat) { + self.currentViewport?.transferVelocity(velocity) + } } diff --git a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift index 38e9579515..4ebc6f519c 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift @@ -769,7 +769,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { self.offsetBarTimer = nil let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut) - transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: self.beganAtDateIndicator ? CGPoint(x: -80.0, y: 0.0) : CGPoint(x: -3.0, y: 0.0)) + transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint(x: -80.0, y: 0.0)) self.updateLineIndicator(transition: transition) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift index 5a06c2ea76..ea3c5ce6af 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift @@ -670,8 +670,8 @@ public final class SparseMessageCalendar { var messagesByDay: [Int32: Message] = [:] for period in periods { switch period { - case let .searchResultsCalendarPeriod(date, minMsgId, maxMsgId, _): - if let message = transaction.getMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: maxMsgId)) { + case let .searchResultsCalendarPeriod(date, minMsgId, _, _): + if let message = transaction.getMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: minMsgId)) { messagesByDay[date] = message } if let minMessageIdValue = minMessageId { diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 8144b38a5f..ec5be42b18 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -676,7 +676,7 @@ private func extractAccountManagerState(records: AccountRecordsView Bool { return self.itemGrid.scrollToTop() } + + func hitTestResultForScrolling() -> UIView? { + return self.itemGrid.hitTestResultForScrolling() + } + + func brieflyDisableTouchActions() { + self.itemGrid.brieflyDisableTouchActions() + } func findLoadedMessage(id: MessageId) -> Message? { guard let items = self.items else { @@ -1991,45 +2006,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro } func transferVelocity(_ velocity: CGFloat) { - /*if velocity > 0.0 { - self.decelerationAnimator?.isPaused = true - let startTime = CACurrentMediaTime() - var currentOffset = self.scrollNode.view.contentOffset - let decelerationRate: CGFloat = 0.998 - self.scrollViewDidEndDragging(self.scrollNode.view, willDecelerate: true) - self.decelerationAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in - guard let strongSelf = self else { - return - } - let t = CACurrentMediaTime() - startTime - var currentVelocity = velocity * 15.0 * CGFloat(pow(Double(decelerationRate), 1000.0 * t)) - currentOffset.y += currentVelocity - let maxOffset = strongSelf.scrollNode.view.contentSize.height - strongSelf.scrollNode.bounds.height - if currentOffset.y >= maxOffset { - currentOffset.y = maxOffset - currentVelocity = 0.0 - } - if currentOffset.y < 0.0 { - currentOffset.y = 0.0 - currentVelocity = 0.0 - } - - var didEnd = false - if abs(currentVelocity) < 0.1 { - strongSelf.decelerationAnimator?.isPaused = true - strongSelf.decelerationAnimator = nil - didEnd = true - } - var contentOffset = strongSelf.scrollNode.view.contentOffset - contentOffset.y = floorToScreenPixels(currentOffset.y) - strongSelf.scrollNode.view.setContentOffset(contentOffset, animated: false) - strongSelf.scrollViewDidScroll(strongSelf.scrollNode.view) - if didEnd { - strongSelf.scrollViewDidEndDecelerating(strongSelf.scrollNode.view) - } - }) - self.decelerationAnimator?.isPaused = false - }*/ + self.itemGrid.transferVelocity(velocity) } func cancelPreviewGestures() { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 55eaf63bfc..857957a682 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -2059,7 +2059,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate switch previewData { case let .gallery(gallery): gallery.setHintWillBePresentedInPreviewingContext(true) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: node, sourceRect: rect)), items: items |> map { ContextController.Items(items: $0) }, gesture: gesture) strongSelf.controller?.presentInGlobalOverlay(contextController) case .instantPage: break @@ -6120,9 +6120,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate items.append(.action(generateAction(true))) items.append(.action(generateAction(false))) + var ignoreNextActions = false items.append(.action(ContextMenuActionItem(text: "Show Calendar", icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor) }, action: { _, a in + if ignoreNextActions { + return + } + ignoreNextActions = true a(.default) self?.openMediaCalendar() @@ -6204,14 +6209,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let localPoint = strongSelf.view.convert(sourceView.convert(point, to: nil), from: nil) guard let localResult = strongSelf.hitTest(localPoint, with: nil) else { - return .dismiss(consume: true) + return .dismiss(consume: true, result: nil) } var testView: UIView? = localResult while true { if let testViewValue = testView { - if testViewValue.asyncdisplaykit_node is PeerInfoVisualMediaPaneNode { - return .dismiss(consume: false) + if let node = testViewValue.asyncdisplaykit_node as? PeerInfoVisualMediaPaneNode { + node.brieflyDisableTouchActions() + return .dismiss(consume: false, result: nil) } else { testView = testViewValue.superview } @@ -6220,7 +6226,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } - return .dismiss(consume: true) + return .dismiss(consume: true, result: nil) } strongSelf.mediaGalleryContextMenu = contextController controller.presentInGlobalOverlay(contextController)