diff --git a/submodules/GalleryUI/Sources/GalleryTitleView.swift b/submodules/GalleryUI/Sources/GalleryTitleView.swift index a85a906487..661b1ffbc2 100644 --- a/submodules/GalleryUI/Sources/GalleryTitleView.swift +++ b/submodules/GalleryUI/Sources/GalleryTitleView.swift @@ -45,8 +45,8 @@ final class GalleryTitleView: UIView, NavigationBarTitleView { let leftInset: CGFloat = 0.0 let rightInset: CGFloat = 0.0 - let authorNameSize = self.authorNameNode.measure(CGSize(width: size.width - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude)) - let dateSize = self.dateNode.measure(CGSize(width: size.width - 8.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)) + let authorNameSize = self.authorNameNode.measure(CGSize(width: max(1.0, size.width - 8.0 * 2.0 - leftInset - rightInset), height: CGFloat.greatestFiniteMagnitude)) + let dateSize = self.dateNode.measure(CGSize(width: max(1.0, size.width - 8.0 * 2.0), height: CGFloat.greatestFiniteMagnitude)) if authorNameSize.height.isZero { self.dateNode.frame = CGRect(origin: CGPoint(x: floor((size.width - dateSize.width) / 2.0), y: floor((size.height - dateSize.height) / 2.0)), size: dateSize) diff --git a/submodules/SparseItemGrid/BUILD b/submodules/SparseItemGrid/BUILD new file mode 100644 index 0000000000..1075f093b6 --- /dev/null +++ b/submodules/SparseItemGrid/BUILD @@ -0,0 +1,21 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SparseItemGrid", + module_name = "SparseItemGrid", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display:Display", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/ComponentFlow:ComponentFlow", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift new file mode 100644 index 0000000000..a1f7869636 --- /dev/null +++ b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift @@ -0,0 +1,8 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit + +public final class SparseItemGrid: ASDisplayNode { + +} \ No newline at end of file diff --git a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift new file mode 100644 index 0000000000..34106ddb17 --- /dev/null +++ b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift @@ -0,0 +1,530 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import SwiftSignalKit + +private final class RoundedRectangle: Component { + let color: UIColor + + init(color: UIColor) { + self.color = color + } + + static func ==(lhs: RoundedRectangle, rhs: RoundedRectangle) -> Bool { + if !lhs.color.isEqual(rhs.color) { + return false + } + return true + } + + final class View: UIView { + private let backgroundView: UIImageView + + private var currentColor: UIColor? + private var currentDiameter: CGFloat? + + init() { + self.backgroundView = UIImageView() + + super.init(frame: CGRect()) + + self.addSubview(self.backgroundView) + } + + required init?(coder aDecoder: NSCoder) { + preconditionFailure() + } + + func update(component: RoundedRectangle, availableSize: CGSize, transition: Transition) -> CGSize { + let shadowInset: CGFloat = 0.0 + let diameter = min(availableSize.width, availableSize.height) + + var updated = false + if let currentColor = self.currentColor { + if !component.color.isEqual(currentColor) { + updated = true + } + } else { + updated = true + } + + if self.currentDiameter != diameter || updated { + self.currentDiameter = diameter + self.currentColor = component.color + + self.backgroundView.image = generateImage(CGSize(width: diameter + shadowInset * 2.0, height: diameter + shadowInset * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(component.color.cgColor) + + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: CGSize(width: size.width - shadowInset * 2.0, height: size.height - shadowInset * 2.0))) + })?.stretchableImage(withLeftCapWidth: Int(diameter + shadowInset * 2.0) / 2, topCapHeight: Int(diameter + shadowInset * 2.0) / 2) + } + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: -shadowInset, y: -shadowInset), size: CGSize(width: availableSize.width + shadowInset * 2.0, height: availableSize.height + shadowInset * 2.0))) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + +private final class ShadowRoundedRectangle: Component { + let color: UIColor + + init(color: UIColor) { + self.color = color + } + + static func ==(lhs: ShadowRoundedRectangle, rhs: ShadowRoundedRectangle) -> Bool { + if !lhs.color.isEqual(rhs.color) { + return false + } + return true + } + + final class View: UIView { + private let backgroundView: UIImageView + + private var currentColor: UIColor? + private var currentDiameter: CGFloat? + + init() { + self.backgroundView = UIImageView() + + super.init(frame: CGRect()) + + self.addSubview(self.backgroundView) + } + + required init?(coder aDecoder: NSCoder) { + preconditionFailure() + } + + func update(component: ShadowRoundedRectangle, availableSize: CGSize, transition: Transition) -> CGSize { + let shadowInset: CGFloat = 10.0 + let diameter = min(availableSize.width, availableSize.height) + + var updated = false + if let currentColor = self.currentColor { + if !component.color.isEqual(currentColor) { + updated = true + } + } else { + updated = true + } + + if self.currentDiameter != diameter || updated { + self.currentDiameter = diameter + self.currentColor = component.color + + self.backgroundView.image = generateImage(CGSize(width: diameter + shadowInset * 2.0, height: diameter + shadowInset * 2.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(component.color.cgColor) + context.setShadow(offset: CGSize(width: 0.0, height: -2.0), blur: 5.0, color: UIColor(white: 0.0, alpha: 0.3).cgColor) + + context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: CGSize(width: size.width - shadowInset * 2.0, height: size.height - shadowInset * 2.0))) + })?.stretchableImage(withLeftCapWidth: Int(diameter + shadowInset * 2.0) / 2, topCapHeight: Int(diameter + shadowInset * 2.0) / 2) + } + + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: -shadowInset, y: -shadowInset), size: CGSize(width: availableSize.width + shadowInset * 2.0, height: availableSize.height + shadowInset * 2.0))) + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + func update(view: View, availableSize: CGSize, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} + +private final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { + let backgroundColor: UIColor + let shadowColor: UIColor + let foregroundColor: UIColor + let dateString: String + + init( + backgroundColor: UIColor, + shadowColor: UIColor, + foregroundColor: UIColor, + dateString: String + ) { + self.backgroundColor = backgroundColor + self.shadowColor = shadowColor + self.foregroundColor = foregroundColor + self.dateString = dateString + } + + static func ==(lhs: SparseItemGridScrollingIndicatorComponent, rhs: SparseItemGridScrollingIndicatorComponent) -> Bool { + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.shadowColor != rhs.shadowColor { + return false + } + if lhs.foregroundColor != rhs.foregroundColor { + return false + } + if lhs.dateString != rhs.dateString { + return false + } + return true + } + + static var body: Body { + let rect = Child(ShadowRoundedRectangle.self) + let text = Child(Text.self) + + return { context in + let text = text.update( + component: Text( + text: context.component.dateString, + font: Font.medium(13.0), + color: .black + ), + availableSize: CGSize(width: 200.0, height: 100.0), + transition: .immediate + ) + + let rect = rect.update( + component: ShadowRoundedRectangle( + color: .white + ), + availableSize: CGSize(width: text.size.width + 26.0, height: 32.0), + transition: .immediate + ) + + let rectFrame = rect.size.centered(around: CGPoint( + x: rect.size.width / 2.0, + y: rect.size.height / 2.0 + )) + + context.add(rect + .position(CGPoint(x: rectFrame.midX, y: rectFrame.midY)) + ) + + let textFrame = text.size.centered(in: rectFrame) + context.add(text + .position(CGPoint(x: textFrame.midX, y: textFrame.midY)) + ) + + return rect.size + } + } +} + +public final class SparseItemGridScrollingArea: 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: ComponentHostView + private var dragGesture: DragGesture? + public private(set) var isDragging: Bool = false + + private weak var draggingScrollView: UIScrollView? + private var scrollingInitialOffset: CGFloat? + + private var activityTimer: SwiftSignalKit.Timer? + + public var beginScrolling: (() -> UIScrollView?)? + + private struct ProjectionData { + var minY: CGFloat + var maxY: CGFloat + var indicatorHeight: CGFloat + var scrollableHeight: CGFloat + } + private var projectionData: ProjectionData? + + override public init() { + self.dateIndicator = ComponentHostView() + self.lineIndicator = ComponentHostView() + + self.dateIndicator.alpha = 0.0 + self.lineIndicator.alpha = 0.0 + + super.init() + + self.view.addSubview(self.dateIndicator) + self.view.addSubview(self.lineIndicator) + + let dragGesture = DragGesture( + shouldBegin: { [weak self] point in + guard let strongSelf = self else { + return false + } + if !strongSelf.dateIndicator.frame.contains(point) { + return false + } + + return true + }, + began: { [weak self] in + guard let strongSelf = self else { + return + } + let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut) + transition.updateSublayerTransformOffset(layer: strongSelf.dateIndicator.layer, offset: CGPoint(x: -80.0, y: 0.0)) + + strongSelf.isDragging = true + + if let scrollView = strongSelf.beginScrolling?() { + strongSelf.draggingScrollView = scrollView + strongSelf.scrollingInitialOffset = scrollView.contentOffset.y + scrollView.setContentOffset(scrollView.contentOffset, animated: false) + } + + strongSelf.updateActivityTimer() + }, + ended: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.draggingScrollView = nil + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring) + transition.updateSublayerTransformOffset(layer: strongSelf.dateIndicator.layer, offset: CGPoint(x: 0.0, y: 0.0)) + + strongSelf.isDragging = false + strongSelf.updateActivityTimer() + }, + moved: { [weak self] relativeOffset in + guard let strongSelf = self else { + return + } + guard let scrollView = strongSelf.draggingScrollView, let scrollingInitialOffset = strongSelf.scrollingInitialOffset else { + return + } + guard let projectionData = strongSelf.projectionData else { + return + } + + let indicatorArea = projectionData.maxY - projectionData.minY + let scrollFraction = projectionData.scrollableHeight / indicatorArea + + var offset = scrollingInitialOffset + scrollFraction * relativeOffset + if offset < 0.0 { + offset = 0.0 + } + if offset > scrollView.contentSize.height - scrollView.bounds.height { + offset = scrollView.contentSize.height - scrollView.bounds.height + } + + scrollView.setContentOffset(CGPoint(x: 0.0, y: offset), animated: false) + let _ = scrollView + let _ = projectionData + } + ) + self.dragGesture = dragGesture + + self.view.addGestureRecognizer(dragGesture) + } + + public func update( + containerSize: CGSize, + containerInsets: UIEdgeInsets, + contentHeight: CGFloat, + contentOffset: CGFloat, + isScrolling: Bool, + dateString: String, + transition: ContainedViewLayoutTransition + ) { + if isScrolling { + self.updateActivityTimer() + } + + let indicatorSize = self.dateIndicator.update( + transition: .immediate, + component: AnyComponent(SparseItemGridScrollingIndicatorComponent( + backgroundColor: .white, + shadowColor: .black, + foregroundColor: .black, + dateString: dateString + )), + environment: {}, + containerSize: containerSize + ) + + let scrollIndicatorHeightFraction = min(1.0, max(0.0, (containerSize.height - containerInsets.top - containerInsets.bottom) / contentHeight)) + if scrollIndicatorHeightFraction >= 1.0 - .ulpOfOne { + self.dateIndicator.isHidden = true + self.lineIndicator.isHidden = true + } else { + self.dateIndicator.isHidden = false + self.lineIndicator.isHidden = false + } + + let indicatorVerticalInset: CGFloat = 3.0 + let topIndicatorInset: CGFloat = indicatorVerticalInset + let bottomIndicatorInset: CGFloat = indicatorVerticalInset + containerInsets.bottom + + let scrollIndicatorHeight = max(35.0, ceil(scrollIndicatorHeightFraction * containerSize.height)) + + let indicatorPositionFraction = min(1.0, max(0.0, contentOffset / (contentHeight - containerSize.height))) + + let indicatorTopPosition = topIndicatorInset + let indicatorBottomPosition = containerSize.height - bottomIndicatorInset - scrollIndicatorHeight + + let dateIndicatorTopPosition = topIndicatorInset + let dateIndicatorBottomPosition = containerSize.height - bottomIndicatorInset - indicatorSize.height + + let indicatorPosition = indicatorTopPosition * (1.0 - indicatorPositionFraction) + indicatorBottomPosition * indicatorPositionFraction + + let dateIndicatorPosition = dateIndicatorTopPosition * (1.0 - indicatorPositionFraction) + dateIndicatorBottomPosition * indicatorPositionFraction + + self.projectionData = ProjectionData( + minY: dateIndicatorTopPosition, + maxY: dateIndicatorBottomPosition, + indicatorHeight: indicatorSize.height, + scrollableHeight: contentHeight - containerSize.height + ) + + transition.updateFrame(view: self.dateIndicator, frame: CGRect(origin: CGPoint(x: containerSize.width - 12.0 - indicatorSize.width, y: dateIndicatorPosition), size: indicatorSize)) + if isScrolling { + self.dateIndicator.alpha = 1.0 + self.lineIndicator.alpha = 1.0 + } + + let lineIndicatorSize = CGSize(width: 3.0, height: scrollIndicatorHeight) + let _ = self.lineIndicator.update( + transition: .immediate, + component: AnyComponent(RoundedRectangle( + color: UIColor(white: 0.0, alpha: 0.3) + )), + environment: {}, + containerSize: lineIndicatorSize + ) + + transition.updateFrame(view: self.lineIndicator, frame: CGRect(origin: CGPoint(x: containerSize.width - 3.0 - lineIndicatorSize.width, y: indicatorPosition), size: lineIndicatorSize)) + } + + private func updateActivityTimer() { + 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: 1.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.dateIndicator.frame.contains(point) { + return self.view + } + + return nil + } +} diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 1236e6f6ba..a2fdccf113 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -11,8 +11,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) } dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } - dict[1304281241] = { return Api.ChatFull.parse_chatFull($0) } - dict[-374179305] = { return Api.ChatFull.parse_channelFull($0) } + dict[-2097579871] = { return Api.ChatFull.parse_chatFull($0) } + dict[1084166537] = { return Api.ChatFull.parse_channelFull($0) } dict[-591909213] = { return Api.PollResults.parse_pollResults($0) } dict[-1070776313] = { return Api.ChatParticipant.parse_chatParticipant($0) } dict[-462696732] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) } @@ -72,7 +72,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[399807445] = { return Api.Chat.parse_channelForbidden($0) } dict[1202287072] = { return Api.StatsURL.parse_statsURL($0) } dict[1516793212] = { return Api.ChatInvite.parse_chatInviteAlready($0) } - dict[-540871282] = { return Api.ChatInvite.parse_chatInvite($0) } + dict[806110401] = { return Api.ChatInvite.parse_chatInvite($0) } dict[1634294960] = { return Api.ChatInvite.parse_chatInvitePeek($0) } dict[813821341] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypeSameBotPM($0) } dict[-2093215828] = { return Api.InlineQueryPeerType.parse_inlineQueryPeerTypePM($0) } @@ -163,6 +163,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-496024847] = { return Api.UserStatus.parse_userStatusRecently($0) } dict[129960444] = { return Api.UserStatus.parse_userStatusLastWeek($0) } dict[2011940674] = { return Api.UserStatus.parse_userStatusLastMonth($0) } + dict[-911191137] = { return Api.SearchResultsCalendarPeriod.parse_searchResultsCalendarPeriod($0) } dict[-11252123] = { return Api.Folder.parse_folder($0) } dict[739712882] = { return Api.Dialog.parse_dialog($0) } dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) } @@ -334,6 +335,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-842824308] = { return Api.account.WallPapers.parse_wallPapers($0) } dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) } dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) } + dict[2014782332] = { return Api.messages.SearchResultsRawMessages.parse_searchResultsRawMessages($0) } dict[-2032041631] = { return Api.Poll.parse_poll($0) } dict[-1195615476] = { return Api.InputNotifyPeer.parse_inputNotifyPeer($0) } dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) } @@ -467,7 +469,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-384910503] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionExportedInviteEdit($0) } dict[1048537159] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionParticipantVolume($0) } dict[1855199800] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeHistoryTTL($0) } - dict[-26672755] = { return Api.ChannelAdminLogEventAction.parse_channelAdminLogEventActionChangeTheme($0) } dict[-1271602504] = { return Api.auth.ExportedAuthorization.parse_exportedAuthorization($0) } dict[2103482845] = { return Api.SecurePlainData.parse_securePlainPhone($0) } dict[569137759] = { return Api.SecurePlainData.parse_securePlainEmail($0) } @@ -566,6 +567,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-478701471] = { return Api.account.ResetPasswordResult.parse_resetPasswordFailedWait($0) } dict[-370148227] = { return Api.account.ResetPasswordResult.parse_resetPasswordRequestedWait($0) } dict[-383330754] = { return Api.account.ResetPasswordResult.parse_resetPasswordOk($0) } + dict[343859772] = { return Api.messages.SearchResultsCalendar.parse_searchResultsCalendar($0) } dict[856375399] = { return Api.Config.parse_config($0) } dict[-75283823] = { return Api.TopPeerCategoryPeers.parse_topPeerCategoryPeers($0) } dict[-1107729093] = { return Api.Game.parse_game($0) } @@ -576,7 +578,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1032140601] = { return Api.BotCommand.parse_botCommand($0) } dict[1474462241] = { return Api.account.ContentSettings.parse_contentSettings($0) } dict[-193506890] = { return Api.phone.GroupParticipants.parse_groupParticipants($0) } - dict[190633460] = { return Api.ChatInviteImporter.parse_chatInviteImporter($0) } + dict[-1574303204] = { return Api.ChatInviteImporter.parse_chatInviteImporter($0) } dict[-2066640507] = { return Api.messages.AffectedMessages.parse_affectedMessages($0) } dict[-402498398] = { return Api.messages.SavedGifs.parse_savedGifsNotModified($0) } dict[-2069878259] = { return Api.messages.SavedGifs.parse_savedGifs($0) } @@ -783,7 +785,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1673717362] = { return Api.InputPeerNotifySettings.parse_inputPeerNotifySettings($0) } dict[-1634752813] = { return Api.messages.FavedStickers.parse_favedStickersNotModified($0) } dict[750063767] = { return Api.messages.FavedStickers.parse_favedStickers($0) } - dict[-1316944408] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) } + dict[19682803] = { return Api.ExportedChatInvite.parse_chatInviteExported($0) } dict[-1389486888] = { return Api.account.AuthorizationForm.parse_authorizationForm($0) } dict[-1392388579] = { return Api.Authorization.parse_authorization($0) } dict[-1361650766] = { return Api.MaskCoords.parse_maskCoords($0) } @@ -1097,6 +1099,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.UserStatus: _1.serialize(buffer, boxed) + case let _1 as Api.SearchResultsCalendarPeriod: + _1.serialize(buffer, boxed) case let _1 as Api.Folder: _1.serialize(buffer, boxed) case let _1 as Api.Dialog: @@ -1145,6 +1149,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.InputTheme: _1.serialize(buffer, boxed) + case let _1 as Api.messages.SearchResultsRawMessages: + _1.serialize(buffer, boxed) case let _1 as Api.Poll: _1.serialize(buffer, boxed) case let _1 as Api.InputNotifyPeer: @@ -1333,6 +1339,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.account.ResetPasswordResult: _1.serialize(buffer, boxed) + case let _1 as Api.messages.SearchResultsCalendar: + _1.serialize(buffer, boxed) case let _1 as Api.Config: _1.serialize(buffer, boxed) case let _1 as Api.TopPeerCategoryPeers: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index 288997a593..1b8a1ae174 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -477,6 +477,56 @@ public struct messages { } } + } + public enum SearchResultsRawMessages: TypeConstructorDescription { + case searchResultsRawMessages(msgIds: [Int32], msgDates: [Int32]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchResultsRawMessages(let msgIds, let msgDates): + if boxed { + buffer.appendInt32(2014782332) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(msgIds.count)) + for item in msgIds { + serializeInt32(item, buffer: buffer, boxed: false) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(msgDates.count)) + for item in msgDates { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .searchResultsRawMessages(let msgIds, let msgDates): + return ("searchResultsRawMessages", [("msgIds", msgIds), ("msgDates", msgDates)]) + } + } + + public static func parse_searchResultsRawMessages(_ reader: BufferReader) -> SearchResultsRawMessages? { + var _1: [Int32]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + var _2: [Int32]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.messages.SearchResultsRawMessages.searchResultsRawMessages(msgIds: _1!, msgDates: _2!) + } + else { + return nil + } + } + } public enum ExportedChatInvites: TypeConstructorDescription { case exportedChatInvites(count: Int32, invites: [Api.ExportedChatInvite], users: [Api.User]) @@ -1245,6 +1295,96 @@ public struct messages { } } + } + public enum SearchResultsCalendar: TypeConstructorDescription { + case searchResultsCalendar(flags: Int32, count: Int32, minDate: Int32, minMsgId: Int32, offsetIdOffset: Int32?, periods: [Api.SearchResultsCalendarPeriod], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users): + if boxed { + buffer.appendInt32(343859772) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + serializeInt32(minDate, buffer: buffer, boxed: false) + serializeInt32(minMsgId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(offsetIdOffset!, buffer: buffer, boxed: false)} + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(periods.count)) + for item in periods { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .searchResultsCalendar(let flags, let count, let minDate, let minMsgId, let offsetIdOffset, let periods, let messages, let chats, let users): + return ("searchResultsCalendar", [("flags", flags), ("count", count), ("minDate", minDate), ("minMsgId", minMsgId), ("offsetIdOffset", offsetIdOffset), ("periods", periods), ("messages", messages), ("chats", chats), ("users", users)]) + } + } + + public static func parse_searchResultsCalendar(_ reader: BufferReader) -> SearchResultsCalendar? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + var _5: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() } + var _6: [Api.SearchResultsCalendarPeriod]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SearchResultsCalendarPeriod.self) + } + var _7: [Api.Message]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _8: [Api.Chat]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _9: [Api.User]? + if let _ = reader.readInt32() { + _9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil + let _c6 = _6 != nil + let _c7 = _7 != nil + let _c8 = _8 != nil + let _c9 = _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.messages.SearchResultsCalendar.searchResultsCalendar(flags: _1!, count: _2!, minDate: _3!, minMsgId: _4!, offsetIdOffset: _5, periods: _6!, messages: _7!, chats: _8!, users: _9!) + } + else { + return nil + } + } + } public enum AffectedMessages: TypeConstructorDescription { case affectedMessages(pts: Int32, ptsCount: Int32) diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index 74ffabf01d..70a407fd93 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -160,14 +160,14 @@ public extension Api { } public enum ChatFull: TypeConstructorDescription { - case chatFull(flags: Int32, id: Int64, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttlPeriod: Int32?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?) - case channelFull(flags: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?) + case chatFull(flags: Int32, id: Int64, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?, call: Api.InputGroupCall?, ttlPeriod: Int32?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?) + case channelFull(flags: Int32, id: Int64, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite?, botInfo: [Api.BotInfo], migratedFromChatId: Int64?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int64?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, call: Api.InputGroupCall?, ttlPeriod: Int32?, pendingSuggestions: [String]?, groupcallDefaultJoinAs: Api.Peer?, themeEmoticon: String?, requestsPending: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon): + case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending): if boxed { - buffer.appendInt32(1304281241) + buffer.appendInt32(-2097579871) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -187,10 +187,11 @@ public extension Api { if Int(flags) & Int(1 << 14) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 15) != 0 {groupcallDefaultJoinAs!.serialize(buffer, true)} if Int(flags) & Int(1 << 16) != 0 {serializeString(themeEmoticon!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 17) != 0 {serializeInt32(requestsPending!, buffer: buffer, boxed: false)} break - case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon): + case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending): if boxed { - buffer.appendInt32(-374179305) + buffer.appendInt32(1084166537) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -232,16 +233,17 @@ public extension Api { }} if Int(flags) & Int(1 << 26) != 0 {groupcallDefaultJoinAs!.serialize(buffer, true)} if Int(flags) & Int(1 << 27) != 0 {serializeString(themeEmoticon!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 28) != 0 {serializeInt32(requestsPending!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon): - return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call), ("ttlPeriod", ttlPeriod), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon)]) - case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon): - return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call), ("ttlPeriod", ttlPeriod), ("pendingSuggestions", pendingSuggestions), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon)]) + case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId, let call, let ttlPeriod, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending): + return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId), ("call", call), ("ttlPeriod", ttlPeriod), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon), ("requestsPending", requestsPending)]) + case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let call, let ttlPeriod, let pendingSuggestions, let groupcallDefaultJoinAs, let themeEmoticon, let requestsPending): + return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("call", call), ("ttlPeriod", ttlPeriod), ("pendingSuggestions", pendingSuggestions), ("groupcallDefaultJoinAs", groupcallDefaultJoinAs), ("themeEmoticon", themeEmoticon), ("requestsPending", requestsPending)]) } } @@ -288,6 +290,8 @@ public extension Api { } } var _14: String? if Int(_1!) & Int(1 << 16) != 0 {_14 = parseString(reader) } + var _15: Int32? + if Int(_1!) & Int(1 << 17) != 0 {_15 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -302,8 +306,9 @@ public extension Api { let _c12 = (Int(_1!) & Int(1 << 14) == 0) || _12 != nil let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil let _c14 = (Int(_1!) & Int(1 << 16) == 0) || _14 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 { - return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13, themeEmoticon: _14) + let _c15 = (Int(_1!) & Int(1 << 17) == 0) || _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.ChatFull.chatFull(flags: _1!, id: _2!, about: _3!, participants: _4!, chatPhoto: _5, notifySettings: _6!, exportedInvite: _7, botInfo: _8, pinnedMsgId: _9, folderId: _10, call: _11, ttlPeriod: _12, groupcallDefaultJoinAs: _13, themeEmoticon: _14, requestsPending: _15) } else { return nil @@ -392,6 +397,8 @@ public extension Api { } } var _32: String? if Int(_1!) & Int(1 << 27) != 0 {_32 = parseString(reader) } + var _33: Int32? + if Int(_1!) & Int(1 << 28) != 0 {_33 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -424,8 +431,9 @@ public extension Api { let _c30 = (Int(_1!) & Int(1 << 25) == 0) || _30 != nil let _c31 = (Int(_1!) & Int(1 << 26) == 0) || _31 != nil let _c32 = (Int(_1!) & Int(1 << 27) == 0) || _32 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 { - return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!, call: _28, ttlPeriod: _29, pendingSuggestions: _30, groupcallDefaultJoinAs: _31, themeEmoticon: _32) + let _c33 = (Int(_1!) & Int(1 << 28) == 0) || _33 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 { + return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!, call: _28, ttlPeriod: _29, pendingSuggestions: _30, groupcallDefaultJoinAs: _31, themeEmoticon: _32, requestsPending: _33) } else { return nil @@ -2023,7 +2031,7 @@ public extension Api { } public enum ChatInvite: TypeConstructorDescription { case chatInviteAlready(chat: Api.Chat) - case chatInvite(flags: Int32, title: String, photo: Api.Photo, participantsCount: Int32, participants: [Api.User]?) + case chatInvite(flags: Int32, title: String, about: String?, photo: Api.Photo, participantsCount: Int32, participants: [Api.User]?) case chatInvitePeek(chat: Api.Chat, expires: Int32) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { @@ -2034,12 +2042,13 @@ public extension Api { } chat.serialize(buffer, true) break - case .chatInvite(let flags, let title, let photo, let participantsCount, let participants): + case .chatInvite(let flags, let title, let about, let photo, let participantsCount, let participants): if boxed { - buffer.appendInt32(-540871282) + buffer.appendInt32(806110401) } serializeInt32(flags, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 5) != 0 {serializeString(about!, buffer: buffer, boxed: false)} photo.serialize(buffer, true) serializeInt32(participantsCount, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 4) != 0 {buffer.appendInt32(481674261) @@ -2062,8 +2071,8 @@ public extension Api { switch self { case .chatInviteAlready(let chat): return ("chatInviteAlready", [("chat", chat)]) - case .chatInvite(let flags, let title, let photo, let participantsCount, let participants): - return ("chatInvite", [("flags", flags), ("title", title), ("photo", photo), ("participantsCount", participantsCount), ("participants", participants)]) + case .chatInvite(let flags, let title, let about, let photo, let participantsCount, let participants): + return ("chatInvite", [("flags", flags), ("title", title), ("about", about), ("photo", photo), ("participantsCount", participantsCount), ("participants", participants)]) case .chatInvitePeek(let chat, let expires): return ("chatInvitePeek", [("chat", chat), ("expires", expires)]) } @@ -2087,23 +2096,26 @@ public extension Api { _1 = reader.readInt32() var _2: String? _2 = parseString(reader) - var _3: Api.Photo? + var _3: String? + if Int(_1!) & Int(1 << 5) != 0 {_3 = parseString(reader) } + var _4: Api.Photo? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.Photo + _4 = Api.parse(reader, signature: signature) as? Api.Photo } - var _4: Int32? - _4 = reader.readInt32() - var _5: [Api.User]? + var _5: Int32? + _5 = reader.readInt32() + var _6: [Api.User]? if Int(_1!) & Int(1 << 4) != 0 {if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil + let _c3 = (Int(_1!) & Int(1 << 5) == 0) || _3 != nil let _c4 = _4 != nil - let _c5 = (Int(_1!) & Int(1 << 4) == 0) || _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.ChatInvite.chatInvite(flags: _1!, title: _2!, photo: _3!, participantsCount: _4!, participants: _5) + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 4) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.ChatInvite.chatInvite(flags: _1!, title: _2!, about: _3, photo: _4!, participantsCount: _5!, participants: _6) } else { return nil @@ -4080,6 +4092,52 @@ public extension Api { return Api.UserStatus.userStatusLastMonth } + } + public enum SearchResultsCalendarPeriod: TypeConstructorDescription { + case searchResultsCalendarPeriod(date: Int32, minMsgId: Int32, maxMsgId: Int32, count: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count): + if boxed { + buffer.appendInt32(-911191137) + } + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(minMsgId, buffer: buffer, boxed: false) + serializeInt32(maxMsgId, buffer: buffer, boxed: false) + serializeInt32(count, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .searchResultsCalendarPeriod(let date, let minMsgId, let maxMsgId, let count): + return ("searchResultsCalendarPeriod", [("date", date), ("minMsgId", minMsgId), ("maxMsgId", maxMsgId), ("count", count)]) + } + } + + public static func parse_searchResultsCalendarPeriod(_ reader: BufferReader) -> SearchResultsCalendarPeriod? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.SearchResultsCalendarPeriod.searchResultsCalendarPeriod(date: _1!, minMsgId: _2!, maxMsgId: _3!, count: _4!) + } + else { + return nil + } + } + } public enum Folder: TypeConstructorDescription { case folder(flags: Int32, id: Int32, title: String, photo: Api.ChatPhoto?) @@ -11366,7 +11424,6 @@ public extension Api { case channelAdminLogEventActionExportedInviteEdit(prevInvite: Api.ExportedChatInvite, newInvite: Api.ExportedChatInvite) case channelAdminLogEventActionParticipantVolume(participant: Api.GroupCallParticipant) case channelAdminLogEventActionChangeHistoryTTL(prevValue: Int32, newValue: Int32) - case channelAdminLogEventActionChangeTheme(prevValue: String, newValue: String) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -11576,13 +11633,6 @@ public extension Api { serializeInt32(prevValue, buffer: buffer, boxed: false) serializeInt32(newValue, buffer: buffer, boxed: false) break - case .channelAdminLogEventActionChangeTheme(let prevValue, let newValue): - if boxed { - buffer.appendInt32(-26672755) - } - serializeString(prevValue, buffer: buffer, boxed: false) - serializeString(newValue, buffer: buffer, boxed: false) - break } } @@ -11652,8 +11702,6 @@ public extension Api { return ("channelAdminLogEventActionParticipantVolume", [("participant", participant)]) case .channelAdminLogEventActionChangeHistoryTTL(let prevValue, let newValue): return ("channelAdminLogEventActionChangeHistoryTTL", [("prevValue", prevValue), ("newValue", newValue)]) - case .channelAdminLogEventActionChangeTheme(let prevValue, let newValue): - return ("channelAdminLogEventActionChangeTheme", [("prevValue", prevValue), ("newValue", newValue)]) } } @@ -12099,20 +12147,6 @@ public extension Api { return nil } } - public static func parse_channelAdminLogEventActionChangeTheme(_ reader: BufferReader) -> ChannelAdminLogEventAction? { - var _1: String? - _1 = parseString(reader) - var _2: String? - _2 = parseString(reader) - let _c1 = _1 != nil - let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChannelAdminLogEventAction.channelAdminLogEventActionChangeTheme(prevValue: _1!, newValue: _2!) - } - else { - return nil - } - } } public enum SecurePlainData: TypeConstructorDescription { @@ -14786,36 +14820,44 @@ public extension Api { } public enum ChatInviteImporter: TypeConstructorDescription { - case chatInviteImporter(userId: Int64, date: Int32) + case chatInviteImporter(flags: Int32, userId: Int64, date: Int32, approvedBy: Int64?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatInviteImporter(let userId, let date): + case .chatInviteImporter(let flags, let userId, let date, let approvedBy): if boxed { - buffer.appendInt32(190633460) + buffer.appendInt32(-1574303204) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(userId, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeInt64(approvedBy!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatInviteImporter(let userId, let date): - return ("chatInviteImporter", [("userId", userId), ("date", date)]) + case .chatInviteImporter(let flags, let userId, let date, let approvedBy): + return ("chatInviteImporter", [("flags", flags), ("userId", userId), ("date", date), ("approvedBy", approvedBy)]) } } public static func parse_chatInviteImporter(_ reader: BufferReader) -> ChatInviteImporter? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int64? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt64() } let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.ChatInviteImporter.chatInviteImporter(userId: _1!, date: _2!) + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.ChatInviteImporter.chatInviteImporter(flags: _1!, userId: _2!, date: _3!, approvedBy: _4) } else { return nil @@ -20074,13 +20116,13 @@ public extension Api { } public enum ExportedChatInvite: TypeConstructorDescription { - case chatInviteExported(flags: Int32, link: String, adminId: Int64, date: Int32, startDate: Int32?, expireDate: Int32?, usageLimit: Int32?, usage: Int32?) + case chatInviteExported(flags: Int32, link: String, adminId: Int64, date: Int32, startDate: Int32?, expireDate: Int32?, usageLimit: Int32?, usage: Int32?, approved: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage): + case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage, let approved): if boxed { - buffer.appendInt32(-1316944408) + buffer.appendInt32(19682803) } serializeInt32(flags, buffer: buffer, boxed: false) serializeString(link, buffer: buffer, boxed: false) @@ -20090,14 +20132,15 @@ public extension Api { if Int(flags) & Int(1 << 1) != 0 {serializeInt32(expireDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(usageLimit!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeInt32(usage!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 7) != 0 {serializeInt32(approved!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage): - return ("chatInviteExported", [("flags", flags), ("link", link), ("adminId", adminId), ("date", date), ("startDate", startDate), ("expireDate", expireDate), ("usageLimit", usageLimit), ("usage", usage)]) + case .chatInviteExported(let flags, let link, let adminId, let date, let startDate, let expireDate, let usageLimit, let usage, let approved): + return ("chatInviteExported", [("flags", flags), ("link", link), ("adminId", adminId), ("date", date), ("startDate", startDate), ("expireDate", expireDate), ("usageLimit", usageLimit), ("usage", usage), ("approved", approved)]) } } @@ -20118,6 +20161,8 @@ public extension Api { if Int(_1!) & Int(1 << 2) != 0 {_7 = reader.readInt32() } var _8: Int32? if Int(_1!) & Int(1 << 3) != 0 {_8 = reader.readInt32() } + var _9: Int32? + if Int(_1!) & Int(1 << 7) != 0 {_9 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -20126,8 +20171,9 @@ public extension Api { let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.ExportedChatInvite.chatInviteExported(flags: _1!, link: _2!, adminId: _3!, date: _4!, startDate: _5, expireDate: _6, usageLimit: _7, usage: _8) + let _c9 = (Int(_1!) & Int(1 << 7) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.ExportedChatInvite.chatInviteExported(flags: _1!, link: _2!, adminId: _3!, date: _4!, startDate: _5, expireDate: _6, usageLimit: _7, usage: _8, approved: _9) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index 01ee7d95e9..3cc98b3729 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -3769,22 +3769,6 @@ public extension Api { }) } - public static func getStatsURL(flags: Int32, peer: Api.InputPeer, params: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(-2127811866) - serializeInt32(flags, buffer: buffer, boxed: false) - peer.serialize(buffer, true) - serializeString(params, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getStatsURL", parameters: [("flags", flags), ("peer", peer), ("params", params)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsURL? in - let reader = BufferReader(buffer) - var result: Api.StatsURL? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.StatsURL - } - return result - }) - } - public static func editChatAbout(peer: Api.InputPeer, about: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(-554301545) @@ -4324,15 +4308,16 @@ public extension Api { }) } - public static func editExportedChatInvite(flags: Int32, peer: Api.InputPeer, link: String, expireDate: Int32?, usageLimit: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func editExportedChatInvite(flags: Int32, peer: Api.InputPeer, link: String, expireDate: Int32?, usageLimit: Int32?, requestNeeded: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(48562110) + buffer.appendInt32(1557932235) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) serializeString(link, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(expireDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(usageLimit!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.editExportedChatInvite", parameters: [("flags", flags), ("peer", peer), ("link", link), ("expireDate", expireDate), ("usageLimit", usageLimit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in + if Int(flags) & Int(1 << 3) != 0 {requestNeeded!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.editExportedChatInvite", parameters: [("flags", flags), ("peer", peer), ("link", link), ("expireDate", expireDate), ("usageLimit", usageLimit), ("requestNeeded", requestNeeded)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ExportedChatInvite? in let reader = BufferReader(buffer) var result: Api.messages.ExportedChatInvite? if let signature = reader.readInt32() { @@ -4386,15 +4371,16 @@ public extension Api { }) } - public static func getChatInviteImporters(peer: Api.InputPeer, link: String, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + public static func getChatInviteImporters(flags: Int32, peer: Api.InputPeer, link: String?, offsetDate: Int32, offsetUser: Api.InputUser, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(654013065) + buffer.appendInt32(-1742901790) + serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) - serializeString(link, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {serializeString(link!, buffer: buffer, boxed: false)} serializeInt32(offsetDate, buffer: buffer, boxed: false) offsetUser.serialize(buffer, true) serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("peer", peer), ("link", link), ("offsetDate", offsetDate), ("offsetUser", offsetUser), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in + return (FunctionDescription(name: "messages.getChatInviteImporters", parameters: [("flags", flags), ("peer", peer), ("link", link), ("offsetDate", offsetDate), ("offsetUser", offsetUser), ("limit", limit)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.ChatInviteImporters? in let reader = BufferReader(buffer) var result: Api.messages.ChatInviteImporters? if let signature = reader.readInt32() { @@ -4462,6 +4448,57 @@ public extension Api { return result }) } + + public static func getSearchResultsCalendar(flags: Int32, peer: Api.InputPeer, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1991714845) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + filter.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSearchResultsCalendar", parameters: [("flags", flags), ("peer", peer), ("filter", filter), ("offsetId", offsetId), ("offsetDate", offsetDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsCalendar? in + let reader = BufferReader(buffer) + var result: Api.messages.SearchResultsCalendar? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsCalendar + } + return result + }) + } + + public static func getSearchResultsRawMessages(peer: Api.InputPeer, filter: Api.MessagesFilter, offsetId: Int32, offsetDate: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1258852762) + peer.serialize(buffer, true) + filter.serialize(buffer, true) + serializeInt32(offsetId, buffer: buffer, boxed: false) + serializeInt32(offsetDate, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.getSearchResultsRawMessages", parameters: [("peer", peer), ("filter", filter), ("offsetId", offsetId), ("offsetDate", offsetDate)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.SearchResultsRawMessages? in + let reader = BufferReader(buffer) + var result: Api.messages.SearchResultsRawMessages? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.SearchResultsRawMessages + } + return result + }) + } + + public static func hideChatJoinRequest(flags: Int32, peer: Api.InputPeer, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2145904661) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "messages.hideChatJoinRequest", parameters: [("flags", flags), ("peer", peer), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } } public struct channels { public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/submodules/TelegramCore/Sources/ApiUtils/ExportedInvitation.swift b/submodules/TelegramCore/Sources/ApiUtils/ExportedInvitation.swift index 81e7e3e49f..13ae629fc9 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ExportedInvitation.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ExportedInvitation.swift @@ -6,7 +6,7 @@ import TelegramApi extension ExportedInvitation { init(apiExportedInvite: Api.ExportedChatInvite) { switch apiExportedInvite { - case let .chatInviteExported(flags, link, adminId, date, startDate, expireDate, usageLimit, usage): + case let .chatInviteExported(flags, link, adminId, date, startDate, expireDate, usageLimit, usage, _): self = ExportedInvitation(link: link, isPermanent: (flags & (1 << 5)) != 0, isRevoked: (flags & (1 << 0)) != 0, adminId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)), date: date, startDate: startDate, expireDate: expireDate, usageLimit: usageLimit, count: usage) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift new file mode 100644 index 0000000000..f334c9ed8a --- /dev/null +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SparseMessageList.swift @@ -0,0 +1,359 @@ +import SwiftSignalKit +import Postbox +import TelegramApi + +public final class SparseMessageList { + private final class Impl { + private let queue: Queue + private let account: Account + private let peerId: PeerId + private let messageTag: MessageTags + + private struct TopSection: Equatable { + var messages: [Message] + + static func ==(lhs: TopSection, rhs: TopSection) -> Bool { + if lhs.messages.count != rhs.messages.count { + return false + } + for i in 0 ..< lhs.messages.count { + if lhs.messages[i].id != rhs.messages[i].id { + return false + } + if lhs.messages[i].stableVersion != rhs.messages[i].stableVersion { + return false + } + } + return true + } + } + + private struct ItemIndices: Equatable { + var ids: [MessageId] + var timestamps: [Int32] + } + + private var topSectionItemRequestCount: Int = 100 + private var topSection: TopSection? + private var topItemsDisposable: Disposable? + + private var messageIndices: ItemIndices? + private var messageIndicesDisposable: Disposable? + + private var loadingPlaceholders: [MessageId: Disposable] = [:] + private var loadedPlaceholders: [MessageId: Message] = [:] + + let statePromise = Promise() + + init(queue: Queue, account: Account, peerId: PeerId, messageTag: MessageTags) { + self.queue = queue + self.account = account + self.peerId = peerId + self.messageTag = messageTag + + self.resetTopSection() + self.messageIndicesDisposable = (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(ItemIndices(ids: [], timestamps: [])) + } + return self.account.network.request(Api.functions.messages.getSearchResultsRawMessages(peer: inputPeer, filter: .inputMessagesFilterPhotoVideo, offsetId: 0, offsetDate: 0)) + |> map { result -> ItemIndices in + switch result { + case let .searchResultsRawMessages(msgIds, msgDates): + return ItemIndices(ids: msgIds.map { id in + return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id) + }, timestamps: msgDates) + } + } + |> `catch` { _ -> Signal in + return .single(ItemIndices(ids: [], timestamps: [])) + } + } + |> deliverOnMainQueue).start(next: { [weak self] indices in + guard let strongSelf = self else { + return + } + strongSelf.messageIndices = indices + if strongSelf.topSection != nil { + strongSelf.updateState() + } + }) + } + + deinit { + self.topItemsDisposable?.dispose() + self.messageIndicesDisposable?.dispose() + } + + private func resetTopSection() { + self.topItemsDisposable = (self.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .upperBound, count: 10, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: self.messageTag, appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: []) + |> deliverOn(self.queue)).start(next: { [weak self] view, updateType, _ in + guard let strongSelf = self else { + return + } + switch updateType { + case .FillHole: + strongSelf.resetTopSection() + default: + strongSelf.updateTopSection(view: view) + } + }) + } + + func loadMoreFromTopSection() { + self.topSectionItemRequestCount += 100 + self.resetTopSection() + } + + func loadPlaceholders(ids: [MessageId]) { + var loadGlobalIds: [MessageId] = [] + var loadChannelIds: [PeerId: [MessageId]] = [:] + for id in ids { + if self.loadingPlaceholders[id] != nil { + continue + } + self.loadingPlaceholders[id] = MetaDisposable() + if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup { + loadGlobalIds.append(id) + } else if id.peerId.namespace == Namespaces.Peer.CloudChannel { + if loadChannelIds[id.peerId] == nil { + loadChannelIds[id.peerId] = [] + } + loadChannelIds[id.peerId]!.append(id) + } + } + + var loadSignals: [Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError>] = [] + let account = self.account + + if !loadGlobalIds.isEmpty { + loadSignals.append(self.account.postbox.transaction { transaction -> [Api.InputMessage] in + var result: [Api.InputMessage] = [] + for id in loadGlobalIds { + let inputMessage: Api.InputMessage = .inputMessageID(id: id.id) + result.append(inputMessage) + } + return result + } + |> mapToSignal { inputMessages -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in + return account.network.request(Api.functions.messages.getMessages(id: inputMessages)) + |> map { result -> (messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) in + switch result { + case let .messages(messages, chats, users): + return (messages, chats, users) + case let .messagesSlice(_, _, _, _, messages, chats, users): + return (messages, chats, users) + case let .channelMessages(_, _, _, _, messages, chats, users): + return (messages, chats, users) + case .messagesNotModified: + return ([], [], []) + } + } + |> `catch` { _ -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in + return .single(([], [], [])) + } + }) + } + + if !loadChannelIds.isEmpty { + for (channelId, ids) in loadChannelIds { + loadSignals.append(self.account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(channelId).flatMap(apiInputChannel) + } + |> mapToSignal { inputChannel -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in + guard let inputChannel = inputChannel else { + return .single(([], [], [])) + } + + return account.network.request(Api.functions.channels.getMessages(channel: inputChannel, id: ids.map { Api.InputMessage.inputMessageID(id: $0.id) })) + |> map { result -> (messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) in + switch result { + case let .messages(messages, chats, users): + return (messages, chats, users) + case let .messagesSlice(_, _, _, _, messages, chats, users): + return (messages, chats, users) + case let .channelMessages(_, _, _, _, messages, chats, users): + return (messages, chats, users) + case .messagesNotModified: + return ([], [], []) + } + } + |> `catch` { _ -> Signal<(messages: [Api.Message], chats: [Api.Chat], users: [Api.User]), NoError> in + return .single(([], [], [])) + } + }) + } + } + + let _ = (combineLatest(queue: self.queue, loadSignals) + |> mapToSignal { messageLists -> Signal<[Message], NoError> in + return account.postbox.transaction { transaction -> [Message] in + var parsedMessages: [StoreMessage] = [] + var peers: [Peer] = [] + var peerPresences: [PeerId: PeerPresence] = [:] + + for (messages, chats, users) in messageLists { + for chat in chats { + if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { + peers.append(groupOrChannel) + } + } + for user in users { + let telegramUser = TelegramUser(user: user) + peers.append(telegramUser) + if let presence = TelegramUserPresence(apiUser: user) { + peerPresences[telegramUser.id] = presence + } + } + + for message in messages { + if let parsedMessage = StoreMessage(apiMessage: message) { + parsedMessages.append(parsedMessage) + } + } + } + + let _ = transaction.addMessages(parsedMessages, location: .Random) + + var result: [Message] = [] + for parsedMessage in parsedMessages { + switch parsedMessage.id { + case let .Id(id): + if let message = transaction.getMessage(id) { + result.append(message) + } + case .Partial: + break + } + } + + return result + } + } + |> deliverOn(self.queue)).start(next: { [weak self] messages in + guard let strongSelf = self else { + return + } + for message in messages { + strongSelf.loadedPlaceholders[message.id] = message + } + + strongSelf.updateState() + }) + } + + private func updateTopSection(view: MessageHistoryView) { + var topSection: TopSection? + + if view.isLoading { + topSection = nil + } else { + topSection = TopSection(messages: view.entries.map { entry in + return entry.message + }) + } + + if self.topSection != topSection { + self.topSection = topSection + } + self.updateState() + } + + private func updateState() { + var items: [SparseMessageList.State.Item] = [] + var minMessageId: MessageId? + if let topSection = self.topSection { + for i in 0 ..< topSection.messages.count { + let message = topSection.messages[i] + items.append(SparseMessageList.State.Item(index: items.count, content: .message(message))) + if let minMessageIdValue = minMessageId { + if message.id < minMessageIdValue { + minMessageId = message.id + } + } else { + minMessageId = message.id + } + } + } + + var totalCount = items.count + if let minMessageId = minMessageId, let messageIndices = self.messageIndices { + for i in 0 ..< messageIndices.ids.count { + if messageIndices.ids[i] < minMessageId { + if let message = self.loadedPlaceholders[messageIndices.ids[i]] { + items.append(SparseMessageList.State.Item(index: items.count, content: .message(message))) + } else { + items.append(SparseMessageList.State.Item(index: items.count, content: .placeholder(id: messageIndices.ids[i], timestamp: messageIndices.timestamps[i]))) + } + totalCount += 1 + } + } + } + + self.statePromise.set(.single(SparseMessageList.State( + items: items, + totalCount: items.count, + isLoading: self.topSection == nil + ))) + } + } + + private let queue: Queue + private let impl: QueueLocalObject + + public struct State { + public final class Item { + public enum Content { + case message(Message) + case placeholder(id: MessageId, timestamp: Int32) + } + + public let index: Int + public let content: Content + + init(index: Int, content: Content) { + self.index = index + self.content = content + } + } + + public var items: [Item] + public var totalCount: Int + public var isLoading: Bool + } + + 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, messageTag: MessageTags) { + self.queue = .mainQueue() + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue, account: account, peerId: peerId, messageTag: messageTag) + }) + } + + public func loadMoreFromTopSection() { + self.impl.with { impl in + impl.loadMoreFromTopSection() + } + } + + public func loadPlaceholders(ids: [MessageId]) { + self.impl.with { impl in + impl.loadPlaceholders(ids: ids) + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 08b883f193..3994a827b9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -241,5 +241,9 @@ public extension TelegramEngine { } } } + + public func sparseMessageList(peerId: EnginePeer.Id, tag: EngineMessage.Tags) -> SparseMessageList { + return SparseMessageList(account: self.account, peerId: peerId, messageTag: tag) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift index 6c2b20eb56..cb46ec5ab1 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChannelAdminEventLogs.swift @@ -250,8 +250,8 @@ func channelAdminLogEvents(postbox: Postbox, network: Network, peerId: PeerId, m action = .groupCallUpdateParticipantVolume(peerId: parsedParticipant.peerId, volume: parsedParticipant.volume ?? 10000) case let .channelAdminLogEventActionChangeHistoryTTL(prevValue, newValue): action = .changeHistoryTTL(previousValue: prevValue, updatedValue: newValue) - case let .channelAdminLogEventActionChangeTheme(prevValue, newValue): - action = .changeTheme(previous: prevValue, updated: newValue) + /*case let .channelAdminLogEventActionChangeTheme(prevValue, newValue): + action = .changeTheme(previous: prevValue, updated: newValue)*/ } let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) if let action = action { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift index 7a0915833a..2efd1aab8a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InvitationLinks.swift @@ -92,7 +92,7 @@ func _internal_editPeerExportedInvitation(account: Account, peerId: PeerId, link if let _ = usageLimit { flags |= (1 << 1) } - return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: expireDate, usageLimit: usageLimit)) + return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: expireDate, usageLimit: usageLimit, requestNeeded: .boolFalse)) |> mapError { _ in return EditPeerExportedInvitationError.generic } |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction in @@ -132,7 +132,7 @@ func _internal_revokePeerExportedInvitation(account: Account, peerId: PeerId, li return account.postbox.transaction { transaction -> Signal in if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) { let flags: Int32 = (1 << 2) - return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: nil, usageLimit: nil)) + return account.network.request(Api.functions.messages.editExportedChatInvite(flags: flags, peer: inputPeer, link: link, expireDate: nil, usageLimit: nil, requestNeeded: .boolFalse)) |> mapError { _ in return RevokePeerExportedInvitationError.generic } |> mapToSignal { result -> Signal in return account.postbox.transaction { transaction in @@ -775,7 +775,7 @@ private final class PeerInvitationImportersContextImpl { if let inputPeer = inputPeer { let offsetUser = lastResult?.peer.peer.flatMap { apiInputUser($0) } ?? .inputUserEmpty let offsetDate = lastResult?.date ?? 0 - let signal = account.network.request(Api.functions.messages.getChatInviteImporters(peer: inputPeer, link: link, offsetDate: offsetDate, offsetUser: offsetUser, limit: lastResult == nil ? 10 : 50)) + let signal = account.network.request(Api.functions.messages.getChatInviteImporters(flags: 0, peer: inputPeer, link: link, offsetDate: offsetDate, offsetUser: offsetUser, limit: lastResult == nil ? 10 : 50)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -799,7 +799,7 @@ private final class PeerInvitationImportersContextImpl { let peerId: PeerId let date: Int32 switch importer { - case let .chatInviteImporter(userId, dateValue): + case let .chatInviteImporter(_, userId, dateValue, _): peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) date = dateValue } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift index 2f20cf0c0c..b7ae155850 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/JoinLink.swift @@ -67,7 +67,7 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal mapToSignal { (result) -> Signal in if let result = result { switch result { - case let .chatInvite(_, title, invitePhoto, participantsCount, participants): + case let .chatInvite(_, title, _, invitePhoto, participantsCount, participants): let photo = telegramMediaImageFromApiPhoto(invitePhoto).flatMap({ smallestImageRepresentation($0.representations) }) return .single(.invite(title: title, photoRepresentation: photo, participantsCount: participantsCount, participants: participants?.map({TelegramUser(user: $0)}))) case let .chatInviteAlready(chat): diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index dd88d1b926..1def6650bf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -222,14 +222,14 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee switch result { case let .chatFull(fullChat, chats, users): switch fullChat { - case let .chatFull(_, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _): + case let .chatFull(_, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _): transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)]) case .channelFull: break } switch fullChat { - case let .chatFull(chatFullFlags, _, chatFullAbout, chatFullParticipants, chatFullChatPhoto, _, chatFullExportedInvite, chatFullBotInfo, chatFullPinnedMsgId, _, chatFullCall, _, chatFullGroupcallDefaultJoinAs, chatFullThemeEmoticon): + case let .chatFull(chatFullFlags, _, chatFullAbout, chatFullParticipants, chatFullChatPhoto, _, chatFullExportedInvite, chatFullBotInfo, chatFullPinnedMsgId, _, chatFullCall, _, chatFullGroupcallDefaultJoinAs, chatFullThemeEmoticon, _): var botInfos: [CachedPeerBotInfo] = [] for botInfo in chatFullBotInfo ?? [] { switch botInfo { @@ -350,14 +350,14 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee switch result { case let .chatFull(fullChat, chats, users): switch fullChat { - case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, notifySettings, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): transaction.updateCurrentPeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)]) case .chatFull: break } switch fullChat { - case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon): + case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, _, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, _, inputCall, ttl, pendingSuggestions, groupcallDefaultJoinAs, themeEmoticon, _): var channelFlags = CachedChannelFlags() if (flags & (1 << 3)) != 0 { channelFlags.insert(.canDisplayParticipants) diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 44b8ae8584..13fbcab13e 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -241,6 +241,7 @@ swift_library( "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", "//submodules/ComponentFlow:ComponentFlow", "//submodules/AdUI:AdUI", + "//submodules/SparseItemGrid:SparseItemGrid", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift index a66f2a017f..99f27ffd4e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoVisualMediaPaneNode.swift @@ -13,6 +13,7 @@ import GridMessageSelectionNode import UniversalMediaPlayer import ListMessageItem import ChatMessageInteractiveMediaBadge +import SparseItemGrid private final class FrameSequenceThumbnailNode: ASDisplayNode { private let context: AccountContext @@ -178,10 +179,10 @@ private final class VisualMediaItemNode: ASDisplayNode { self.containerNode.addSubnode(self.mediaBadgeNode) self.containerNode.activated = { [weak self] gesture, _ in - guard let strongSelf = self, let item = strongSelf.item else { + guard let strongSelf = self, let item = strongSelf.item, let message = item.0.message else { return } - strongSelf.interaction.openMessageContextActions(item.0.message, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) + strongSelf.interaction.openMessageContextActions(message, strongSelf.containerNode, strongSelf.containerNode.bounds, gesture) } } @@ -208,9 +209,9 @@ private final class VisualMediaItemNode: ASDisplayNode { if case .ended = recognizer.state { if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { if case .tap = gesture { - if let (item, _, _, _) = self.item { + if let (item, _, _, _) = self.item, let message = item.message { var media: Media? - for value in item.message.media { + for value in message.media { if let image = value as? TelegramMediaImage { media = image break @@ -222,13 +223,13 @@ private final class VisualMediaItemNode: ASDisplayNode { if let media = media { if let file = media as? TelegramMediaFile { - if isMediaStreamable(message: item.message, media: file) { - self.interaction.openMessage(item.message) + if isMediaStreamable(message: message, media: file) { + self.interaction.openMessage(message) } else { self.progressPressed() } } else { - self.interaction.openMessage(item.message) + self.interaction.openMessage(message) } } } @@ -275,17 +276,19 @@ private final class VisualMediaItemNode: ASDisplayNode { } self.theme = theme var media: Media? - for value in item.message.media { - if let image = value as? TelegramMediaImage { - media = image - break - } else if let file = value as? TelegramMediaFile { - media = file - break + if let message = item.message { + for value in message.media { + if let image = value as? TelegramMediaImage { + media = image + break + } else if let file = value as? TelegramMediaFile { + media = file + break + } } } - if let file = media as? TelegramMediaFile, file.isAnimated { + if let message = item.message, let file = media as? TelegramMediaFile, file.isAnimated { if self.videoLayerFrameManager == nil { let sampleBufferLayer: SampleBufferLayer if let current = self.sampleBufferLayer { @@ -296,7 +299,7 @@ private final class VisualMediaItemNode: ASDisplayNode { self.imageNode.layer.addSublayer(sampleBufferLayer.layer) } - self.videoLayerFrameManager = SoftwareVideoLayerFrameManager(account: self.context.account, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), layerHolder: sampleBufferLayer) + self.videoLayerFrameManager = SoftwareVideoLayerFrameManager(account: self.context.account, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), layerHolder: sampleBufferLayer) self.videoLayerFrameManager?.start() } } else { @@ -307,12 +310,12 @@ private final class VisualMediaItemNode: ASDisplayNode { self.videoLayerFrameManager = nil } - if let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)) { + if let message = item.message, let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)) { var mediaDimensions: CGSize? if let image = media as? TelegramMediaImage, let largestSize = largestImageRepresentation(image.representations)?.dimensions { mediaDimensions = largestSize.cgSize - self.imageNode.setSignal(mediaGridMessagePhoto(account: context.account, photoReference: .message(message: MessageReference(item.message), media: image), fullRepresentationSize: CGSize(width: 300.0, height: 300.0), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true) + self.imageNode.setSignal(mediaGridMessagePhoto(account: context.account, photoReference: .message(message: MessageReference(message), media: image), fullRepresentationSize: CGSize(width: 300.0, height: 300.0), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true) self.fetchStatusDisposable.set(nil) self.statusNode.transitionToState(.none, completion: { [weak self] in @@ -322,7 +325,7 @@ private final class VisualMediaItemNode: ASDisplayNode { self.resourceStatus = nil } else if let file = media as? TelegramMediaFile, file.isVideo { mediaDimensions = file.dimensions?.cgSize - self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(item.message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad) + self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad) self.mediaBadgeNode.isHidden = file.isAnimated @@ -330,12 +333,12 @@ private final class VisualMediaItemNode: ASDisplayNode { self.item = (item, media, size, mediaDimensions) - self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: item.message.id, file: file) + self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: message.id, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in - if let strongSelf = self, let (item, _, _, _) = strongSelf.item { + if let strongSelf = self, let (item, _, _, _) = strongSelf.item, let message = item.message { strongSelf.resourceStatus = status - let isStreamable = isMediaStreamable(message: item.message, media: file) + let isStreamable = isMediaStreamable(message: message, media: file) var statusState: RadialStatusNodeState = .none if isStreamable || file.isAnimated { @@ -484,18 +487,18 @@ private final class VisualMediaItemNode: ASDisplayNode { } func updateSelectionState(animated: Bool) { - if let (item, _, _, _) = self.item, let theme = self.theme { + if let (item, _, _, _) = self.item, let message = item.message, let theme = self.theme { self.containerNode.isGestureEnabled = self.interaction.selectedMessageIds == nil if let selectedIds = self.interaction.selectedMessageIds { - let selected = selectedIds.contains(item.message.id) + let selected = selectedIds.contains(message.id) if let selectionNode = self.selectionNode { selectionNode.updateSelected(selected, animated: animated) selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) } else { let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in - if let strongSelf = self, let messageId = strongSelf.item?.0.message.id { + if let strongSelf = self, let messageId = strongSelf.item?.0.message?.id { var toggledValue = true if let selectedMessageIds = strongSelf.interaction.selectedMessageIds, selectedMessageIds.contains(messageId) { toggledValue = false @@ -549,7 +552,7 @@ private final class VisualMediaItemNode: ASDisplayNode { func updateHiddenMedia() { if let (item, _, _, _) = self.item { - if let _ = self.interaction.hiddenMedia[item.message.id] { + if let _ = self.interaction.hiddenMedia[item.id] { self.isHidden = true } else { self.isHidden = false @@ -562,29 +565,37 @@ private final class VisualMediaItemNode: ASDisplayNode { } private final class VisualMediaItem { - let message: Message - let dimensions: CGSize - let aspectRatio: CGFloat + let id: MessageId + let timestamp: Int32 + let message: Message? + + enum StableId: Hashable { + case message(UInt32) + case placeholder(MessageId) + } + + var stableId: StableId { + if let message = self.message { + return .message(message.stableId) + } else { + return .placeholder(self.id) + } + } init(message: Message) { self.message = message - - var aspectRatio: CGFloat = 1.0 - var dimensions = CGSize(width: 100.0, height: 100.0) - for media in message.media { - if let file = media as? TelegramMediaFile { - if let dimensionsValue = file.dimensions, dimensions.height > 1 { - dimensions = dimensionsValue.cgSize - aspectRatio = CGFloat(dimensionsValue.width) / CGFloat(dimensionsValue.height) - } - } - } - self.aspectRatio = aspectRatio - self.dimensions = dimensions + self.id = message.id + self.timestamp = message.timestamp + } + + init(id: MessageId, timestamp: Int32) { + self.id = id + self.timestamp = timestamp + self.message = nil } } -private final class FloatingHeaderNode: ASDisplayNode { +/*private final class FloatingHeaderNode: ASDisplayNode { private let backgroundNode: ASImageNode private let labelNode: ImmediateTextNode @@ -631,7 +642,7 @@ private final class FloatingHeaderNode: ASDisplayNode { let size = CGSize(width: labelSize.width + sideInset * 2.0, height: 27.0) return size } -} +}*/ private func tagMaskForType(_ type: PeerInfoVisualMediaPaneNode.ContentType) -> MessageTags { switch type { @@ -684,52 +695,12 @@ private enum ItemsLayout { } } - final class Balanced { - let frames: [CGRect] - let contentHeight: CGFloat - - init(containerWidth: CGFloat, items: [VisualMediaItem], bottomInset: CGFloat) { - self.frames = calculateItemFrames(items: items, containerWidth: containerWidth) - if let last = self.frames.last { - self.contentHeight = last.maxY + bottomInset - } else { - self.contentHeight = bottomInset - } - } - - func visibleRange(rect: CGRect) -> (Int, Int) { - for i in 0 ..< self.frames.count { - if self.frames[i].maxY >= rect.minY { - for j in i ..< self.frames.count { - if self.frames[j].minY >= rect.maxY { - return (i, j - 1) - } - } - return (i, self.frames.count - 1) - } - } - return (0, -1) - } - - func frame(forItemAt index: Int, sideInset: CGFloat) -> CGRect { - if index >= 0 && index < self.frames.count { - return self.frames[index] - } else { - assertionFailure() - return CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 100.0)) - } - } - } - case grid(Grid) - case balanced(Balanced) var contentHeight: CGFloat { switch self { case let .grid(grid): return grid.contentHeight - case let .balanced(balanced): - return balanced.contentHeight } } @@ -737,8 +708,6 @@ private enum ItemsLayout { switch self { case let .grid(grid): return grid.visibleRange(rect: rect) - case let .balanced(balanced): - return balanced.visibleRange(rect: rect) } } @@ -746,8 +715,6 @@ private enum ItemsLayout { switch self { case let .grid(grid): return grid.frame(forItemAt: index, sideInset: sideInset) - case let .balanced(balanced): - return balanced.frame(forItemAt: index, sideInset: sideInset) } } } @@ -764,10 +731,10 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro private let contentType: ContentType weak var parentController: ViewController? - + + private let scrollingArea: SparseItemGridScrollingArea private let scrollNode: ASScrollNode - private let floatingHeaderNode: FloatingHeaderNode - private var flashHeaderDelayTimer: Foundation.Timer? + private var isDeceleratingAfterTracking = false private var _itemInteraction: VisualMediaItemInteraction? @@ -787,28 +754,39 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro private var hiddenMediaDisposable: Disposable? private var mediaItems: [VisualMediaItem] = [] private var itemsLayout: ItemsLayout? - private var visibleMediaItems: [UInt32: VisualMediaItemNode] = [:] + private var visibleMediaItems: [VisualMediaItem.StableId: VisualMediaItemNode] = [:] private var numberOfItemsToRequest: Int = 50 - private var currentView: MessageHistoryView? + //private var currentView: MessageHistoryView? private var isRequestingView: Bool = false private var isFirstHistoryView: Bool = true private var decelerationAnimator: ConstantDisplayLinkAnimator? private var animationTimer: SwiftSignalKit.Timer? + + private let listSource: SparseMessageList + private var requestedPlaceholderIds = Set() init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, contentType: ContentType) { self.context = context self.peerId = peerId self.chatControllerInteraction = chatControllerInteraction self.contentType = contentType - + + self.scrollingArea = SparseItemGridScrollingArea() self.scrollNode = ASScrollNode() - self.floatingHeaderNode = FloatingHeaderNode() - self.floatingHeaderNode.alpha = 0.0 + + self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, tag: tagMaskForType(self.contentType)) super.init() + + self.scrollingArea.beginScrolling = { [weak self] in + guard let strongSelf = self else { + return nil + } + return strongSelf.scrollNode.view + } self._itemInteraction = VisualMediaItemInteraction( openMessage: { [weak self] message in @@ -833,7 +811,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.scrollNode.view.delegate = self self.addSubnode(self.scrollNode) - self.addSubnode(self.floatingHeaderNode) + self.addSubnode(self.scrollingArea) self.requestHistoryAroundVisiblePosition() @@ -874,8 +852,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro func ensureMessageIsVisible(id: MessageId) { let activeRect = self.scrollNode.bounds for item in self.mediaItems { - if item.message.id == id { - if let itemNode = self.visibleMediaItems[item.message.stableId] { + if let message = item.message, message.id == id { + if let itemNode = self.visibleMediaItems[item.stableId] { if !activeRect.contains(itemNode.frame) { let targetContentOffset = CGPoint(x: 0.0, y: max(-self.scrollNode.view.contentInset.top, itemNode.frame.minY - (self.scrollNode.frame.height - itemNode.frame.height) / 2.0)) self.scrollNode.view.setContentOffset(targetContentOffset, animated: false) @@ -891,38 +869,38 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro return } self.isRequestingView = true - self.listDisposable.set((self.context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(self.peerId), index: .upperBound, anchorIndex: .upperBound, count: self.numberOfItemsToRequest, fixedCombinedReadStates: nil, tagMask: tagMaskForType(self.contentType)) - |> deliverOnMainQueue).start(next: { [weak self] (view, updateType, _) in + self.listDisposable.set((self.listSource.state + |> deliverOnMainQueue).start(next: { [weak self] list in guard let strongSelf = self else { return } - strongSelf.updateHistory(view: view, updateType: updateType) + strongSelf.updateHistory(list: list) strongSelf.isRequestingView = false })) } - private func updateHistory(view: MessageHistoryView, updateType: ViewUpdateType) { - self.currentView = view - - switch updateType { - case .FillHole: - self.requestHistoryAroundVisiblePosition() - default: - self.mediaItems.removeAll() - for entry in view.entries.reversed() { - self.mediaItems.append(VisualMediaItem(message: entry.message)) + private func updateHistory(list: SparseMessageList.State) { + //self.currentView = view + + self.mediaItems.removeAll() + for item in list.items { + switch item.content { + case let .message(message): + self.mediaItems.append(VisualMediaItem(message: message)) + case let .placeholder(id, timestamp): + self.mediaItems.append(VisualMediaItem(id: id, timestamp: timestamp)) } - self.itemsLayout = nil - - let wasFirstHistoryView = self.isFirstHistoryView - self.isFirstHistoryView = false - - if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { - self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate) - if !self.didSetReady { - self.didSetReady = true - self.ready.set(.single(true)) - } + } + self.itemsLayout = nil + + let wasFirstHistoryView = self.isFirstHistoryView + self.isFirstHistoryView = false + + if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams { + self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: wasFirstHistoryView, transition: .immediate) + if !self.didSetReady { + self.didSetReady = true + self.ready.set(.single(true)) } } } @@ -938,7 +916,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro func findLoadedMessage(id: MessageId) -> Message? { for item in self.mediaItems { - if item.message.id == id { + if let message = item.message, message.id == id { return item.message } } @@ -1003,8 +981,8 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { for item in self.mediaItems { - if item.message.id == messageId { - if let itemNode = self.visibleMediaItems[item.message.stableId] { + if let message = item.message, message.id == messageId { + if let itemNode = self.visibleMediaItems[item.stableId] { return itemNode.transitionNode() } break @@ -1039,14 +1017,15 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro switch self.contentType { case .photoOrVideo, .gifs: itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, itemCount: self.mediaItems.count, bottomInset: bottomInset)) - /*case .gifs: - itemsLayout = .balanced(ItemsLayout.Balanced(containerWidth: availableWidth, items: self.mediaItems, bottomInset: bottomInset))*/ } self.itemsLayout = itemsLayout } self.scrollNode.view.contentSize = CGSize(width: size.width, height: itemsLayout.contentHeight) self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: synchronous) + + transition.updateFrame(node: self.scrollingArea, frame: CGRect(origin: CGPoint(), size: size)) + self.updateScrollingArea(transition: transition) if isScrollingLockedAtTop { if self.scrollNode.view.contentOffset.y > .ulpOfOne { @@ -1073,12 +1052,14 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro if let (size, sideInset, bottomInset, visibleHeight, _, _, presentationData) = self.currentParams { self.updateVisibleItems(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, theme: presentationData.theme, strings: presentationData.strings, synchronousLoad: false) - if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0, let currentView = self.currentView, currentView.earlierId != nil { + /*if scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.bounds.height * 2.0, let currentView = self.currentView, currentView.earlierId != nil { if !self.isRequestingView { self.numberOfItemsToRequest += 50 self.requestHistoryAroundVisiblePosition() } - } + }*/ + + self.updateScrollingArea(transition: .immediate) } } @@ -1091,12 +1072,61 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.resetHeaderFlashTimer(start: true) self.updateHeaderFlashing(animated: true) } + + if !decelerate { + self.updateScrollingArea(transition: .animated(duration: 0.3, curve: .easeInOut)) + } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.isDeceleratingAfterTracking = false self.resetHeaderFlashTimer(start: true) self.updateHeaderFlashing(animated: true) + + self.updateScrollingArea(transition: .animated(duration: 0.3, curve: .easeInOut)) + } + + private func updateScrollingArea(transition: ContainedViewLayoutTransition) { + guard let currentParams = self.currentParams, let itemsLayout = self.itemsLayout else { + return + } + + let headerItemMinY = self.scrollNode.view.bounds.minY + 20.0 + let activeRect = self.scrollNode.view.bounds + let visibleRect = activeRect.insetBy(dx: 0.0, dy: -400.0) + + let (minVisibleIndex, maxVisibleIndex) = itemsLayout.visibleRange(rect: visibleRect) + + var headerItem: Int32? + + if minVisibleIndex <= maxVisibleIndex { + for i in minVisibleIndex ... maxVisibleIndex { + let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: currentParams.sideInset) + + if headerItem == nil && itemFrame.maxY > headerItemMinY { + headerItem = self.mediaItems[i].timestamp + break + } + } + } + + var dateString: String = "" + + if let headerItem = headerItem { + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + let (year, month) = listMessageDateHeaderInfo(timestamp: headerItem) + dateString = stringForMonth(strings: presentationData.strings, month: month, ofYear: year) + } + + self.scrollingArea.update( + containerSize: currentParams.size, + containerInsets: UIEdgeInsets(top: 0.0, left: currentParams.sideInset, bottom: currentParams.bottomInset, right: currentParams.sideInset), + contentHeight: itemsLayout.contentHeight, + contentOffset: self.scrollNode.view.contentOffset.y, + isScrolling: self.scrollNode.view.isDragging || self.scrollNode.view.isDecelerating, + dateString: dateString, + transition: transition + ) } private func updateVisibleItems(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, theme: PresentationTheme, strings: PresentationStrings, synchronousLoad: Bool) { @@ -1104,19 +1134,24 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro return } - let headerItemMinY = self.scrollNode.view.bounds.minY + 20.0 let activeRect = self.scrollNode.view.bounds let visibleRect = activeRect.insetBy(dx: 0.0, dy: -400.0) - + + let (minActuallyVisibleIndex, maxActuallyVisibleIndex) = itemsLayout.visibleRange(rect: activeRect) let (minVisibleIndex, maxVisibleIndex) = itemsLayout.visibleRange(rect: visibleRect) + + var requestPlaceholderIds: [MessageId] = [] - var headerItem: Message? - - var validIds = Set() + var validIds = Set() if minVisibleIndex <= maxVisibleIndex { for i in minVisibleIndex ... maxVisibleIndex { - let stableId = self.mediaItems[i].message.stableId + let stableId = self.mediaItems[i].stableId validIds.insert(stableId) + + if self.mediaItems[i].message == nil && !self.requestedPlaceholderIds.contains(self.mediaItems[i].id) { + requestPlaceholderIds.append(self.mediaItems[i].id) + self.requestedPlaceholderIds.insert(self.mediaItems[i].id) + } let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset) @@ -1129,18 +1164,16 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.scrollNode.addSubnode(itemNode) } itemNode.frame = itemFrame - if headerItem == nil && itemFrame.maxY > headerItemMinY { - headerItem = self.mediaItems[i].message - } + var itemSynchronousLoad = false - if itemFrame.maxY <= visibleHeight { + if i >= minActuallyVisibleIndex && i <= maxActuallyVisibleIndex { itemSynchronousLoad = synchronousLoad } itemNode.update(size: itemFrame.size, item: self.mediaItems[i], theme: theme, synchronousLoad: itemSynchronousLoad) itemNode.updateIsVisible(itemFrame.intersects(activeRect)) } } - var removeKeys: [UInt32] = [] + var removeKeys: [VisualMediaItem.StableId] = [] for (id, _) in self.visibleMediaItems { if !validIds.contains(id) { removeKeys.append(id) @@ -1152,18 +1185,13 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro } } - if let headerItem = headerItem { - let (year, month) = listMessageDateHeaderInfo(timestamp: headerItem.timestamp) - let headerSize = self.floatingHeaderNode.update(constrainedWidth: size.width, year: year, month: month, theme: theme, strings: strings) - self.floatingHeaderNode.frame = CGRect(origin: CGPoint(x: floor((size.width - headerSize.width) / 2.0), y: 7.0), size: headerSize) - self.floatingHeaderNode.isHidden = false - } else { - self.floatingHeaderNode.isHidden = true + if !requestPlaceholderIds.isEmpty { + self.listSource.loadPlaceholders(ids: requestPlaceholderIds) } } private func resetHeaderFlashTimer(start: Bool, duration: Double = 0.3) { - if let flashHeaderDelayTimer = self.flashHeaderDelayTimer { + /*if let flashHeaderDelayTimer = self.flashHeaderDelayTimer { flashHeaderDelayTimer.invalidate() self.flashHeaderDelayTimer = nil } @@ -1194,15 +1222,16 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro self.flashHeaderDelayTimer = timer RunLoop.main.add(timer, forMode: RunLoop.Mode.common) self.updateHeaderFlashing(animated: true) - } + }*/ } private func headerIsFlashing() -> Bool { - return self.scrollNode.view.isDragging || self.isDeceleratingAfterTracking || self.flashHeaderDelayTimer != nil + return false + //return self.scrollNode.view.isDragging || self.isDeceleratingAfterTracking || self.flashHeaderDelayTimer != nil } private func updateHeaderFlashing(animated: Bool) { - let flashing = self.headerIsFlashing() + /*let flashing = self.headerIsFlashing() let alpha: CGFloat = flashing ? 1.0 : 0.0 let previousAlpha = self.floatingHeaderNode.alpha @@ -1212,7 +1241,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro let duration: Double = flashing ? 0.3 : 0.4 self.floatingHeaderNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration) } - } + }*/ } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -1228,107 +1257,3 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro return result } } - -private func calculateItemFrames(items: [VisualMediaItem], containerWidth: CGFloat) -> [CGRect] { - var frames: [CGRect] = [] - - var rowsCount = 0 - var firstRowMax = 0; - - let viewPortAvailableSize = containerWidth - - let preferredRowSize: CGFloat = 100.0 - let itemsCount = items.count - let spanCount: CGFloat = 100.0 - var spanLeft = spanCount - var currentItemsInRow = 0 - var currentItemsSpanAmount: CGFloat = 0.0 - - var itemSpans: [Int: CGFloat] = [:] - var itemsToRow: [Int: Int] = [:] - - for a in 0 ..< itemsCount { - var size: CGSize = items[a].dimensions - if size.width <= 0.0 { - size.width = 100.0 - } - if size.height <= 0.0 { - size.height = 100.0 - } - let aspect: CGFloat = size.width / size.height - if aspect > 4.0 || aspect < 0.2 { - size.width = max(size.width, size.height) - size.height = size.width - } - - var requiredSpan = min(spanCount, floor(spanCount * (size.width / size.height * preferredRowSize / viewPortAvailableSize))) - let moveToNewRow = spanLeft < requiredSpan || requiredSpan > 33.0 && spanLeft < requiredSpan - 15.0 - if moveToNewRow { - if spanLeft > 0 { - let spanPerItem = floor(spanLeft / CGFloat(currentItemsInRow)) - - let start = a - currentItemsInRow - var b = start - while b < start + currentItemsInRow { - if (b == start + currentItemsInRow - 1) { - itemSpans[b] = itemSpans[b]! + spanLeft - } else { - itemSpans[b] = itemSpans[b]! + spanPerItem - } - spanLeft -= spanPerItem; - - b += 1 - } - - itemsToRow[a - 1] = rowsCount - } - rowsCount += 1 - currentItemsSpanAmount = 0 - currentItemsInRow = 0 - spanLeft = spanCount - } else { - if spanLeft < requiredSpan { - requiredSpan = spanLeft - } - } - if rowsCount == 0 { - firstRowMax = max(firstRowMax, a) - } - if a == itemsCount - 1 { - itemsToRow[a] = rowsCount - } - currentItemsSpanAmount += requiredSpan - currentItemsInRow += 1 - spanLeft -= requiredSpan - spanLeft = max(0, spanLeft) - - itemSpans[a] = requiredSpan - } - if itemsCount != 0 { - rowsCount += 1 - } - - var verticalOffset: CGFloat = 1.0 - - var currentRowHorizontalOffset: CGFloat = 0.0 - for index in 0 ..< items.count { - guard let width = itemSpans[index] else { - continue - } - let itemWidth = floor(width * containerWidth / 100.0) - 1 - - var itemSize = CGSize(width: itemWidth, height: preferredRowSize) - if itemsToRow[index] != nil && currentRowHorizontalOffset + itemSize.width >= containerWidth - 10.0 { - itemSize.width = max(itemSize.width, containerWidth - currentRowHorizontalOffset) - } - frames.append(CGRect(origin: CGPoint(x: currentRowHorizontalOffset, y: verticalOffset), size: itemSize)) - currentRowHorizontalOffset += itemSize.width + 1.0 - - if itemsToRow[index] != nil { - verticalOffset += preferredRowSize + 1.0 - currentRowHorizontalOffset = 0.0 - } - } - - return frames -}