From f936636f6ad038ecdae81b1e833173704fb938f5 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 9 Nov 2021 14:36:59 +0400 Subject: [PATCH 1/3] Enable range message deletion --- submodules/TelegramUI/Sources/ChatController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c2ab63641a..6eb6432b69 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -11411,7 +11411,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var selectDay: ((Int32) -> Void)? var openClearHistory: ((Int32) -> Void)? - let enableMessageRangeDeletion: Bool = false + let enableMessageRangeDeletion: Bool = peerId.namespace == Namespaces.Peer.CloudUser let calendarScreen = CalendarMessageScreen( context: self.context, From ed5e3b3c5ad03a4b6cfa4a916ed700f725189842 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 9 Nov 2021 14:37:39 +0400 Subject: [PATCH 2/3] Temp --- .../Telegram-iOS/en.lproj/Localizable.strings | 1 + .../Sources/CalendarMessageScreen.swift | 364 ++++++++++++---- .../Source/Components/Text.swift | 2 +- submodules/Display/Source/GenerateImage.swift | 95 +++++ submodules/Display/Source/ListView.swift | 96 +++-- .../Display/Source/TransformImageNode.swift | 201 +++++++-- .../Sources/SparseDiscreteScrollingArea.swift | 393 ++++++++++++++++++ .../Sources/SparseItemGridScrollingArea.swift | 2 +- .../Messages/SparseMessageList.swift | 105 +++++ .../Messages/TelegramEngineMessages.swift | 4 + .../Sources/ChatControllerNode.swift | 30 +- .../Sources/ChatHistoryListNode.swift | 21 +- .../ChatMessageInteractiveMediaNode.swift | 1 + .../ChatMessageWebpageBubbleContentNode.swift | 14 +- 14 files changed, 1170 insertions(+), 159 deletions(-) create mode 100644 submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4c16d038c0..30046f25db 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -2891,6 +2891,7 @@ Unused sets are archived when you add more."; "FastTwoStepSetup.EmailHelp" = "Please add your valid e-mail. It is the only way to recover a forgotten password."; "Conversation.ViewMessage" = "VIEW MESSAGE"; +"Conversation.ViewPost" = "VIEW POST"; "GroupInfo.GroupHistory" = "History For New Members"; "GroupInfo.GroupHistoryVisible" = "Visible"; diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift index 361e6f2f46..ec099f974b 100644 --- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift +++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift @@ -12,14 +12,28 @@ import PhotoResources import DirectMediaImageCache import TelegramStringFormatting -private final class MediaPreviewView: UIView { +private final class NullActionClass: NSObject, CAAction { + @objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { + } +} + +private let nullAction = NullActionClass() + +private class SimpleLayer: CALayer { + override func action(forKey event: String) -> CAAction? { + return nullAction + } + + func update(size: CGSize) { + } +} + +private final class MediaPreviewView: SimpleLayer { private let context: AccountContext private let message: EngineMessage private let media: EngineMedia private let imageCache: DirectMediaImageCache - private let imageView: UIImageView - private var requestedImage: Bool = false private var disposable: Disposable? @@ -29,12 +43,9 @@ private final class MediaPreviewView: UIView { self.media = media self.imageCache = imageCache - self.imageView = UIImageView() - self.imageView.contentMode = .scaleToFill + super.init() - super.init(frame: CGRect()) - - self.addSubview(self.imageView) + self.contentsGravity = .resize } required init?(coder: NSCoder) { @@ -62,7 +73,7 @@ private final class MediaPreviewView: UIView { 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) + self.contents = processImage(image).cgImage } if let signal = result.loadSignal { self.disposable = (signal @@ -74,49 +85,22 @@ private final class MediaPreviewView: UIView { 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() + if strongSelf.contents != nil { + let tempView = SimpleLayer() + tempView.contents = strongSelf.contents + tempView.frame = strongSelf.bounds + tempView.contentsGravity = strongSelf.contentsGravity + strongSelf.addSublayer(tempView) + tempView.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak tempView] _ in + tempView?.removeFromSuperlayer() }) } - strongSelf.imageView.image = image + strongSelf.contents = image.cgImage } }) } } } - - 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 - if !self.requestedImage { - self.requestedImage = true - let signal = mediaGridMessagePhoto(account: self.context.account, photoReference: .message(message: MessageReference(self.message._asMessage()), media: image), fullRepresentationSize: CGSize(width: 36.0, height: 36.0), synchronousLoad: synchronousLoads) - self.imageView.setSignal(signal, attemptSynchronously: synchronousLoads) - } - } - } else if case let .file(file) = self.media { - if let mediaDimensions = file.dimensions { - dimensions = mediaDimensions.cgSize - if !self.requestedImage { - self.requestedImage = true - let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, videoReference: .message(message: MessageReference(self.message._asMessage()), media: file), synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true, useMiniThumbnailIfAvailable: true) - self.imageView.setSignal(signal, attemptSynchronously: synchronousLoads) - } - } - } - - 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()*/ } } @@ -425,12 +409,10 @@ private final class DayComponent: Component { return true } - final class View: UIView { - private let button: HighlightTrackingButton - - private let highlightView: UIImageView - private var selectionView: UIImageView? - private let titleView: UIImageView + final class View: HighlightTrackingButton { + private let highlightView: SimpleLayer + private var selectionView: SimpleLayer? + private let titleView: SimpleLayer private var mediaPreviewView: MediaPreviewView? private var action: (() -> Void)? @@ -441,29 +423,24 @@ private final class DayComponent: Component { private var isHighlightingEnabled: Bool = false init() { - self.button = HighlightTrackingButton() - self.highlightView = UIImageView() - self.highlightView.isUserInteractionEnabled = false - self.titleView = UIImageView() - self.titleView.isUserInteractionEnabled = false + self.highlightView = SimpleLayer() + self.titleView = SimpleLayer() super.init(frame: CGRect()) - self.button.addSubview(self.highlightView) - self.button.addSubview(self.titleView) + self.layer.addSublayer(self.highlightView) + self.layer.addSublayer(self.titleView) - self.addSubview(self.button) - - self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) - self.button.highligthedChanged = { [weak self] highligthed in + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + self.highligthedChanged = { [weak self] highligthed in guard let strongSelf = self, let mediaPreviewView = strongSelf.mediaPreviewView else { return } if strongSelf.isHighlightingEnabled && highligthed { - mediaPreviewView.alpha = 0.8 + mediaPreviewView.opacity = 0.8 } else { let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut) - transition.updateAlpha(layer: mediaPreviewView.layer, alpha: 1.0) + transition.updateAlpha(layer: mediaPreviewView, alpha: 1.0) } } } @@ -489,9 +466,9 @@ private final class DayComponent: Component { let dayEnvironment = environment[DayEnvironment.self].value if component.media != nil { - self.highlightView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2)) + self.highlightView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: UIColor(white: 0.0, alpha: 0.2)).cgImage } else { - self.highlightView.image = nil + self.highlightView.contents = nil } var animateMediaIn = false @@ -500,16 +477,15 @@ private final class DayComponent: Component { if let mediaPreviewView = self.mediaPreviewView { self.mediaPreviewView = nil - mediaPreviewView.removeFromSuperview() + mediaPreviewView.removeFromSuperlayer() } else { animateMediaIn = !isFirstTime } if let media = component.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) + self.layer.insertSublayer(mediaPreviewView, below: self.highlightView) } } @@ -552,24 +528,24 @@ private final class DayComponent: Component { switch component.selection { case .edge: - let selectionView: UIImageView + let selectionView: SimpleLayer if let current = self.selectionView { selectionView = current } else { - selectionView = UIImageView() + selectionView = SimpleLayer() self.selectionView = selectionView - self.button.insertSubview(selectionView, belowSubview: self.titleView) + self.layer.insertSublayer(selectionView, below: self.titleView) } selectionView.frame = contentFrame if self.mediaPreviewView != nil { - selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor) + selectionView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: diameter - 2.0 * 2.0, color: component.theme.list.itemCheckColors.fillColor).cgImage } else { - selectionView.image = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor) + selectionView.contents = dayEnvironment.imageCache.filledCircle(diameter: diameter, innerDiameter: nil, color: component.theme.list.itemCheckColors.fillColor).cgImage } case .middle, .none: if let selectionView = self.selectionView { self.selectionView = nil - selectionView.removeFromSuperview() + selectionView.removeFromSuperlayer() } } @@ -583,32 +559,31 @@ private final class DayComponent: Component { let titleImage = dayEnvironment.imageCache.text(fontSize: titleFontSize, isSemibold: titleFontIsSemibold, color: titleColor, string: component.title) if animateMediaIn { - let previousTitleView = UIImageView(image: self.titleView.image) + let previousTitleView = SimpleLayer() + previousTitleView.contents = self.titleView.contents previousTitleView.frame = self.titleView.frame - self.titleView.superview?.insertSubview(previousTitleView, aboveSubview: self.titleView) - previousTitleView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousTitleView] _ in - previousTitleView?.removeFromSuperview() + self.titleView.superlayer?.insertSublayer(previousTitleView, above: self.titleView) + previousTitleView.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousTitleView] _ in + previousTitleView?.removeFromSuperlayer() }) - self.titleView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.16) + self.titleView.animateAlpha(from: 0.0, to: 1.0, duration: 0.16) } - self.titleView.image = titleImage + self.titleView.contents = titleImage.cgImage let titleSize = titleImage.size - transition.setFrame(view: self.highlightView, frame: CGRect(origin: CGPoint(x: contentFrame.midX - contentFrame.width * contentScale / 2.0, y: contentFrame.midY - contentFrame.width * contentScale / 2.0), size: CGSize(width: contentFrame.width * contentScale, height: contentFrame.height * contentScale))) + self.highlightView.frame = CGRect(origin: CGPoint(x: contentFrame.midX - contentFrame.width * contentScale / 2.0, y: contentFrame.midY - contentFrame.width * contentScale / 2.0), size: CGSize(width: contentFrame.width * contentScale, height: contentFrame.height * contentScale)) self.titleView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize) - self.button.frame = CGRect(origin: CGPoint(), size: availableSize) - if let mediaPreviewView = self.mediaPreviewView { mediaPreviewView.frame = contentFrame mediaPreviewView.updateLayout(size: contentFrame.size, synchronousLoads: false) - mediaPreviewView.layer.sublayerTransform = CATransform3DMakeScale(contentScale, contentScale, 1.0) + mediaPreviewView.sublayerTransform = CATransform3DMakeScale(contentScale, contentScale, 1.0) if animateMediaIn { - mediaPreviewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.highlightView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + mediaPreviewView.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.highlightView.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } } @@ -625,6 +600,219 @@ private final class DayComponent: Component { } } +private final class ManualMonthComponent: Component { + typealias EnvironmentType = DayEnvironment + + let context: AccountContext + let model: MonthModel + let foregroundColor: UIColor + let strings: PresentationStrings + let theme: PresentationTheme + let dayAction: (Int32) -> Void + let selectedDays: ClosedRange? + + init( + context: AccountContext, + model: MonthModel, + foregroundColor: UIColor, + strings: PresentationStrings, + theme: PresentationTheme, + dayAction: @escaping (Int32) -> Void, + selectedDays: ClosedRange? + ) { + self.context = context + self.model = model + self.foregroundColor = foregroundColor + self.strings = strings + self.theme = theme + self.dayAction = dayAction + self.selectedDays = selectedDays + } + + static func ==(lhs: ManualMonthComponent, rhs: ManualMonthComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.model != rhs.model { + return false + } + if lhs.foregroundColor != rhs.foregroundColor { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.selectedDays != rhs.selectedDays { + return false + } + return true + } + + final class View: UIView { + private let title: Text.View + private var weekdayTitles: [UIImageView] = [] + private var days: [Int: DayComponent.View] = [:] + + init() { + self.title = Text.View() + + super.init(frame: CGRect()) + + self.addSubview(self.title) + } + + required init?(coder aDecoder: NSCoder) { + preconditionFailure() + } + + func update(component: ManualMonthComponent, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { + let sideInset: CGFloat = 14.0 + let titleWeekdaysSpacing: CGFloat = 18.0 + let weekdayDaySpacing: CGFloat = 14.0 + let weekdaySize: CGFloat = 46.0 + let weekdaySpacing: CGFloat = 6.0 + + let dayEnvironment = environment[DayEnvironment.self].value + + let usableWeekdayWidth = floor((availableSize.width - sideInset * 2.0 - weekdaySpacing * 6.0) / 7.0) + let weekdayWidth = floor((availableSize.width - sideInset * 2.0) / 7.0) + + let monthName = stringForMonth(strings: component.strings, month: Int32(component.model.index - 1), ofYear: Int32(component.model.year - 1900)) + + let titleSize = self.title.update( + component: Text( + text: monthName, + font: Font.semibold(17.0), + color: component.foregroundColor + ), + availableSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) + ) + + let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: 0.0), size: titleSize) + self.title.frame = titleFrame + + for i in 0 ..< 7 { + let weekdayTitle: UIImageView + if self.weekdayTitles.count > i { + weekdayTitle = self.weekdayTitles[i] + } else { + weekdayTitle = UIImageView() + self.addSubview(weekdayTitle) + self.weekdayTitles.append(weekdayTitle) + } + let image = dayEnvironment.imageCache.text(fontSize: 10.0, isSemibold: false, color: component.foregroundColor, string: gridDayName(index: i, firstDayOfWeek: component.model.firstWeekday, strings: component.strings)) + weekdayTitle.image = image + } + + let baseWeekdayTitleY = titleFrame.maxY + titleWeekdaysSpacing + var maxWeekdayY = baseWeekdayTitleY + + for i in 0 ..< self.weekdayTitles.count { + guard let image = self.weekdayTitles[i].image else { + continue + } + let weekdaySize = image.size + let weekdayFrame = CGRect(origin: CGPoint(x: sideInset + CGFloat(i) * weekdayWidth + floor((weekdayWidth - weekdaySize.width) / 2.0), y: baseWeekdayTitleY), size: weekdaySize) + maxWeekdayY = max(maxWeekdayY, weekdayFrame.maxY) + self.weekdayTitles[i].frame = weekdayFrame + } + + var daySizes: [Int: CGSize] = [:] + for index in 0 ..< component.model.numberOfDays { + let dayOfMonth = index + 1 + let isCurrent = component.model.currentYear == component.model.year && component.model.currentMonth == component.model.index && component.model.currentDayOfMonth == dayOfMonth + var isEnabled = true + if component.model.currentYear == component.model.year { + if component.model.currentMonth == component.model.index { + if dayOfMonth > component.model.currentDayOfMonth { + isEnabled = false + } + } else if component.model.index > component.model.currentMonth { + isEnabled = false + } + } else if component.model.year > component.model.currentYear { + isEnabled = false + } + + let dayTimestamp = Int32(component.model.firstDay.timeIntervalSince1970) + 24 * 60 * 60 * Int32(index) + let dayAction = component.dayAction + + let daySelection: DayComponent.DaySelection + if let selectedDays = component.selectedDays, selectedDays.contains(dayTimestamp) { + if selectedDays.lowerBound == dayTimestamp || selectedDays.upperBound == dayTimestamp { + daySelection = .edge + } else { + daySelection = .middle + } + } else { + daySelection = .none + } + + let day: DayComponent.View + if let current = self.days[index] { + day = current + } else { + day = DayComponent.View() + self.addSubview(day) + self.days[index] = day + } + + let daySize = day.update( + component: DayComponent( + title: "\(dayOfMonth)", + isCurrent: isCurrent, + isEnabled: isEnabled, + theme: component.theme, + context: component.context, + timestamp: dayTimestamp, + media: component.model.mediaByDay[index], + selection: daySelection, + isSelecting: component.selectedDays != nil, + action: { + dayAction(dayTimestamp) + } + ), + availableSize: CGSize(width: usableWeekdayWidth, height: weekdaySize), + environment: environment, + transition: .immediate + ) + daySizes[index] = daySize + } + + let baseDayY = maxWeekdayY + weekdayDaySpacing + var maxDayY = baseDayY + + for i in 0 ..< component.model.numberOfDays { + guard let dayView = self.days[i], let dayItemSize = daySizes[i] else { + continue + } + let gridIndex = gridDayOffset(firstDayOfWeek: component.model.firstWeekday, firstWeekdayOfMonth: component.model.firstDayWeekday) + i + let rowIndex = gridIndex % 7 + let lineIndex = gridIndex / 7 + + let gridX = sideInset + CGFloat(rowIndex) * weekdayWidth + let gridY = baseDayY + CGFloat(lineIndex) * (weekdaySize + weekdaySpacing) + let dayFrame = CGRect(origin: CGPoint(x: gridX + floor((weekdayWidth - dayItemSize.width) / 2.0), y: gridY + floor((weekdaySize - dayItemSize.height) / 2.0)), size: dayItemSize) + maxDayY = max(maxDayY, gridY + weekdaySize) + dayView.frame = dayFrame + } + + return CGSize(width: availableSize.width, height: maxDayY) + } + } + + func makeView() -> View { + return View() + } + + 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 = DayEnvironment @@ -1054,7 +1242,7 @@ public final class CalendarMessageScreen: ViewController { return false } - guard let dayView = result.superview as? DayComponent.View else { + guard let dayView = result as? DayComponent.View else { return false } diff --git a/submodules/ComponentFlow/Source/Components/Text.swift b/submodules/ComponentFlow/Source/Components/Text.swift index 153cfd551b..4c7b405ae6 100644 --- a/submodules/ComponentFlow/Source/Components/Text.swift +++ b/submodules/ComponentFlow/Source/Components/Text.swift @@ -30,7 +30,7 @@ public final class Text: Component { public final class View: UIView { private var measureState: MeasureState? - func update(component: Text, availableSize: CGSize) -> CGSize { + public func update(component: Text, availableSize: CGSize) -> CGSize { let attributedText = NSAttributedString(string: component.text, attributes: [ NSAttributedString.Key.font: component.font, NSAttributedString.Key.foregroundColor: component.color diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift index a3a1775129..19f22159bb 100644 --- a/submodules/Display/Source/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Accelerate import AsyncDisplayKit +import CoreMedia public let deviceColorSpace: CGColorSpace = { if #available(iOSApplicationExtension 9.3, iOS 9.3, *) { @@ -581,6 +582,30 @@ public class DrawingContext { return nil } } + + public func generatePixelBuffer() -> CVPixelBuffer? { + if self.scaledSize.width.isZero || self.scaledSize.height.isZero { + return nil + } + if self.hasGeneratedImage { + preconditionFailure() + } + + let ioSurfaceProperties = NSMutableDictionary() + let options = NSMutableDictionary() + options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString) + + var pixelBuffer: CVPixelBuffer? + CVPixelBufferCreateWithBytes(nil, Int(self.scaledSize.width), Int(self.scaledSize.height), kCVPixelFormatType_32BGRA, self.bytes, self.bytesPerRow, { pointer, _ in + if let pointer = pointer { + Unmanaged.fromOpaque(pointer).release() + } + }, Unmanaged.passRetained(self.imageBuffer).toOpaque(), options as CFDictionary, &pixelBuffer) + + self.hasGeneratedImage = true + + return pixelBuffer + } public func colorAt(_ point: CGPoint) -> UIColor { let x = Int(point.x * self.scale) @@ -649,6 +674,76 @@ public class DrawingContext { } } +public extension UIImage { + var cvPixelBuffer: CVPixelBuffer? { + guard let cgImage = self.cgImage else { + return nil + } + let _ = cgImage + + var maybePixelBuffer: CVPixelBuffer? = nil + let ioSurfaceProperties = NSMutableDictionary() + let options = NSMutableDictionary() + options.setObject(ioSurfaceProperties, forKey: kCVPixelBufferIOSurfacePropertiesKey as NSString) + + let _ = CVPixelBufferCreate(kCFAllocatorDefault, Int(size.width * self.scale), Int(size.height * self.scale), kCVPixelFormatType_32ARGB, options as CFDictionary, &maybePixelBuffer) + guard let pixelBuffer = maybePixelBuffer else { + return nil + } + CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) + defer { + CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) + } + + let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) + + let context = CGContext( + data: baseAddress, + width: Int(self.size.width * self.scale), + height: Int(self.size.height * self.scale), + bitsPerComponent: 8, + bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGBitmapInfo.byteOrder32Big.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue, + releaseCallback: nil, + releaseInfo: nil + )! + context.clear(CGRect(origin: .zero, size: CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale))) + context.draw(cgImage, in: CGRect(origin: .zero, size: CGSize(width: self.size.width * self.scale, height: self.size.height * self.scale))) + + return pixelBuffer + } + + var cmSampleBuffer: CMSampleBuffer? { + guard let pixelBuffer = self.cvPixelBuffer else { + return nil + } + var newSampleBuffer: CMSampleBuffer? = nil + + var timingInfo = CMSampleTimingInfo( + duration: CMTimeMake(value: 1, timescale: 30), + presentationTimeStamp: CMTimeMake(value: 0, timescale: 30), + decodeTimeStamp: CMTimeMake(value: 0, timescale: 30) + ) + + var videoInfo: CMVideoFormatDescription? = nil + CMVideoFormatDescriptionCreateForImageBuffer(allocator: nil, imageBuffer: pixelBuffer, formatDescriptionOut: &videoInfo) + guard let videoInfo = videoInfo else { + return nil + } + CMSampleBufferCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: videoInfo, sampleTiming: &timingInfo, sampleBufferOut: &newSampleBuffer) + + if let newSampleBuffer = newSampleBuffer { + let attachments = CMSampleBufferGetSampleAttachmentsArray(newSampleBuffer, createIfNecessary: true)! as NSArray + let dict = attachments[0] as! NSMutableDictionary + + dict.setValue(kCFBooleanTrue as AnyObject, forKey: kCMSampleAttachmentKey_DisplayImmediately as NSString as String) + } + + return newSampleBuffer + } +} + public enum ParsingError: Error { case Generic } diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 6b93d5a465..0f9be9f0c5 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -150,6 +150,41 @@ private func cancelContextGestures(view: UIView) { } open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate { + public struct ScrollingIndicatorState { + public struct Item { + public var index: Int + public var offset: CGFloat + public var height: CGFloat + + public init( + index: Int, + offset: CGFloat, + height: CGFloat + ) { + self.index = index + self.offset = offset + self.height = height + } + } + + public var insets: UIEdgeInsets + public var topItem: Item + public var bottomItem: Item + public var itemCount: Int + + public init( + insets: UIEdgeInsets, + topItem: Item, + bottomItem: Item, + itemCount: Int + ) { + self.insets = insets + self.topItem = topItem + self.bottomItem = bottomItem + self.itemCount = itemCount + } + } + public final let scroller: ListViewScroller public private(set) final var visibleSize: CGSize = CGSize() public private(set) final var insets = UIEdgeInsets() @@ -214,14 +249,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } public final var snapToBottomInsetUntilFirstInteraction: Bool = false - public final var updateFloatingHeaderOffset: ((CGFloat, ContainedViewLayoutTransition) -> Void)? { - didSet { - - } - } - + public final var updateFloatingHeaderOffset: ((CGFloat, ContainedViewLayoutTransition) -> Void)? public final var didScrollWithOffset: ((CGFloat, ContainedViewLayoutTransition, ListViewItemNode?) -> Void)? public final var addContentOffset: ((CGFloat, ListViewItemNode?) -> Void)? + + public final var updateScrollingIndicator: ((ScrollingIndicatorState?, ContainedViewLayoutTransition) -> Void)? private var topItemOverscrollBackground: ListViewOverscrollBackgroundNode? private var bottomItemOverscrollBackground: ASDisplayNode? @@ -3766,33 +3798,51 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture break } } - if let topIndexAndBoundary = topIndexAndBoundary, let bottomIndexAndBoundary = bottomIndexAndBoundary { + + var scrollingIndicatorStateValue: ScrollingIndicatorState? + if let topIndexAndBoundaryValue = topIndexAndBoundary, let bottomIndexAndBoundaryValue = bottomIndexAndBoundary { + let scrollingIndicatorState = ScrollingIndicatorState( + insets: self.insets, + topItem: ScrollingIndicatorState.Item( + index: topIndexAndBoundaryValue.0, + offset: topIndexAndBoundaryValue.1, + height: topIndexAndBoundaryValue.2 + ), + bottomItem: ScrollingIndicatorState.Item( + index: bottomIndexAndBoundaryValue.0, + offset: bottomIndexAndBoundaryValue.1, + height: bottomIndexAndBoundaryValue.2 + ), + itemCount: self.items.count + ) + scrollingIndicatorStateValue = scrollingIndicatorState + let averageRangeItemHeight: CGFloat = 44.0 - var upperItemsHeight = floor(averageRangeItemHeight * CGFloat(topIndexAndBoundary.0)) - var approximateContentHeight = CGFloat(self.items.count) * averageRangeItemHeight - if topIndexAndBoundary.0 >= 0 && self.items[topIndexAndBoundary.0].approximateHeight.isZero { + var upperItemsHeight = floor(averageRangeItemHeight * CGFloat(scrollingIndicatorState.topItem.index)) + var approximateContentHeight = CGFloat(scrollingIndicatorState.itemCount) * averageRangeItemHeight + if scrollingIndicatorState.topItem.index >= 0 && self.items[scrollingIndicatorState.topItem.index].approximateHeight.isZero { upperItemsHeight -= averageRangeItemHeight approximateContentHeight -= averageRangeItemHeight } var convertedTopBoundary: CGFloat - if topIndexAndBoundary.1 < self.insets.top { - convertedTopBoundary = (topIndexAndBoundary.1 - self.insets.top) * averageRangeItemHeight / topIndexAndBoundary.2 + if scrollingIndicatorState.topItem.offset < self.insets.top { + convertedTopBoundary = (scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top) * averageRangeItemHeight / scrollingIndicatorState.topItem.height } else { - convertedTopBoundary = topIndexAndBoundary.1 - self.insets.top + convertedTopBoundary = scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top } convertedTopBoundary -= upperItemsHeight let approximateOffset = -convertedTopBoundary var convertedBottomBoundary: CGFloat = 0.0 - if bottomIndexAndBoundary.1 > self.visibleSize.height - self.insets.bottom { - convertedBottomBoundary = ((self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1) * averageRangeItemHeight / bottomIndexAndBoundary.2 + if scrollingIndicatorState.bottomItem.offset > self.visibleSize.height - self.insets.bottom { + convertedBottomBoundary = ((self.visibleSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset) * averageRangeItemHeight / scrollingIndicatorState.bottomItem.height } else { - convertedBottomBoundary = (self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1 + convertedBottomBoundary = (self.visibleSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset } - convertedBottomBoundary += CGFloat(bottomIndexAndBoundary.0 + 1) * averageRangeItemHeight + convertedBottomBoundary += CGFloat(scrollingIndicatorState.bottomItem.index + 1) * averageRangeItemHeight let approximateVisibleHeight = max(0.0, convertedBottomBoundary - approximateOffset) @@ -3801,8 +3851,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let indicatorSideInset: CGFloat = 3.0 var indicatorTopInset: CGFloat = 3.0 if self.verticalScrollIndicatorFollowsOverscroll { - if topIndexAndBoundary.0 == 0 { - indicatorTopInset = max(topIndexAndBoundary.1 + 3.0 - self.insets.top, 3.0) + if scrollingIndicatorState.topItem.index == 0 { + indicatorTopInset = max(scrollingIndicatorState.topItem.offset + 3.0 - self.insets.top, 3.0) } } let indicatorBottomInset: CGFloat = 3.0 @@ -3814,7 +3864,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if approximateContentHeight <= 0 { indicatorHeight = 0.0 } else { - indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - self.insets.top - self.insets.bottom) / approximateContentHeight)) + indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - scrollingIndicatorState.insets.top - scrollingIndicatorState.insets.bottom) / approximateContentHeight)) } let upperBound = self.scrollIndicatorInsets.top + indicatorTopInset @@ -3852,6 +3902,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } else { verticalScrollIndicator.isHidden = true } + + self.updateScrollingIndicator?(scrollingIndicatorStateValue, transition) } } @@ -4638,7 +4690,3 @@ private func findAccessibilityFocus(_ node: ASDisplayNode) -> Bool { } return false } - -public func randomfqweeqwf() { - print("t") -} diff --git a/submodules/Display/Source/TransformImageNode.swift b/submodules/Display/Source/TransformImageNode.swift index 1cb1926a3f..93285773a1 100644 --- a/submodules/Display/Source/TransformImageNode.swift +++ b/submodules/Display/Source/TransformImageNode.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import SwiftSignalKit +import AVFoundation public struct TransformImageNodeContentAnimations: OptionSet { public var rawValue: Int32 @@ -21,10 +22,60 @@ open class TransformImageNode: ASDisplayNode { private var currentTransform: ((TransformImageArguments) -> DrawingContext?)? private var currentArguments: TransformImageArguments? + private var image: UIImage? private var argumentsPromise = ValuePromise(ignoreRepeated: true) private var overlayColor: UIColor? private var overlayNode: ASDisplayNode? + + private var captureProtectedContentLayer: CaptureProtectedContentLayer? + + public var captureProtected: Bool = false { + didSet { + if self.captureProtected != oldValue { + if self.captureProtected { + if self.captureProtectedContentLayer == nil { + let captureProtectedContentLayer = CaptureProtectedContentLayer() + self.captureProtectedContentLayer = captureProtectedContentLayer + if #available(iOS 13.0, *) { + captureProtectedContentLayer.preventsCapture = true + captureProtectedContentLayer.preventsDisplaySleepDuringVideoPlayback = false + } + captureProtectedContentLayer.frame = self.bounds + self.layer.addSublayer(captureProtectedContentLayer) + if let image = self.image { + if let cmSampleBuffer = image.cmSampleBuffer { + captureProtectedContentLayer.enqueue(cmSampleBuffer) + } + } + self.contents = nil + } + } else if let captureProtectedContentLayer = self.captureProtectedContentLayer { + self.captureProtectedContentLayer = nil + captureProtectedContentLayer.removeFromSuperlayer() + } + } + } + } + + open override var bounds: CGRect { + didSet { + if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size { + captureProtectedContentLayer.frame = super.bounds + } + } + } + + open override var frame: CGRect { + didSet { + if let overlayNode = self.overlayNode { + overlayNode.frame = self.bounds + } + if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size { + captureProtectedContentLayer.frame = super.bounds + } + } + } deinit { self.disposable.dispose() @@ -38,19 +89,12 @@ open class TransformImageNode: ASDisplayNode { } } - override open var frame: CGRect { - didSet { - if let overlayNode = self.overlayNode { - overlayNode.frame = self.bounds - } - } - } - public func reset() { self.disposable.set(nil) self.currentArguments = nil self.currentTransform = nil self.contents = nil + self.image = nil } public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) { @@ -85,21 +129,31 @@ open class TransformImageNode: ASDisplayNode { strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } } else if strongSelf.contentAnimations.contains(.subsequentUpdates) { - let tempLayer = CALayer() - tempLayer.frame = strongSelf.bounds - tempLayer.contentsGravity = strongSelf.layer.contentsGravity - tempLayer.contents = strongSelf.contents - strongSelf.layer.addSublayer(tempLayer) - tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in - tempLayer?.removeFromSuperlayer() - }) + if let _ = strongSelf.captureProtectedContentLayer { + } else { + let tempLayer = CALayer() + tempLayer.frame = strongSelf.bounds + tempLayer.contentsGravity = strongSelf.layer.contentsGravity + tempLayer.contents = strongSelf.contents + strongSelf.layer.addSublayer(tempLayer) + tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in + tempLayer?.removeFromSuperlayer() + }) + } } var imageUpdate: UIImage? if let (transform, arguments, image) = next { strongSelf.currentTransform = transform strongSelf.currentArguments = arguments - strongSelf.contents = image?.cgImage + if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer { + if let cmSampleBuffer = image?.cmSampleBuffer { + captureProtectedContentLayer.enqueue(cmSampleBuffer) + } + } else { + strongSelf.contents = image?.cgImage + } + strongSelf.image = image imageUpdate = image } if let _ = strongSelf.overlayColor { @@ -135,7 +189,14 @@ open class TransformImageNode: ASDisplayNode { return } if let image = updatedImage { - strongSelf.contents = image.cgImage + if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer { + if let cmSampleBuffer = image.cmSampleBuffer { + captureProtectedContentLayer.enqueue(cmSampleBuffer) + } + } else { + strongSelf.contents = image.cgImage + } + strongSelf.image = image strongSelf.currentArguments = arguments if let _ = strongSelf.overlayColor { strongSelf.applyOverlayColor(animated: false) @@ -207,6 +268,19 @@ open class TransformImageNode: ASDisplayNode { } } +private final class NullActionClass: NSObject, CAAction { + @objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) { + } +} + +private let nullAction = NullActionClass() + +private class CaptureProtectedContentLayer: AVSampleBufferDisplayLayer { + override func action(forKey event: String) -> CAAction? { + return nullAction + } +} + open class TransformImageView: UIView { public var imageUpdated: ((UIImage?) -> Void)? public var contentAnimations: TransformImageNodeContentAnimations = [] @@ -215,10 +289,55 @@ open class TransformImageView: UIView { private var currentTransform: ((TransformImageArguments) -> DrawingContext?)? private var currentArguments: TransformImageArguments? private var argumentsPromise = ValuePromise(ignoreRepeated: true) + private var image: UIImage? + + private var captureProtectedContentLayer: CaptureProtectedContentLayer? private var overlayColor: UIColor? private var overlayView: UIView? + open override var bounds: CGRect { + didSet { + if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size { + captureProtectedContentLayer.frame = super.bounds + } + } + } + + open override var frame: CGRect { + didSet { + if let overlayView = self.overlayView { + overlayView.frame = self.bounds + } + if let captureProtectedContentLayer = self.captureProtectedContentLayer, super.bounds.size != oldValue.size { + captureProtectedContentLayer.frame = super.bounds + } + } + } + + public var captureProtected: Bool = false { + didSet { + if self.captureProtected != oldValue { + if self.captureProtected { + if self.captureProtectedContentLayer == nil { + let captureProtectedContentLayer = CaptureProtectedContentLayer() + captureProtectedContentLayer.frame = self.bounds + self.layer.addSublayer(captureProtectedContentLayer) + if let image = self.image { + if let cmSampleBuffer = image.cmSampleBuffer { + captureProtectedContentLayer.enqueue(cmSampleBuffer) + } + } + self.layer.contents = nil + } + } else if let captureProtectedContentLayer = self.captureProtectedContentLayer { + self.captureProtectedContentLayer = nil + captureProtectedContentLayer.removeFromSuperlayer() + } + } + } + } + override public init(frame: CGRect) { super.init(frame: frame) @@ -235,19 +354,13 @@ open class TransformImageView: UIView { self.disposable.dispose() } - override open var frame: CGRect { - didSet { - if let overlayView = self.overlayView { - overlayView.frame = self.bounds - } - } - } - public func reset() { self.disposable.set(nil) self.currentArguments = nil self.currentTransform = nil self.layer.contents = nil + self.image = nil + self.captureProtectedContentLayer?.flushAndRemoveImage() } public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) { @@ -277,26 +390,36 @@ open class TransformImageView: UIView { self.disposable.set((result |> deliverOnMainQueue).start(next: { [weak self] next in let apply: () -> Void = { if let strongSelf = self { - if strongSelf.layer.contents == nil { + if strongSelf.image == nil { if strongSelf.contentAnimations.contains(.firstUpdate) && !attemptSynchronously { strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) } } else if strongSelf.contentAnimations.contains(.subsequentUpdates) { - let tempLayer = CALayer() - tempLayer.frame = strongSelf.bounds - tempLayer.contentsGravity = strongSelf.layer.contentsGravity - tempLayer.contents = strongSelf.layer.contents - strongSelf.layer.addSublayer(tempLayer) - tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in - tempLayer?.removeFromSuperlayer() - }) + if let _ = strongSelf.captureProtectedContentLayer { + } else { + let tempLayer = CALayer() + tempLayer.frame = strongSelf.bounds + tempLayer.contentsGravity = strongSelf.layer.contentsGravity + tempLayer.contents = strongSelf.layer.contents + strongSelf.layer.addSublayer(tempLayer) + tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in + tempLayer?.removeFromSuperlayer() + }) + } } var imageUpdate: UIImage? if let (transform, arguments, image) = next { strongSelf.currentTransform = transform strongSelf.currentArguments = arguments - strongSelf.layer.contents = image?.cgImage + if let captureProtectedContentLayer = strongSelf.captureProtectedContentLayer { + if let cmSampleBuffer = image?.cmSampleBuffer { + captureProtectedContentLayer.enqueue(cmSampleBuffer) + } + } else { + strongSelf.layer.contents = image?.cgImage + } + strongSelf.image = image imageUpdate = image } if let _ = strongSelf.overlayColor { @@ -369,13 +492,13 @@ open class TransformImageView: UIView { private func applyOverlayColor(animated: Bool) { if let overlayColor = self.overlayColor { - if let contents = self.layer.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID { + if let image = self.image { if let overlayView = self.overlayView { - (overlayView as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate) + (overlayView as! UIImageView).image = UIImage(cgImage: image.cgImage!).withRenderingMode(.alwaysTemplate) overlayView.tintColor = overlayColor } else { let overlayView = UIImageView() - overlayView.image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate) + overlayView.image = UIImage(cgImage: image.cgImage!).withRenderingMode(.alwaysTemplate) overlayView.tintColor = overlayColor overlayView.frame = self.bounds self.addSubview(overlayView) diff --git a/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift new file mode 100644 index 0000000000..278c3387cd --- /dev/null +++ b/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift @@ -0,0 +1,393 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import SwiftSignalKit +import TelegramPresentationData + +public final class SparseDiscreteScrollingArea: ASDisplayNode { + private final class DragGesture: UIGestureRecognizer { + private let shouldBegin: (CGPoint) -> Bool + private let began: () -> Void + private let ended: () -> Void + private let moved: (CGFloat) -> Void + + private var initialLocation: CGPoint? + + public init( + shouldBegin: @escaping (CGPoint) -> Bool, + began: @escaping () -> Void, + ended: @escaping () -> Void, + moved: @escaping (CGFloat) -> Void + ) { + self.shouldBegin = shouldBegin + self.began = began + self.ended = ended + self.moved = moved + + super.init(target: nil, action: nil) + } + + deinit { + } + + override public func reset() { + super.reset() + + self.initialLocation = nil + self.initialLocation = nil + } + + override public func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if self.numberOfTouches > 1 { + self.state = .failed + self.ended() + return + } + + if self.state == .possible { + if let location = touches.first?.location(in: self.view) { + if self.shouldBegin(location) { + self.initialLocation = location + self.state = .began + self.began() + } else { + self.state = .failed + } + } else { + self.state = .failed + } + } + } + + override public func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.initialLocation = nil + + if self.state == .began || self.state == .changed { + self.ended() + self.state = .failed + } + } + + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.initialLocation = nil + + if self.state == .began || self.state == .changed { + self.ended() + self.state = .failed + } + } + + override public func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) { + self.state = .changed + let offset = location.y - initialLocation.y + self.moved(offset) + } + } + } + + private let dateIndicator: ComponentHostView + private let lineIndicator: UIImageView + + private var containerSize: CGSize? + private var indicatorPosition: CGFloat? + private var scrollIndicatorHeight: CGFloat? + + private var dragGesture: DragGesture? + public private(set) var isDragging: Bool = false + + private var activityTimer: SwiftSignalKit.Timer? + + public var openCurrentDate: (() -> Void)? + + private var offsetBarTimer: SwiftSignalKit.Timer? + private let hapticFeedback = HapticFeedback() + + private var theme: PresentationTheme? + + override public init() { + self.dateIndicator = ComponentHostView() + self.lineIndicator = UIImageView() + + self.dateIndicator.alpha = 0.0 + self.lineIndicator.alpha = 0.0 + + super.init() + + self.dateIndicator.isUserInteractionEnabled = false + self.lineIndicator.isUserInteractionEnabled = false + + self.view.addSubview(self.dateIndicator) + self.view.addSubview(self.lineIndicator) + + let dragGesture = DragGesture( + shouldBegin: { [weak self] point in + guard let _ = self else { + return false + } + return true + }, + began: { [weak self] in + guard let strongSelf = self else { + return + } + + let offsetBarTimer = SwiftSignalKit.Timer(timeout: 0.2, repeat: false, completion: { + guard let strongSelf = self else { + return + } + strongSelf.performOffsetBarTimerEvent() + }, queue: .mainQueue()) + strongSelf.offsetBarTimer?.invalidate() + strongSelf.offsetBarTimer = offsetBarTimer + offsetBarTimer.start() + + strongSelf.isDragging = true + + /*if let scrollView = strongSelf.beginScrolling?() { + strongSelf.draggingScrollView = scrollView + strongSelf.scrollingInitialOffset = scrollView.contentOffset.y + strongSelf.setContentOffset?(scrollView.contentOffset) + }*/ + + strongSelf.updateActivityTimer(isScrolling: false) + }, + ended: { [weak self] in + guard let strongSelf = self else { + return + } + if strongSelf.offsetBarTimer != nil { + strongSelf.offsetBarTimer?.invalidate() + strongSelf.offsetBarTimer = nil + + strongSelf.openCurrentDate?() + } + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut) + transition.updateSublayerTransformOffset(layer: strongSelf.dateIndicator.layer, offset: CGPoint(x: 0.0, y: 0.0)) + + strongSelf.isDragging = false + + //strongSelf.updateLineIndicator(transition: transition) + + strongSelf.updateActivityTimer(isScrolling: false) + }, + moved: { [weak self] relativeOffset in + guard let strongSelf = self else { + return + } + + let _ = relativeOffset + + if strongSelf.offsetBarTimer != nil { + strongSelf.offsetBarTimer?.invalidate() + strongSelf.offsetBarTimer = nil + strongSelf.performOffsetBarTimerEvent() + } + } + ) + self.dragGesture = dragGesture + + self.view.addGestureRecognizer(dragGesture) + } + + private func performOffsetBarTimerEvent() { + self.hapticFeedback.impact() + self.offsetBarTimer = nil + + /*let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut) + transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint(x: -80.0, y: 0.0)) + self.updateLineIndicator(transition: transition)*/ + } + + func feedbackTap() { + self.hapticFeedback.tap() + } + + public func update( + containerSize: CGSize, + containerInsets: UIEdgeInsets, + scrollingState: ListView.ScrollingIndicatorState?, + isScrolling: Bool, + theme: PresentationTheme, + transition: ContainedViewLayoutTransition + ) { + self.containerSize = containerSize + if self.theme !== theme { + self.theme = theme + + /*var backgroundColors: [UInt32] = [] + switch chatPresentationInterfaceState.chatWallpaper { + case let .file(file): + if file.isPattern { + backgroundColors = file.settings.colors + } + case let .gradient(gradient): + backgroundColors = gradient.colors + case let .color(color): + backgroundColors = [color] + default: + break + }*/ + let lineColor: UIColor + if theme.overallDarkAppearance { + lineColor = UIColor(white: 0.0, alpha: 0.3) + } else { + lineColor = UIColor(white: 0.0, alpha: 0.3) + } + self.lineIndicator.image = generateStretchableFilledCircleImage(diameter: 3.0, color: lineColor, strokeColor: nil, strokeWidth: nil, backgroundColor: nil) + } + + if self.dateIndicator.alpha.isZero { + let transition: ContainedViewLayoutTransition = .immediate + transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint()) + } + + if isScrolling { + self.updateActivityTimer(isScrolling: true) + } + + let indicatorSize = self.dateIndicator.update( + transition: .immediate, + component: AnyComponent(SparseItemGridScrollingIndicatorComponent( + backgroundColor: theme.list.itemBlocksBackgroundColor, + shadowColor: .black, + foregroundColor: theme.list.itemPrimaryTextColor, + dateString: "Date" + )), + environment: {}, + containerSize: containerSize + ) + let _ = indicatorSize + + self.dateIndicator.isHidden = true + + + if let scrollingIndicatorState = scrollingState { + let averageRangeItemHeight: CGFloat = 44.0 + + let upperItemsHeight = floor(averageRangeItemHeight * CGFloat(scrollingIndicatorState.topItem.index)) + let approximateContentHeight = CGFloat(scrollingIndicatorState.itemCount) * averageRangeItemHeight + + var convertedTopBoundary: CGFloat + if scrollingIndicatorState.topItem.offset < scrollingIndicatorState.insets.top { + convertedTopBoundary = (scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top) * averageRangeItemHeight / scrollingIndicatorState.topItem.height + } else { + convertedTopBoundary = scrollingIndicatorState.topItem.offset - scrollingIndicatorState.insets.top + } + convertedTopBoundary -= upperItemsHeight + + let approximateOffset = -convertedTopBoundary + + var convertedBottomBoundary: CGFloat = 0.0 + if scrollingIndicatorState.bottomItem.offset > containerSize.height - scrollingIndicatorState.insets.bottom { + convertedBottomBoundary = ((containerSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset) * averageRangeItemHeight / scrollingIndicatorState.bottomItem.height + } else { + convertedBottomBoundary = (containerSize.height - scrollingIndicatorState.insets.bottom) - scrollingIndicatorState.bottomItem.offset + } + convertedBottomBoundary += CGFloat(scrollingIndicatorState.bottomItem.index + 1) * averageRangeItemHeight + + let approximateVisibleHeight = max(0.0, convertedBottomBoundary - approximateOffset) + + let approximateScrollingProgress = approximateOffset / (approximateContentHeight - approximateVisibleHeight) + + let indicatorSideInset: CGFloat = 3.0 + let indicatorTopInset: CGFloat = 3.0 + /*if self.verticalScrollIndicatorFollowsOverscroll { + if scrollingIndicatorState.topItem.index == 0 { + indicatorTopInset = max(scrollingIndicatorState.topItem.offset + 3.0 - self.insets.top, 3.0) + } + }*/ + let indicatorBottomInset: CGFloat = 3.0 + let minIndicatorContentHeight: CGFloat = 12.0 + let minIndicatorHeight: CGFloat = 6.0 + + let visibleHeightWithoutIndicatorInsets = containerSize.height - containerInsets.top - containerInsets.bottom - indicatorTopInset - indicatorBottomInset + let indicatorHeight: CGFloat + if approximateContentHeight <= 0 { + indicatorHeight = 0.0 + } else { + indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (containerSize.height - scrollingIndicatorState.insets.top - scrollingIndicatorState.insets.bottom) / approximateContentHeight)) + } + + let upperBound = containerInsets.top + indicatorTopInset + let lowerBound = containerSize.height - containerInsets.bottom - indicatorTopInset - indicatorBottomInset - indicatorHeight + + let indicatorOffset = ceilToScreenPixels(upperBound * (1.0 - approximateScrollingProgress) + lowerBound * approximateScrollingProgress) + + //var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorSideInset : (self.visibleSize.width - 3.0 - indicatorSideInset), y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) + + var indicatorFrame = CGRect(origin: CGPoint(x: containerSize.width - 3.0 - indicatorSideInset, y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) + + if indicatorFrame.minY < containerInsets.top + indicatorTopInset { + indicatorFrame.size.height -= containerInsets.top + indicatorTopInset - indicatorFrame.minY + indicatorFrame.origin.y = containerInsets.top + indicatorTopInset + indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height) + } + if indicatorFrame.maxY > containerSize.height - (containerInsets.bottom + indicatorTopInset + indicatorBottomInset) { + indicatorFrame.size.height -= indicatorFrame.maxY - (containerSize.height - (containerInsets.bottom + indicatorTopInset)) + indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height) + indicatorFrame.origin.y = containerSize.height - (containerInsets.bottom + indicatorBottomInset) - indicatorFrame.height + } + + if indicatorFrame.origin.y.isNaN { + indicatorFrame.origin.y = indicatorTopInset + } + + if indicatorHeight >= visibleHeightWithoutIndicatorInsets { + self.lineIndicator.isHidden = true + self.lineIndicator.frame = indicatorFrame + } else { + if self.lineIndicator.isHidden { + self.lineIndicator.isHidden = false + self.lineIndicator.frame = indicatorFrame + } else { + self.lineIndicator.frame = indicatorFrame + } + } + } else { + self.lineIndicator.isHidden = true + } + } + + private func updateActivityTimer(isScrolling: Bool) { + self.activityTimer?.invalidate() + + if self.isDragging { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) + transition.updateAlpha(layer: self.dateIndicator.layer, alpha: 1.0) + transition.updateAlpha(layer: self.lineIndicator.layer, alpha: 1.0) + } else { + self.activityTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) + transition.updateAlpha(layer: strongSelf.dateIndicator.layer, alpha: 0.0) + transition.updateAlpha(layer: strongSelf.lineIndicator.layer, alpha: 0.0) + }, queue: .mainQueue()) + self.activityTimer?.start() + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.lineIndicator.alpha <= 0.01 { + return nil + } + if self.lineIndicator.frame.insetBy(dx: -4.0, dy: -2.0).contains(point) { + return super.hitTest(point, with: event) + } + + return nil + } +} diff --git a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift index 20eadc11bb..c0f4e37302 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift @@ -555,7 +555,7 @@ private final class ShadowRoundedRectangle: Component { } } -private final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { +final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { let backgroundColor: UIColor let shadowColor: UIColor let foregroundColor: UIColor diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift index ae0191afa3..bc99ee142a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift @@ -709,6 +709,111 @@ public final class SparseMessageList { } } +public final class SparseMessageScrollingContext { + public struct State: Equatable { + public var totalCount: Int + public var minTimestamp: Int32 + } + + private final class Impl { + private let queue: Queue + private let account: Account + private let peerId: PeerId + + let statePromise = Promise() + + private let disposable = MetaDisposable() + + init(queue: Queue, account: Account, peerId: PeerId) { + self.queue = queue + self.account = account + self.peerId = peerId + + self.reload() + } + + deinit { + self.disposable.dispose() + } + + private func reload() { + let account = self.account + let peerId = self.peerId + + let signal: Signal = self.account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(peerId).flatMap(apiInputPeer) + } + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .single(nil) + } + return account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: 1, offsetDate: 0, addOffset: -1, limit: 1, maxId: 0, minId: 0, hash: 0)) + |> map { result -> State? in + let messages: [Api.Message] + let totalCount: Int + + switch result { + case let .messages(apiMessages, _, _): + messages = apiMessages + totalCount = messages.count + case let .messagesSlice(_, count, _, _, apiMessages, _, _): + messages = apiMessages + totalCount = Int(count) + case let .channelMessages(_, _, count, _, apiMessages, _, _): + messages = apiMessages + totalCount = Int(count) + case .messagesNotModified: + messages = [] + totalCount = 0 + } + + if let apiMessage = messages.first, let message = StoreMessage(apiMessage: apiMessage) { + return State(totalCount: totalCount, minTimestamp: message.timestamp) + } else { + return State(totalCount: 0, minTimestamp: 0) + } + } + |> `catch` { _ -> Signal in + return .single(nil) + } + } + + self.disposable.set((signal |> deliverOn(self.queue)).start(next: { [weak self] state in + guard let strongSelf = self else { + return + } + if let state = state { + strongSelf.statePromise.set(.single(state)) + } + })) + } + } + + private let queue: Queue + private let impl: QueueLocalObject + + public var state: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + self.impl.with { impl in + disposable.set(impl.statePromise.get().start(next: subscriber.putNext)) + } + + return disposable + } + } + + init(account: Account, peerId: PeerId) { + let queue = Queue() + self.queue = queue + + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue, account: account, peerId: peerId) + }) + } +} + public final class SparseMessageCalendar { private final class Impl { struct InternalState { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index c60a74cc9c..ae3f5417e1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -251,6 +251,10 @@ public extension TelegramEngine { return SparseMessageCalendar(account: self.account, peerId: peerId, messageTag: tag) } + public func sparseMessageScrollingContext(peerId: EnginePeer.Id) -> SparseMessageScrollingContext { + return SparseMessageScrollingContext(account: self.account, peerId: peerId) + } + public func refreshMessageTagStats(peerId: EnginePeer.Id, tags: [EngineMessage.Tags]) -> Signal { let account = self.account return self.account.postbox.transaction { transaction -> Api.InputPeer? in diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 20c49b6e8a..2db64d996f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -16,6 +16,7 @@ import FastBlur import ConfettiEffect import WallpaperBackgroundNode import GridMessageSelectionNode +import SparseItemGrid final class VideoNavigationControllerDropContentItem: NavigationControllerDropContentItem { let itemNode: OverlayMediaItemNode @@ -81,6 +82,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { let backgroundNode: WallpaperBackgroundNode let historyNode: ChatHistoryListNode + let historyScrollingArea: SparseDiscreteScrollingArea var blurredHistoryNode: ASImageNode? let historyNodeContainer: ASDisplayNode let loadingNode: ChatLoadingNode @@ -323,8 +325,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { return getMessageTransitionNode?() }) self.historyNode.rotated = true + + self.historyScrollingArea = SparseDiscreteScrollingArea() + self.historyNodeContainer = ASDisplayNode() self.historyNodeContainer.addSubnode(self.historyNode) + self.historyNodeContainer.addSubnode(self.historyScrollingArea) var getContentAreaInScreenSpaceImpl: (() -> CGRect)? var onTransitionEventImpl: ((ContainedViewLayoutTransition) -> Void)? @@ -438,7 +444,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } }) - var backgroundColors: [UInt32] = [] + /*var backgroundColors: [UInt32] = [] switch chatPresentationInterfaceState.chatWallpaper { case let .file(file): if file.isPattern { @@ -461,7 +467,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { } else { self.historyNode.verticalScrollIndicatorColor = UIColor(white: 0.5, alpha: 0.8) } - self.historyNode.enableExtractedBackgrounds = true + self.historyNode.enableExtractedBackgrounds = true*/ + self.historyNode.verticalScrollIndicatorColor = .clear self.addSubnode(self.backgroundNode) self.addSubnode(self.historyNodeContainer) @@ -522,6 +529,23 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.textInputPanelNode?.updateActivity = { [weak self] in self?.updateTypingActivity(true) } + + self.historyNode.updateScrollingIndicator = { [weak self] scrollingState, transition in + guard let strongSelf = self else { + return + } + guard let (_, _) = strongSelf.validLayout else { + return + } + strongSelf.historyScrollingArea.update( + containerSize: strongSelf.historyNode.bounds.size, + containerInsets: UIEdgeInsets(top: strongSelf.historyNode.scrollIndicatorInsets.bottom, left: 0.0, bottom: strongSelf.historyNode.scrollIndicatorInsets.top, right: 0.0), + scrollingState: scrollingState, + isScrolling: true, + theme: strongSelf.chatPresentationInterfaceState.theme, + transition: transition + ) + } } deinit { @@ -1041,6 +1065,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let blurredHistoryNode = self.blurredHistoryNode { transition.updateFrame(node: blurredHistoryNode, frame: contentBounds) } + + transition.updateFrame(node: self.historyScrollingArea, frame: contentBounds) transition.updateFrame(node: self.loadingNode, frame: contentBounds) diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 414a9b0879..fbcfe05213 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -562,6 +562,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private let adMessagesContext: AdMessagesHistoryContext? private var preloadAdPeerId: PeerId? private let preloadAdPeerDisposable = MetaDisposable() + + private let sparseScrollingContext: SparseMessageScrollingContext? private let clientId: Atomic @@ -588,8 +590,19 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.prefetchManager = InChatPrefetchManager(context: context) + var displayAdPeer: PeerId? + var sparseScrollPeerId: PeerId? + switch subject { + case .none, .message: + if case let .peer(peerId) = chatLocation { + displayAdPeer = peerId + sparseScrollPeerId = peerId + } + default: + break + } var adMessages: Signal<[Message], NoError> - if case .bubbles = mode, case let .peer(peerId) = chatLocation, case .none = subject { + if case .bubbles = mode, let peerId = displayAdPeer { let adMessagesContext = context.engine.messages.adMessages(peerId: peerId) self.adMessagesContext = adMessagesContext adMessages = adMessagesContext.state @@ -597,6 +610,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.adMessagesContext = nil adMessages = .single([]) } + + if case .bubbles = mode, let peerId = sparseScrollPeerId { + self.sparseScrollingContext = context.engine.messages.sparseMessageScrollingContext(peerId: peerId) + } else { + self.sparseScrollingContext = nil + } let clientId = Atomic(value: nextClientId) self.clientId = clientId diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index fd984494ea..07d3fdc11a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -969,6 +969,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio } if let updateImageSignal = updateImageSignal { + strongSelf.imageNode.captureProtected = message.id.peerId.namespace == Namespaces.Peer.SecretChat strongSelf.imageNode.setSignal(updateImageSignal(synchronousLoads, false), attemptSynchronously: synchronousLoads) var imageDimensions: CGSize? diff --git a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift index 1446db7a84..ad9857853d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWebpageBubbleContentNode.swift @@ -321,7 +321,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { break } } - } else if let _ = item.message.adAttribute { + } else if let adAttribute = item.message.adAttribute { title = nil subtitle = nil text = item.message.text @@ -342,9 +342,17 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { if let author = item.message.author as? TelegramUser, author.botInfo != nil { actionTitle = item.presentationData.strings.Conversation_ViewBot } else if let author = item.message.author as? TelegramChannel, case .group = author.info { - actionTitle = item.presentationData.strings.Conversation_ViewGroup + if adAttribute.messageId != nil { + actionTitle = item.presentationData.strings.Conversation_ViewPost + } else { + actionTitle = item.presentationData.strings.Conversation_ViewGroup + } } else { - actionTitle = item.presentationData.strings.Conversation_ViewChannel + if adAttribute.messageId != nil { + actionTitle = item.presentationData.strings.Conversation_ViewMessage + } else { + actionTitle = item.presentationData.strings.Conversation_ViewChannel + } } displayLine = false } From 920e16a17f2365b08507643f4d03b3d8f74f5c57 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 9 Nov 2021 21:55:54 +0400 Subject: [PATCH 3/3] Local scrolling --- submodules/Display/Source/ListView.swift | 4 + .../Postbox/Sources/MessageHistoryTable.swift | 32 ++- .../Postbox/Sources/MessageHistoryView.swift | 1 - .../Sources/MessageHistoryViewState.swift | 29 +-- submodules/Postbox/Sources/Postbox.swift | 5 + .../Sources/SparseDiscreteScrollingArea.swift | 192 ++++++++++++++---- .../Sources/ChatControllerNode.swift | 18 +- .../Sources/ChatHistoryEntriesForView.swift | 12 +- .../TelegramUI/Sources/ChatHistoryEntry.swift | 11 +- .../Sources/ChatHistoryListNode.swift | 184 +++++++++++++++-- .../Sources/ChatMessageBubbleItemNode.swift | 14 +- .../TelegramUI/Sources/ChatMessageItem.swift | 20 +- .../Sources/ChatMessageItemView.swift | 4 +- .../ChatRecentActionsHistoryTransition.swift | 80 ++++---- .../Sources/SharedAccountContext.swift | 4 +- 15 files changed, 440 insertions(+), 170 deletions(-) diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 0f9be9f0c5..7ed049ce8b 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -284,6 +284,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture public private(set) var isTracking = false public private(set) var trackingOffset: CGFloat = 0.0 public private(set) var beganTrackingAtTopOrigin = false + public private(set) var isDragging = false public private(set) var isDeceleratingAfterTracking = false private final var transactionQueue: ListViewTransactionQueue @@ -770,6 +771,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.snapToBottomInsetUntilFirstInteraction = false } self.scrolledToItem = nil + + self.isDragging = true self.beganInteractiveDragging(self.touchesPosition) @@ -781,6 +784,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + self.isDragging = false if decelerate { self.lastContentOffsetTimestamp = CACurrentMediaTime() self.isDeceleratingAfterTracking = true diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 80b0d3ec11..43706be814 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -2691,6 +2691,20 @@ final class MessageHistoryTable: Table { } return closestIndex } + + func findMessageAtAbsoluteIndex(peerId: PeerId, namespace: MessageId.Namespace, index: Int) -> MessageIndex? { + var count = 0 + var result: MessageIndex? + self.valueBox.range(self.table, start: self.upperBound(peerId: peerId, namespace: namespace), end: self.lowerBound(peerId: peerId, namespace: namespace), keys: { key in + if count == index { + result = extractKey(key) + return false + } + count += 1 + return true + }, limit: 10000) + return result + } func findRandomMessage(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, ignoreIds: ([MessageId], Set)) -> MessageIndex? { if let index = self.tagsTable.findRandomIndex(peerId: peerId, namespace: namespace, tag: tag, ignoreIds: ignoreIds, isMessage: { index in @@ -2811,8 +2825,22 @@ final class MessageHistoryTable: Table { return indices } - func getMessageCountInRange(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { - return self.tagsTable.getMessageCountInRange(tag: tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) + func getMessageCountInRange(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags?, lowerBound: MessageIndex, upperBound: MessageIndex) -> Int { + if let tag = tag { + return self.tagsTable.getMessageCountInRange(tag: tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) + } else { + precondition(lowerBound.id.namespace == namespace) + precondition(upperBound.id.namespace == namespace) + var lowerBoundKey = self.key(lowerBound) + if lowerBound.timestamp > 1 { + lowerBoundKey = lowerBoundKey.predecessor + } + var upperBoundKey = self.key(upperBound) + if upperBound.timestamp < Int32.max - 1 { + upperBoundKey = upperBoundKey.successor + } + return Int(self.valueBox.count(self.table, start: lowerBoundKey, end: upperBoundKey)) + } } func setPendingMessageAction(id: MessageId, type: PendingMessageActionType, action: PendingMessageActionData?, pendingActionsOperations: inout [PendingMessageActionsOperation], updatedMessageActionsSummaries: inout [PendingMessageActionsSummaryKey: Int32]) { diff --git a/submodules/Postbox/Sources/MessageHistoryView.swift b/submodules/Postbox/Sources/MessageHistoryView.swift index 280bdb4bf0..046926c74a 100644 --- a/submodules/Postbox/Sources/MessageHistoryView.swift +++ b/submodules/Postbox/Sources/MessageHistoryView.swift @@ -245,7 +245,6 @@ public struct MessageHistoryViewOrderStatistics: OptionSet { } public static let combinedLocation = MessageHistoryViewOrderStatistics(rawValue: 1 << 0) - public static let locationWithinMonth = MessageHistoryViewOrderStatistics(rawValue: 1 << 1) } public final class MessageHistoryViewExternalInput: Equatable { diff --git a/submodules/Postbox/Sources/MessageHistoryViewState.swift b/submodules/Postbox/Sources/MessageHistoryViewState.swift index d0e40d65d6..b1bd48ca4f 100644 --- a/submodules/Postbox/Sources/MessageHistoryViewState.swift +++ b/submodules/Postbox/Sources/MessageHistoryViewState.swift @@ -80,7 +80,7 @@ private extension MessageHistoryInput { if let automatic = automatic { return postbox.messageHistoryTagsTable.getMessageCountInRange(tag: automatic.tag, peerId: peerId, namespace: namespace, lowerBound: lowerBound, upperBound: upperBound) } else { - return 0 + return postbox.messageHistoryTable.getMessageCountInRange(peerId: peerId, namespace: namespace, tag: nil, lowerBound: lowerBound, upperBound: upperBound) } case .external: return 0 @@ -981,7 +981,7 @@ final class HistoryViewLoadedState { var entries = OrderedHistoryViewEntries(lowerOrAtAnchor: lowerOrAtAnchorMessages, higherThanAnchor: higherThanAnchorMessages) - if case let .automatic(automaticValue) = self.input, let _ = automaticValue, self.statistics.contains(.combinedLocation), let first = entries.first { + if case .automatic = self.input, self.statistics.contains(.combinedLocation), let first = entries.first { let messageIndex = first.index let previousCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: MessageIndex.lowerBound(peerId: space.peerId, namespace: space.namespace), upperBound: messageIndex) let nextCount = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: MessageIndex.upperBound(peerId: space.peerId, namespace: space.namespace)) @@ -999,30 +999,7 @@ final class HistoryViewLoadedState { } } } - - if case let .automatic(tagValue) = self.input, let _ = tagValue, self.statistics.contains(.locationWithinMonth), let first = entries.first { - let messageIndex = first.index - let monthIndex = MessageMonthIndex(timestamp: messageIndex.timestamp) - let count = self.input.getMessageCountInRange(postbox: postbox, peerId: space.peerId, namespace: space.namespace, lowerBound: messageIndex, upperBound: monthUpperBoundIndex(peerId: space.peerId, namespace: space.namespace, index: monthIndex)) - - var nextLocation: (MessageMonthIndex, Int) = (monthIndex, count - 1) - - let _ = entries.mutableScan { entry in - let messageMonthIndex = MessageMonthIndex(timestamp: entry.index.timestamp) - if messageMonthIndex != nextLocation.0 { - nextLocation = (messageMonthIndex, 0) - } - - let currentIndexInMonth = nextLocation.1 - nextLocation.1 = max(0, nextLocation.1 - 1) - switch entry { - case let .IntermediateMessageEntry(message, location, _): - return .IntermediateMessageEntry(message, location, MessageHistoryEntryMonthLocation(indexInMonth: Int32(currentIndexInMonth))) - case let .MessageEntry(entry, reloadAssociatedMessages, reloadPeers): - return .MessageEntry(MessageHistoryMessageEntry(message: entry.message, location: entry.location, monthLocation: MessageHistoryEntryMonthLocation(indexInMonth: Int32(currentIndexInMonth)), attributes: entry.attributes), reloadAssociatedMessages: reloadAssociatedMessages, reloadPeers: reloadPeers) - } - } - } + if canContainHoles(space, input: self.input, seedConfiguration: self.seedConfiguration) { entries.fixMonotony() diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 37a84f0a7c..e3df35b01a 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -703,6 +703,11 @@ public final class Transaction { assert(!self.disposed) return self.postbox?.messageHistoryTable.findClosestMessageIndex(peerId: peerId, timestamp: timestamp)?.id } + + public func findMessageAtAbsoluteIndex(peerId: PeerId, namespace: MessageId.Namespace, index: Int) -> MessageIndex? { + assert(!self.disposed) + return self.postbox?.messageHistoryTable.findMessageAtAbsoluteIndex(peerId: peerId, namespace: namespace, index: index) + } public func findRandomMessage(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, ignoreIds: ([MessageId], Set)) -> MessageIndex? { assert(!self.disposed) diff --git a/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift index 278c3387cd..7d183a6cbf 100644 --- a/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift +++ b/submodules/SparseItemGrid/Sources/SparseDiscreteScrollingArea.swift @@ -102,6 +102,10 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { private var containerSize: CGSize? private var indicatorPosition: CGFloat? private var scrollIndicatorHeight: CGFloat? + private var scrollIndicatorRange: (CGFloat, CGFloat)? + + private var initialDraggingOffset: CGFloat? + private var draggingOffset: CGFloat? private var dragGesture: DragGesture? public private(set) var isDragging: Bool = false @@ -110,11 +114,25 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { public var openCurrentDate: (() -> Void)? + public var navigateToPosition: ((Float) -> Void)? + private var navigatingToPositionOffset: CGFloat? + private var offsetBarTimer: SwiftSignalKit.Timer? private let hapticFeedback = HapticFeedback() private var theme: PresentationTheme? + private struct State { + var containerSize: CGSize + var containerInsets: UIEdgeInsets + var scrollingState: ListView.ScrollingIndicatorState? + var isScrolling: Bool + var isDragging: Bool + var theme: PresentationTheme + } + + private var state: State? + override public init() { self.dateIndicator = ComponentHostView() self.lineIndicator = UIImageView() @@ -153,12 +171,19 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { offsetBarTimer.start() strongSelf.isDragging = true + strongSelf.initialDraggingOffset = strongSelf.lineIndicator.frame.minY + strongSelf.draggingOffset = 0.0 - /*if let scrollView = strongSelf.beginScrolling?() { - strongSelf.draggingScrollView = scrollView - strongSelf.scrollingInitialOffset = scrollView.contentOffset.y - strongSelf.setContentOffset?(scrollView.contentOffset) - }*/ + if let state = strongSelf.state { + strongSelf.update( + containerSize: state.containerSize, + containerInsets: state.containerInsets, + scrollingState: state.scrollingState, + isScrolling: state.isScrolling, + theme: state.theme, + transition: .animated(duration: 0.2, curve: .easeInOut) + ) + } strongSelf.updateActivityTimer(isScrolling: false) }, @@ -177,8 +202,29 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { transition.updateSublayerTransformOffset(layer: strongSelf.dateIndicator.layer, offset: CGPoint(x: 0.0, y: 0.0)) strongSelf.isDragging = false + if let _ = strongSelf.initialDraggingOffset, let _ = strongSelf.draggingOffset, let scrollIndicatorRange = strongSelf.scrollIndicatorRange { + strongSelf.navigatingToPositionOffset = strongSelf.lineIndicator.frame.minY + var absoluteOffset = strongSelf.lineIndicator.frame.minY - scrollIndicatorRange.0 + absoluteOffset /= (scrollIndicatorRange.1 - scrollIndicatorRange.0) + absoluteOffset = abs(absoluteOffset) + absoluteOffset = 1.0 - absoluteOffset + strongSelf.navigateToPosition?(Float(absoluteOffset)) + } else { + strongSelf.navigatingToPositionOffset = nil + } + strongSelf.initialDraggingOffset = nil + strongSelf.draggingOffset = nil - //strongSelf.updateLineIndicator(transition: transition) + if let state = strongSelf.state { + strongSelf.update( + containerSize: state.containerSize, + containerInsets: state.containerInsets, + scrollingState: state.scrollingState, + isScrolling: state.isScrolling, + theme: state.theme, + transition: transition + ) + } strongSelf.updateActivityTimer(isScrolling: false) }, @@ -187,13 +233,24 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { return } - let _ = relativeOffset - if strongSelf.offsetBarTimer != nil { strongSelf.offsetBarTimer?.invalidate() strongSelf.offsetBarTimer = nil strongSelf.performOffsetBarTimerEvent() } + + strongSelf.draggingOffset = relativeOffset + + if let state = strongSelf.state { + strongSelf.update( + containerSize: state.containerSize, + containerInsets: state.containerInsets, + scrollingState: state.scrollingState, + isScrolling: state.isScrolling, + theme: state.theme, + transition: .immediate + ) + } } ) self.dragGesture = dragGesture @@ -214,6 +271,20 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { self.hapticFeedback.tap() } + public func resetNavigatingToPosition() { + self.navigatingToPositionOffset = nil + if let state = self.state { + self.update( + containerSize: state.containerSize, + containerInsets: state.containerInsets, + scrollingState: state.scrollingState, + isScrolling: state.isScrolling, + theme: state.theme, + transition: .animated(duration: 0.2, curve: .easeInOut) + ) + } + } + public func update( containerSize: CGSize, containerInsets: UIEdgeInsets, @@ -222,6 +293,17 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { theme: PresentationTheme, transition: ContainedViewLayoutTransition ) { + let updateLineImage = self.state?.isDragging != self.isDragging || self.state?.theme !== theme + + self.state = State( + containerSize: containerSize, + containerInsets: containerInsets, + scrollingState: scrollingState, + isScrolling: isScrolling, + isDragging: self.isDragging, + theme: theme + ) + self.containerSize = containerSize if self.theme !== theme { self.theme = theme @@ -239,13 +321,24 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { default: break }*/ + + } + + if updateLineImage { let lineColor: UIColor if theme.overallDarkAppearance { lineColor = UIColor(white: 0.0, alpha: 0.3) } else { lineColor = UIColor(white: 0.0, alpha: 0.3) } - self.lineIndicator.image = generateStretchableFilledCircleImage(diameter: 3.0, color: lineColor, strokeColor: nil, strokeWidth: nil, backgroundColor: nil) + if let image = generateStretchableFilledCircleImage(diameter: self.isDragging ? 6.0 : 3.0, color: lineColor, strokeColor: nil, strokeWidth: nil, backgroundColor: nil) { + if transition.isAnimated, let previousImage = self.lineIndicator.image { + self.lineIndicator.image = image + self.lineIndicator.layer.animate(from: previousImage.cgImage!, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) + } else { + self.lineIndicator.image = image + } + } } if self.dateIndicator.alpha.isZero { @@ -253,9 +346,7 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint()) } - if isScrolling { - self.updateActivityTimer(isScrolling: true) - } + self.updateActivityTimer(isScrolling: isScrolling) let indicatorSize = self.dateIndicator.update( transition: .immediate, @@ -272,7 +363,6 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { self.dateIndicator.isHidden = true - if let scrollingIndicatorState = scrollingState { let averageRangeItemHeight: CGFloat = 44.0 @@ -317,7 +407,7 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { if approximateContentHeight <= 0 { indicatorHeight = 0.0 } else { - indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (containerSize.height - scrollingIndicatorState.insets.top - scrollingIndicatorState.insets.bottom) / approximateContentHeight)) + indicatorHeight = max(minIndicatorContentHeight, 44.0)//max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (containerSize.height - scrollingIndicatorState.insets.top - scrollingIndicatorState.insets.bottom) / approximateContentHeight)) } let upperBound = containerInsets.top + indicatorTopInset @@ -325,8 +415,6 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { let indicatorOffset = ceilToScreenPixels(upperBound * (1.0 - approximateScrollingProgress) + lowerBound * approximateScrollingProgress) - //var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorSideInset : (self.visibleSize.width - 3.0 - indicatorSideInset), y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) - var indicatorFrame = CGRect(origin: CGPoint(x: containerSize.width - 3.0 - indicatorSideInset, y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) if indicatorFrame.minY < containerInsets.top + indicatorTopInset { @@ -344,39 +432,71 @@ public final class SparseDiscreteScrollingArea: ASDisplayNode { indicatorFrame.origin.y = indicatorTopInset } - if indicatorHeight >= visibleHeightWithoutIndicatorInsets { - self.lineIndicator.isHidden = true - self.lineIndicator.frame = indicatorFrame - } else { - if self.lineIndicator.isHidden { - self.lineIndicator.isHidden = false - self.lineIndicator.frame = indicatorFrame - } else { - self.lineIndicator.frame = indicatorFrame + indicatorFrame.origin.y = containerSize.height - indicatorFrame.origin.y - indicatorFrame.height + + if self.isDragging { + indicatorFrame.origin.x -= 3.0 + indicatorFrame.size.width += 3.0 + } + + var alternativeOffset: CGFloat? + if let navigatingToPositionOffset = self.navigatingToPositionOffset { + alternativeOffset = navigatingToPositionOffset + } else if let initialDraggingOffset = self.initialDraggingOffset, let draggingOffset = self.draggingOffset { + alternativeOffset = initialDraggingOffset + draggingOffset + } + + if let alternativeOffset = alternativeOffset { + indicatorFrame.origin.y = alternativeOffset + + if indicatorFrame.origin.y > containerSize.height - (containerInsets.top + indicatorBottomInset) - indicatorFrame.height { + indicatorFrame.origin.y = containerSize.height - (containerInsets.top + indicatorBottomInset) - indicatorFrame.height + } + if indicatorFrame.origin.y < containerInsets.bottom + indicatorTopInset { + indicatorFrame.origin.y = containerInsets.bottom + indicatorTopInset } } + + transition.updateFrame(view: self.lineIndicator, frame: indicatorFrame) + + if indicatorHeight >= visibleHeightWithoutIndicatorInsets { + self.lineIndicator.isHidden = true + } else { + self.lineIndicator.isHidden = false + } + + self.scrollIndicatorRange = ( + containerInsets.bottom + indicatorTopInset, + containerSize.height - (containerInsets.top + indicatorBottomInset) - self.lineIndicator.bounds.height + ) } else { self.lineIndicator.isHidden = true + self.scrollIndicatorRange = nil } } private func updateActivityTimer(isScrolling: Bool) { - self.activityTimer?.invalidate() + if self.isDragging || isScrolling { + self.activityTimer?.invalidate() + self.activityTimer = nil - if self.isDragging { let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) transition.updateAlpha(layer: self.dateIndicator.layer, alpha: 1.0) transition.updateAlpha(layer: self.lineIndicator.layer, alpha: 1.0) } else { - self.activityTimer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in - guard let strongSelf = self else { - return - } - let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) - transition.updateAlpha(layer: strongSelf.dateIndicator.layer, alpha: 0.0) - transition.updateAlpha(layer: strongSelf.lineIndicator.layer, alpha: 0.0) - }, queue: .mainQueue()) - self.activityTimer?.start() + if self.activityTimer == nil { + self.activityTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.activityTimer = nil + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut) + transition.updateAlpha(layer: strongSelf.dateIndicator.layer, alpha: 0.0) + transition.updateAlpha(layer: strongSelf.lineIndicator.layer, alpha: 0.0) + }, queue: .mainQueue()) + self.activityTimer?.start() + } } } diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 2db64d996f..f0c0fed726 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -327,6 +327,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.historyNode.rotated = true self.historyScrollingArea = SparseDiscreteScrollingArea() + self.historyNode.historyScrollingArea = self.historyScrollingArea self.historyNodeContainer = ASDisplayNode() self.historyNodeContainer.addSubnode(self.historyNode) @@ -529,23 +530,6 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.textInputPanelNode?.updateActivity = { [weak self] in self?.updateTypingActivity(true) } - - self.historyNode.updateScrollingIndicator = { [weak self] scrollingState, transition in - guard let strongSelf = self else { - return - } - guard let (_, _) = strongSelf.validLayout else { - return - } - strongSelf.historyScrollingArea.update( - containerSize: strongSelf.historyNode.bounds.size, - containerInsets: UIEdgeInsets(top: strongSelf.historyNode.scrollIndicatorInsets.bottom, left: 0.0, bottom: strongSelf.historyNode.scrollIndicatorInsets.top, right: 0.0), - scrollingState: scrollingState, - isScrolling: true, - theme: strongSelf.chatPresentationInterfaceState.theme, - transition: transition - ) - } } deinit { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift index 52a83f9064..87f7002b50 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntriesForView.swift @@ -76,7 +76,7 @@ func chatHistoryEntriesForView( } - var groupBucket: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = [] + var groupBucket: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)] = [] var count = 0 loop: for entry in view.entries { var message = entry.message @@ -154,7 +154,7 @@ func chatHistoryEntriesForView( } else { selection = .none } - groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false))) + groupBucket.append((message, isRead, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false), entry.location)) } else { let selection: ChatHistoryMessageSelection if let selectedMessages = selectedMessages { @@ -162,7 +162,7 @@ func chatHistoryEntriesForView( } else { selection = .none } - entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId))) + entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId))) } } else { let selection: ChatHistoryMessageSelection @@ -171,7 +171,7 @@ func chatHistoryEntriesForView( } else { selection = .none } - entries.append(.MessageEntry(message, presentationData, isRead, entry.monthLocation, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId))) + entries.append(.MessageEntry(message, presentationData, isRead, entry.location, selection, ChatMessageEntryAttributes(rank: adminRank, isContact: entry.attributes.authorIsContact, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: message.index == associatedData.currentlyPlayingMessageId))) } } @@ -225,9 +225,9 @@ func chatHistoryEntriesForView( addedThreadHead = true if messages.count > 1, let groupInfo = messages[0].groupInfo { - var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = [] + var groupMessages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)] = [] for message in messages { - groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false))) + groupMessages.append((message, false, .none, ChatMessageEntryAttributes(rank: adminRank, isContact: false, contentTypeHint: contentTypeHint, updatingMedia: updatingMedia[message.id], isPlaying: false), nil)) } entries.insert(.MessageGroupEntry(groupInfo, groupMessages, presentationData), at: 0) } else { diff --git a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift index eef307bdb0..c918c641c4 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryEntry.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryEntry.swift @@ -36,8 +36,8 @@ public struct ChatMessageEntryAttributes: Equatable { } enum ChatHistoryEntry: Identifiable, Comparable { - case MessageEntry(Message, ChatPresentationData, Bool, MessageHistoryEntryMonthLocation?, ChatHistoryMessageSelection, ChatMessageEntryAttributes) - case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)], ChatPresentationData) + case MessageEntry(Message, ChatPresentationData, Bool, MessageHistoryEntryLocation?, ChatHistoryMessageSelection, ChatMessageEntryAttributes) + case MessageGroupEntry(MessageGroupInfo, [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)], ChatPresentationData) case UnreadEntry(MessageIndex, ChatPresentationData) case ReplyCountEntry(MessageIndex, Bool, Int, ChatPresentationData) case ChatInfoEntry(String, String, ChatPresentationData) @@ -130,8 +130,8 @@ enum ChatHistoryEntry: Identifiable, Comparable { case let .MessageGroupEntry(lhsGroupInfo, lhsMessages, lhsPresentationData): if case let .MessageGroupEntry(rhsGroupInfo, rhsMessages, rhsPresentationData) = rhs, lhsGroupInfo == rhsGroupInfo, lhsPresentationData === rhsPresentationData, lhsMessages.count == rhsMessages.count { for i in 0 ..< lhsMessages.count { - let (lhsMessage, lhsRead, lhsSelection, lhsAttributes) = lhsMessages[i] - let (rhsMessage, rhsRead, rhsSelection, rhsAttributes) = rhsMessages[i] + let (lhsMessage, lhsRead, lhsSelection, lhsAttributes, lhsLocation) = lhsMessages[i] + let (rhsMessage, rhsRead, rhsSelection, rhsAttributes, rhsLocation) = rhsMessages[i] if lhsMessage.id != rhsMessage.id { return false @@ -177,6 +177,9 @@ enum ChatHistoryEntry: Identifiable, Comparable { if lhsAttributes != rhsAttributes { return false } + if lhsLocation != rhsLocation { + return false + } } return true diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index fbcfe05213..f3556e5fab 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -18,6 +18,7 @@ import AccountContext import ChatInterfaceState import ChatListUI import ComponentFlow +import SparseItemGrid extension ChatReplyThreadMessage { var effectiveTopId: MessageId { @@ -212,11 +213,11 @@ extension ListMessageItemInteraction { private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, entries: [ChatHistoryViewTransitionInsertEntry]) -> [ListViewInsertItem] { return entries.map { entry -> ListViewInsertItem in switch entry.entry { - case let .MessageEntry(message, presentationData, read, _, selection, attributes): + case let .MessageEntry(message, presentationData, read, location, selection, attributes): let item: ListViewItem switch mode { case .bubbles: - item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) + item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location)) case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { @@ -257,11 +258,11 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLocation, associatedData: ChatMessageItemAssociatedData, controllerInteraction: ChatControllerInteraction, mode: ChatHistoryListMode, lastHeaderId: Int64, entries: [ChatHistoryViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { return entries.map { entry -> ListViewUpdateItem in switch entry.entry { - case let .MessageEntry(message, presentationData, read, _, selection, attributes): + case let .MessageEntry(message, presentationData, read, location, selection, attributes): let item: ListViewItem switch mode { case .bubbles: - item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) + item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location)) case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { @@ -563,7 +564,22 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { private var preloadAdPeerId: PeerId? private let preloadAdPeerDisposable = MetaDisposable() + var historyScrollingArea: SparseDiscreteScrollingArea? { + didSet { + oldValue?.navigateToPosition = nil + if let historyScrollingArea = self.historyScrollingArea { + historyScrollingArea.navigateToPosition = { [weak self] position in + guard let strongSelf = self else { + return + } + strongSelf.navigateToAbsolutePosition(position: position) + } + } + } + } + private var scrollingState: ListView.ScrollingIndicatorState? private let sparseScrollingContext: SparseMessageScrollingContext? + private let scrollNavigationDisposable = MetaDisposable() private let clientId: Atomic @@ -774,7 +790,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { historyViewUpdate = self.chatHistoryLocationPromise.get() |> distinctUntilChanged |> mapToSignal { location in - return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: isScheduledMessages, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, additionalData: additionalData) + return chatHistoryViewForLocation(location, context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, scheduled: isScheduledMessages, fixedCombinedReadStates: fixedCombinedReadStates.with { $0 }, tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, additionalData: additionalData, orderStatistics: [.combinedLocation]) |> beforeNext { viewUpdate in switch viewUpdate { case let .HistoryView(view, _, _, _, _, _, _): @@ -974,7 +990,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { break loop } case let .MessageGroupEntry(_, messages, _): - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { if message.adAttribute == nil { anchorIndex = message.index break loop @@ -1383,6 +1399,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self?.isInteractivelyScrollingValue = true self?.isInteractivelyScrollingPromise.set(true) self?.beganDragging?() + self?.updateHistoryScrollingArea(transition: .immediate) } self.endedInteractiveDragging = { [weak self] _ in @@ -1424,6 +1441,15 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } strongSelf.isInteractivelyScrollingValue = false strongSelf.isInteractivelyScrollingPromise.set(false) + strongSelf.updateHistoryScrollingArea(transition: .immediate) + } + + self.updateScrollingIndicator = { [weak self] scrollingState, transition in + guard let strongSelf = self else { + return + } + strongSelf.scrollingState = scrollingState + strongSelf.updateHistoryScrollingArea(transition: transition) } let selectionRecognizer = ChatHistoryListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:))) @@ -1443,12 +1469,136 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.canReadHistoryDisposable?.dispose() self.loadedMessagesFromCachedDataDisposable?.dispose() self.preloadAdPeerDisposable.dispose() + self.scrollNavigationDisposable.dispose() } public func setLoadStateUpdated(_ f: @escaping (ChatHistoryNodeLoadState, Bool) -> Void) { self.loadStateUpdated = f } + private func updateHistoryScrollingArea(transition: ContainedViewLayoutTransition) { + guard let historyScrollingArea = self.historyScrollingArea else { + return + } + guard let transactionState = self.opaqueTransactionState as? ChatHistoryTransactionOpaqueState else { + return + } + + let historyView = transactionState.historyView + + var updatedScrollingState = self.scrollingState + if var scrollingState = updatedScrollingState { + let convertedIndex = historyView.filteredEntries.count - scrollingState.topItem.index - 1 + if convertedIndex < 0 || convertedIndex >= historyView.filteredEntries.count { + return + } + let firstItem = historyView.filteredEntries[convertedIndex] + var location: MessageHistoryEntryLocation? + switch firstItem { + case let .MessageEntry(_, _, _, locationValue, _, _): + location = locationValue + case let .MessageGroupEntry(_, group, _): + if let locationValue = group.last?.4 { + location = locationValue + } + default: + break + } + + if let location = location { + let locationDelta = (location.count - location.index - 1) - scrollingState.topItem.index + scrollingState.topItem.index += locationDelta + scrollingState.bottomItem.index += locationDelta + scrollingState.itemCount = max(scrollingState.itemCount, location.count) + } + + updatedScrollingState = scrollingState + } + + historyScrollingArea.update( + containerSize: self.bounds.size, + containerInsets: UIEdgeInsets(top: self.scrollIndicatorInsets.top, left: 0.0, bottom: self.scrollIndicatorInsets.bottom, right: 0.0), + scrollingState: updatedScrollingState, + isScrolling: self.isDragging || self.isDeceleratingAfterTracking, + theme: self.currentPresentationData.theme.theme, + transition: transition + ) + } + + private func navigateToAbsolutePosition(position: Float) { + guard let transactionState = self.opaqueTransactionState as? ChatHistoryTransactionOpaqueState else { + return + } + + let historyView = transactionState.historyView + + let convertedIndex = 0 + if convertedIndex < 0 || convertedIndex >= historyView.filteredEntries.count { + self.historyScrollingArea?.resetNavigatingToPosition() + return + } + let firstItem = historyView.filteredEntries[convertedIndex] + var location: MessageHistoryEntryLocation? + switch firstItem { + case let .MessageEntry(_, _, _, locationValue, _, _): + location = locationValue + case let .MessageGroupEntry(_, group, _): + if let locationValue = group.last?.4 { + location = locationValue + } + default: + break + } + + if let location = location { + var absoluteIndex = Int(Float(location.count) * position) + if absoluteIndex >= location.count { + absoluteIndex = location.count - 1 + } + if absoluteIndex < 0 { + absoluteIndex = 0 + } + if case let .peer(peerId) = self.chatLocation { + let _ = (self.context.account.postbox.transaction { transaction -> MessageIndex? in + return transaction.findMessageAtAbsoluteIndex(peerId: peerId, namespace: Namespaces.Message.Cloud, index: absoluteIndex) + } + |> deliverOnMainQueue).start(next: { [weak self] index in + guard let strongSelf = self else { + return + } + if let index = index { + let content: ChatHistoryLocation = .Scroll(index: .message(index), anchorIndex: .message(index), sourceIndex: .message(index), scrollPosition: .top(0.0), animated: false, highlight: false) + + strongSelf.scrollNavigationDisposable.set((preloadedChatHistoryViewForLocation(ChatHistoryLocationInput(content: content, id: 0), context: strongSelf.context, chatLocation: strongSelf.chatLocation, subject: strongSelf.subject, chatLocationContextHolder: strongSelf.chatLocationContextHolder, fixedCombinedReadStates: nil, tagMask: nil, additionalData: []) + |> map { historyView -> Bool in + switch historyView { + case .Loading: + return false + case .HistoryView: + return true + } + } + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + guard let strongSelf = self else { + return + } + strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: content, id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0) + Queue.mainQueue().after(0.5, { + self?.historyScrollingArea?.resetNavigatingToPosition() + }) + })) + } else { + strongSelf.historyScrollingArea?.resetNavigatingToPosition() + } + }) + } + } else { + self.historyScrollingArea?.resetNavigatingToPosition() + } + } + private func maybeUpdateOverscrollAction(offset: CGFloat?) { if self.freezeOverscrollControl { return @@ -1663,7 +1813,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { topVisibleMessageRange = ChatTopVisibleMessageRange(lowerBound: message.id, upperBound: message.id, isLast: i == historyView.filteredEntries.count - 1) } case let .MessageGroupEntry(_, messages, _): - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { var hasUnconsumedMention = false var hasUnconsumedContent = false if message.tags.contains(.unseenPersonalMessage) { @@ -1726,7 +1876,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } case let .MessageGroupEntry(_, messages, _): - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { var stop = false for media in message.media { if !addMediaToPrefetch(message, media, &toEarlierMediaMessages) { @@ -1754,7 +1904,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } case let .MessageGroupEntry(_, messages, _): - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { for media in message.media { if !addMediaToPrefetch(message, media, &toLaterMediaMessages) { break outer @@ -1979,7 +2129,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { return message } } else if case let .MessageGroupEntry(_, messages, _) = entry { - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { if message.id == id { return message } @@ -1998,7 +2148,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { return [message] } } else if case let .MessageGroupEntry(_, messages, _) = entry { - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { if message.id == id { return messages.map { $0.0 } } @@ -2017,7 +2167,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { return } } else if case let .MessageGroupEntry(_, messages, _) = entry { - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { if !f(message) { return } @@ -2449,13 +2599,13 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { loop: for i in 0 ..< historyView.filteredEntries.count { switch historyView.filteredEntries[i] { - case let .MessageEntry(message, presentationData, read, _, selection, attributes): + case let .MessageEntry(message, presentationData, read, location, selection, attributes): if message.id == id { let index = historyView.filteredEntries.count - 1 - i let item: ListViewItem switch self.mode { case .bubbles: - item = ChatMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) + item = ChatMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location)) case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { @@ -2499,13 +2649,13 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { loop: for i in 0 ..< historyView.filteredEntries.count { switch historyView.filteredEntries[i] { - case let .MessageEntry(message, presentationData, read, _, selection, attributes): + case let .MessageEntry(message, presentationData, read, location, selection, attributes): if message.stableId == stableId { let index = historyView.filteredEntries.count - 1 - i let item: ListViewItem switch self.mode { case .bubbles: - item = ChatMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes)) + item = ChatMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes, location: location)) case let .list(_, _, displayHeaders, hintLinks, isGlobalSearch): let displayHeader: Bool switch displayHeaders { @@ -2536,7 +2686,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if resultMessages == nil, let itemNode = itemNode as? ListViewItemNode, itemNode.frame.contains(point) { if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item { switch item.content { - case let .message(message, _, _ , _): + case let .message(message, _, _ , _, _): resultMessages = [message] case let .group(messages): resultMessages = messages.map { $0.0 } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 0366b07a14..3778f0d084 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -1166,7 +1166,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode var authorRank: CachedChannelAdminRank? var authorIsChannel: Bool = false switch content { - case let .message(message, _, _, attributes): + case let .message(message, _, _, attributes, _): if let peer = message.peers[message.id.peerId] as? TelegramChannel { if case .broadcast = peer.info { } else { @@ -1241,7 +1241,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode var isItemEdited = false switch item.content { - case let .message(message, value, _, _): + case let .message(message, value, _, _, _): read = value isItemPinned = message.tags.contains(.pinned) case let .group(messages): @@ -1322,7 +1322,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode case .message: break case let .group(messages): - for (m, _, selection, _) in messages { + for (m, _, selection, _, _) in messages { if m.id == message.id { switch selection { case .none: @@ -3312,7 +3312,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode var canHaveSelection = true switch item.content { - case let .message(message, _, _, _): + case let .message(message, _, _, _, _): for media in message.media { if let action = media as? TelegramMediaAction { if case .phoneCall = action.action { } else { @@ -3336,11 +3336,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) switch item.content { - case let .message(message, _, _, _): + case let .message(message, _, _, _, _): selected = selectionState.selectedIds.contains(message.id) case let .group(messages: messages): var allSelected = !messages.isEmpty - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { if !selectionState.selectedIds.contains(message.id) { allSelected = false break @@ -3361,7 +3361,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode let selectionNode = ChatMessageSelectionNode(wallpaper: item.presentationData.theme.wallpaper, theme: item.presentationData.theme.theme, toggle: { [weak self] value in if let strongSelf = self, let item = strongSelf.item { switch item.content { - case let .message(message, _, _, _): + case let .message(message, _, _, _, _): item.controllerInteraction.toggleMessagesSelection([message.id], value) case let .group(messages): item.controllerInteraction.toggleMessagesSelection(messages.map { $0.0.id }, value) diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index 8aa39fd243..a3ee2422ae 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -12,15 +12,15 @@ import Emoji import PersistentStringHash public enum ChatMessageItemContent: Sequence { - case message(message: Message, read: Bool, selection: ChatHistoryMessageSelection, attributes: ChatMessageEntryAttributes) - case group(messages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)]) + case message(message: Message, read: Bool, selection: ChatHistoryMessageSelection, attributes: ChatMessageEntryAttributes, location: MessageHistoryEntryLocation?) + case group(messages: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes, MessageHistoryEntryLocation?)]) func effectivelyIncoming(_ accountPeerId: PeerId, associatedData: ChatMessageItemAssociatedData? = nil) -> Bool { if let subject = associatedData?.subject, case .forwardedMessages = subject { return false } switch self { - case let .message(message, _, _, _): + case let .message(message, _, _, _, _): return message.effectivelyIncoming(accountPeerId) case let .group(messages): return messages[0].0.effectivelyIncoming(accountPeerId) @@ -29,7 +29,7 @@ public enum ChatMessageItemContent: Sequence { var index: MessageIndex { switch self { - case let .message(message, _, _, _): + case let .message(message, _, _, _, _): return message.index case let .group(messages): return messages[0].0.index @@ -38,7 +38,7 @@ public enum ChatMessageItemContent: Sequence { var firstMessage: Message { switch self { - case let .message(message, _, _, _): + case let .message(message, _, _, _, _): return message case let .group(messages): return messages[0].0 @@ -47,7 +47,7 @@ public enum ChatMessageItemContent: Sequence { var firstMessageAttributes: ChatMessageEntryAttributes { switch self { - case let .message(_, _, _, attributes): + case let .message(_, _, _, attributes, _): return attributes case let .group(messages): return messages[0].3 @@ -58,7 +58,7 @@ public enum ChatMessageItemContent: Sequence { var index = 0 return AnyIterator { () -> (Message, ChatMessageEntryAttributes)? in switch self { - case let .message(message, _, _, attributes): + case let .message(message, _, _, attributes, _): if index == 0 { index += 1 return (message, attributes) @@ -265,7 +265,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { var message: Message { switch self.content { - case let .message(message, _, _, _): + case let .message(message, _, _, _, _): return message case let .group(messages): return messages[0].0 @@ -274,7 +274,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { var read: Bool { switch self.content { - case let .message(_, read, _, _): + case let .message(_, read, _, _, _): return read case let .group(messages): return messages[0].1 @@ -433,7 +433,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { } if viewClassName == ChatMessageBubbleItemNode.self && self.presentationData.largeEmoji && self.message.media.isEmpty { - if case let .message(_, _, _, attributes) = self.content { + if case let .message(_, _, _, attributes, _) = self.content { switch attributes.contentTypeHint { case .largeEmoji: viewClassName = ChatMessageStickerItemNode.self diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index 849968962c..9fa9fd2672 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -774,12 +774,12 @@ public class ChatMessageItemView: ListViewItemNode { var isHighlightedInOverlay = false if let item = self.item, let contextHighlightedState = item.controllerInteraction.contextHighlightedState { switch item.content { - case let .message(message, _, _, _): + case let .message(message, _, _, _, _): if contextHighlightedState.messageStableId == message.stableId { isHighlightedInOverlay = true } case let .group(messages): - for (message, _, _, _) in messages { + for (message, _, _, _, _) in messages { if contextHighlightedState.messageStableId == message.stableId { isHighlightedInOverlay = true break diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index 09b5ee1dd0..8884a2cc3e 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -114,7 +114,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.titleUpdated(title: new) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeAbout(prev, new): var peers = SimpleDictionary() var author: Peer? @@ -145,14 +145,14 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: let peers = SimpleDictionary() let attributes: [MessageAttribute] = [] let prevMessage = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prev, attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: new, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousDescription(prevMessage) : nil) } case let .changeUsername(prev, new): var peers = SimpleDictionary() @@ -183,7 +183,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action: TelegramMediaActionType = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var previousAttributes: [MessageAttribute] = [] var attributes: [MessageAttribute] = [] @@ -202,7 +202,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let prevMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: prevText, attributes: previousAttributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.isEmpty ? .eventLogPreviousLink(prevMessage) : nil) } case let .changePhoto(_, new): var peers = SimpleDictionary() @@ -221,7 +221,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.photoUpdated(image: photo) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleInvites(value): var peers = SimpleDictionary() var author: Peer? @@ -248,7 +248,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleSignatures(value): var peers = SimpleDictionary() var author: Peer? @@ -275,7 +275,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updatePinned(message): switch self.id.contentIndex { case .header: @@ -299,7 +299,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: if let message = message { var peers = SimpleDictionary() @@ -317,7 +317,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } else { var peers = SimpleDictionary() var author: Peer? @@ -339,7 +339,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 0), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } } case let .editMessage(prev, message): @@ -384,7 +384,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -401,7 +401,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: !prev.text.isEmpty || !message.text.isEmpty ? .eventLogPreviousMessage(filterOriginalMessageFlags(prev)) : nil) } case let .deleteMessage(message): switch self.id.contentIndex { @@ -427,7 +427,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -451,7 +451,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } case .participantJoin, .participantLeave: var peers = SimpleDictionary() @@ -469,7 +469,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { action = TelegramMediaActionType.removedMembers(peerIds: [self.entry.event.peerId]) } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantInvite(participant): var peers = SimpleDictionary() var author: Peer? @@ -486,7 +486,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantToggleBan(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -616,7 +616,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantToggleAdmin(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -832,7 +832,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeStickerPack(_, new): var peers = SimpleDictionary() var author: Peer? @@ -861,7 +861,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .togglePreHistoryHidden(value): var peers = SimpleDictionary() var author: Peer? @@ -891,7 +891,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updateDefaultBannedRights(prev, new): var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -949,7 +949,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: attributes, media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .pollStopped(message): switch self.id.contentIndex { case .header: @@ -977,7 +977,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: 1), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .content: var peers = SimpleDictionary() var attributes: [MessageAttribute] = [] @@ -994,7 +994,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } } let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.author, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes()), additionalContent: nil) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: filterOriginalMessageFlags(message), read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: nil) } case let .linkedPeerUpdated(previous, updated): var peers = SimpleDictionary() @@ -1050,7 +1050,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeGeoLocation(_, updated): var peers = SimpleDictionary() var author: Peer? @@ -1072,12 +1072,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let mediaMap = TelegramMediaMap(latitude: updated.latitude, longitude: updated.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: text, attributes: [], media: [mediaMap], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } else { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } case let .updateSlowmode(_, newValue): var peers = SimpleDictionary() @@ -1108,7 +1108,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case .startGroupCall, .endGroupCall: var peers = SimpleDictionary() var author: Peer? @@ -1145,7 +1145,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .groupCallUpdateParticipantMuteStatus(participantId, isMuted): var peers = SimpleDictionary() var author: Peer? @@ -1179,7 +1179,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .updateGroupCallSettings(joinMuted): var peers = SimpleDictionary() var author: Peer? @@ -1208,7 +1208,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .groupCallUpdateParticipantVolume(participantId, volume): var peers = SimpleDictionary() var author: Peer? @@ -1239,7 +1239,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .deleteExportedInvitation(invite): var peers = SimpleDictionary() var author: Peer? @@ -1265,7 +1265,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .revokeExportedInvitation(invite): var peers = SimpleDictionary() var author: Peer? @@ -1291,7 +1291,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .editExportedInvitation(_, updatedInvite): var peers = SimpleDictionary() var author: Peer? @@ -1317,7 +1317,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantJoinedViaInvite(invite): var peers = SimpleDictionary() var author: Peer? @@ -1343,7 +1343,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeHistoryTTL(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1374,7 +1374,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .changeTheme(_, updatedValue): var peers = SimpleDictionary() var author: Peer? @@ -1405,7 +1405,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .participantJoinByRequest(invite, approvedBy): var peers = SimpleDictionary() var author: Peer? @@ -1438,7 +1438,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) case let .toggleCopyProtection(value): var peers = SimpleDictionary() var author: Peer? @@ -1465,7 +1465,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { } let action = TelegramMediaActionType.customText(text: text, entities: entities) let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: author, text: "", attributes: [], media: [TelegramMediaAction(action: action)], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes())) + return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil)) } } } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index d45e8d58d0..d648127285 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1298,10 +1298,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { let content: ChatMessageItemContent let chatLocation: ChatLocation if messages.count > 1 { - content = .group(messages: messages.map { ($0, true, .none, ChatMessageEntryAttributes()) }) + content = .group(messages: messages.map { ($0, true, .none, ChatMessageEntryAttributes(), nil) }) chatLocation = .peer(messages.first!.id.peerId) } else { - content = .message(message: messages.first!, read: true, selection: .none, attributes: ChatMessageEntryAttributes()) + content = .message(message: messages.first!, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil) chatLocation = .peer(messages.first!.id.peerId) }