mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-19 04:00:54 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
56633f8f35
@ -5483,3 +5483,5 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"CreatePoll.ExplanationHeader" = "EXPLANATION";
|
||||
"CreatePoll.Explanation" = "Add a Comment (Optional)";
|
||||
"CreatePoll.ExplanationInfo" = "Users will see this comment after choosing a wrong answer, good for educational purposes.";
|
||||
|
||||
"FeaturedStickers.OtherSection" = "OTHER STICKERS";
|
||||
|
@ -1 +1 @@
|
||||
11.4
|
||||
11.4.1
|
||||
|
@ -146,6 +146,8 @@ API_AVAILABLE(ios(10))
|
||||
dataDict[@"device_token"] = [appToken base64EncodedStringWithOptions:0];
|
||||
dataDict[@"device_token_type"] = @"voip";
|
||||
}
|
||||
float tzOffset = ([[NSTimeZone systemTimeZone] secondsFromGMT] / 3600.0);
|
||||
dataDict[@"tz_offset"] = @((int)tzOffset);
|
||||
if (signatureDict != nil) {
|
||||
for (id<NSCopying> key in signatureDict.allKeys) {
|
||||
dataDict[key] = signatureDict[key];
|
||||
|
@ -183,7 +183,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
|
||||
if let dimensions = file.dimensions {
|
||||
pixelsCount = Int(dimensions.width) * Int(dimensions.height)
|
||||
}
|
||||
if (file.size == nil || file.size! < 4 * 1024 * 1024) && pixelsCount < 4096 * 4096 {
|
||||
if true /*(file.size == nil || file.size! < 4 * 1024 * 1024) && pixelsCount < 4096 * 4096*/ {
|
||||
return ChatImageGalleryItem(context: context, presentationData: presentationData, message: message, location: location, performAction: performAction, openActionOptions: openActionOptions)
|
||||
} else {
|
||||
return ChatDocumentGalleryItem(context: context, presentationData: presentationData, message: message, location: location)
|
||||
|
@ -162,6 +162,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private var message: Message?
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
private var tilingNode: TilingNode?
|
||||
fileprivate let _ready = Promise<Void>()
|
||||
fileprivate let _title = Promise<String>()
|
||||
fileprivate let _rightBarButtonItem = Promise<UIBarButtonItem?>(nil)
|
||||
@ -173,6 +174,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
private var fetchDisposable = MetaDisposable()
|
||||
private let statusDisposable = MetaDisposable()
|
||||
private let dataDisposable = MetaDisposable()
|
||||
private var status: MediaResourceStatus?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void) {
|
||||
@ -208,6 +210,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
deinit {
|
||||
//self.fetchDisposable.dispose()
|
||||
self.statusDisposable.dispose()
|
||||
self.dataDisposable.dispose()
|
||||
}
|
||||
|
||||
override func ready() -> Signal<Void, NoError> {
|
||||
@ -247,6 +250,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
return generate
|
||||
}
|
||||
self.imageNode.setSignal(signal)
|
||||
|
||||
self.zoomableContent = (largestSize.dimensions.cgSize, self.imageNode)
|
||||
|
||||
self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, reference: imageReference.resourceReference(largestSize.resource)).start())
|
||||
@ -262,6 +266,38 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self.contextAndMedia = (self.context, imageReference.abstract)
|
||||
}
|
||||
|
||||
private func updateImageFromFile(path: String) {
|
||||
if let _ = self.tilingNode {
|
||||
self.tilingNode = nil
|
||||
}
|
||||
|
||||
guard let dataProvider = CGDataProvider(url: URL(fileURLWithPath: path) as CFURL) else {
|
||||
self._ready.set(.single(Void()))
|
||||
return
|
||||
}
|
||||
|
||||
var maybeImage: CGImage?
|
||||
|
||||
if let image = CGImage(jpegDataProviderSource: dataProvider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) {
|
||||
maybeImage = image
|
||||
} else if let image = CGImage(pngDataProviderSource: dataProvider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) {
|
||||
maybeImage = image
|
||||
}
|
||||
|
||||
guard let image = maybeImage else {
|
||||
self._ready.set(.single(Void()))
|
||||
return
|
||||
}
|
||||
|
||||
let tilingNode = TilingNode(image: image, path: path)
|
||||
self.tilingNode = tilingNode
|
||||
|
||||
let size = CGSize(width: image.width, height: image.height)
|
||||
self.zoomableContent = (size, tilingNode)
|
||||
|
||||
self._ready.set(.single(Void()))
|
||||
}
|
||||
|
||||
@objc func openStickersButtonPressed() {
|
||||
guard let (context, media) = self.contextAndMedia else {
|
||||
return
|
||||
@ -314,7 +350,22 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))()
|
||||
self.imageNode.setSignal(chatMessageImageFile(account: context.account, fileReference: fileReference, thumbnail: false), dispatchOnDisplayLink: false)
|
||||
|
||||
if largestSize.width > 2600 || largestSize.height > 2600 {
|
||||
self.dataDisposable.set((self.context.account.postbox.mediaBox.resourceData(fileReference.media.resource)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if !data.complete {
|
||||
return
|
||||
}
|
||||
strongSelf.updateImageFromFile(path: data.path)
|
||||
}))
|
||||
} else {
|
||||
self.imageNode.setSignal(chatMessageImageFile(account: context.account, fileReference: fileReference, thumbnail: false), dispatchOnDisplayLink: false)
|
||||
}
|
||||
|
||||
self.zoomableContent = (largestSize.cgSize, self.imageNode)
|
||||
self.setupStatus(resource: fileReference.media.resource)
|
||||
} else {
|
||||
@ -370,16 +421,18 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view)
|
||||
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view.superview)
|
||||
let contentNode = self.tilingNode ?? self.imageNode
|
||||
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: contentNode.view)
|
||||
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: contentNode.view.superview)
|
||||
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
||||
|
||||
/*let projectedScale = CGPoint(x: self.imageNode.view.bounds.width / node.1.width, y: self.imageNode.view.bounds.height / node.1.height)
|
||||
/*let projectedScale = CGPoint(x: contentNode.view.bounds.width / node.1.width, y: contentNode.view.bounds.height / node.1.height)
|
||||
let scaledLocalImageViewBounds = CGRect(x: -node.1.minX * projectedScale.x, y: -node.1.minY * projectedScale.y, width: node.0.bounds.width * projectedScale.x, height: node.0.bounds.height * projectedScale.y)*/
|
||||
|
||||
let scaledLocalImageViewBounds = self.imageNode.view.bounds
|
||||
let scaledLocalImageViewBounds = contentNode.view.bounds
|
||||
|
||||
let transformedCopyViewFinalFrame = self.imageNode.view.convert(scaledLocalImageViewBounds, to: self.view)
|
||||
let transformedCopyViewFinalFrame = contentNode.view.convert(scaledLocalImageViewBounds, to: self.view)
|
||||
|
||||
let (maybeSurfaceCopyView, _) = node.2()
|
||||
let (maybeCopyView, copyViewBackgrond) = node.2()
|
||||
@ -393,7 +446,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
var transformedSurfaceFinalFrame: CGRect?
|
||||
if let contentSurface = surfaceCopyView.superview {
|
||||
transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface)
|
||||
transformedSurfaceFinalFrame = self.imageNode.view.convert(scaledLocalImageViewBounds, to: contentSurface)
|
||||
transformedSurfaceFinalFrame = contentNode.view.convert(scaledLocalImageViewBounds, to: contentSurface)
|
||||
}
|
||||
|
||||
if let transformedSurfaceFrame = transformedSurfaceFrame {
|
||||
@ -423,11 +476,11 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
self.imageNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.imageNode.layer.position, duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
contentNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: contentNode.layer.position, duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
self.imageNode.layer.animateBounds(from: transformedFrame, to: self.imageNode.layer.bounds, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
contentNode.layer.animateBounds(from: transformedFrame, to: contentNode.layer.bounds, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
self.statusNodeContainer.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.statusNodeContainer.position, duration: positionDuration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.statusNodeContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
@ -437,10 +490,12 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
override func animateOut(to node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
|
||||
self.fetchDisposable.set(nil)
|
||||
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view)
|
||||
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view.superview)
|
||||
let contentNode = self.tilingNode ?? self.imageNode
|
||||
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: contentNode.view)
|
||||
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: contentNode.view.superview)
|
||||
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
||||
let transformedCopyViewInitialFrame = self.imageNode.view.convert(self.imageNode.view.bounds, to: self.view)
|
||||
let transformedCopyViewInitialFrame = contentNode.view.convert(contentNode.view.bounds, to: self.view)
|
||||
|
||||
var positionCompleted = false
|
||||
var boundsCompleted = false
|
||||
@ -458,7 +513,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
var transformedSurfaceCopyViewInitialFrame: CGRect?
|
||||
if let contentSurface = surfaceCopyView.superview {
|
||||
transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface)
|
||||
transformedSurfaceCopyViewInitialFrame = self.imageNode.view.convert(self.imageNode.view.bounds, to: contentSurface)
|
||||
transformedSurfaceCopyViewInitialFrame = contentNode.view.convert(contentNode.view.bounds, to: contentSurface)
|
||||
}
|
||||
|
||||
self.view.insertSubview(copyView, belowSubview: self.scrollNode.view)
|
||||
@ -488,15 +543,15 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
self.imageNode.layer.animatePosition(from: self.imageNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
contentNode.layer.animatePosition(from: contentNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
positionCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
self.imageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.08, removeOnCompletion: false)
|
||||
contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.08, removeOnCompletion: false)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
self.imageNode.layer.animateBounds(from: self.imageNode.layer.bounds, to: transformedFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
contentNode.layer.animateBounds(from: contentNode.layer.bounds, to: transformedFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
boundsCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
@ -552,3 +607,232 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*private func tileRectForImage(_ mappedImage: CGImage, rect: CGRect) -> CGRect {
|
||||
let scaleX = CGFloat(mappedImage.width) / lowResolutionImage.size.width
|
||||
let scaleY = CGFloat(mappedImage.height) / lowResolutionImage.size.height
|
||||
|
||||
let mappedX = rect.minX * scaleX
|
||||
let mappedY = rect.minY * scaleY
|
||||
let mappedWidth = rect.width * scaleX
|
||||
let mappedHeight = rect.height * scaleY
|
||||
|
||||
return CGRect(x: mappedX, y: mappedY, width: mappedWidth, height: mappedHeight)
|
||||
}*/
|
||||
|
||||
private func zoomScale(for zoomLevel: CGFloat) -> CGFloat {
|
||||
return pow(2.0, zoomLevel - 1.0)
|
||||
}
|
||||
|
||||
private func zoomLevel(for zoomScale: CGFloat) -> CGFloat {
|
||||
return log2(zoomScale) + 1.0
|
||||
}
|
||||
|
||||
private final class TilingLayer: CATiledLayer {
|
||||
override var contentsScale: CGFloat {
|
||||
get {
|
||||
return super.contentsScale
|
||||
} set(value) {
|
||||
super.contentsScale = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TilingView: UIView {
|
||||
let image: CGImage
|
||||
let path: String
|
||||
var cachedScaledImage: UIImage?
|
||||
|
||||
let imageSize: CGSize
|
||||
let tileSize: CGSize
|
||||
var normalizedSize: CGSize?
|
||||
|
||||
override static var layerClass: AnyClass {
|
||||
return TilingLayer.self
|
||||
}
|
||||
|
||||
private var tiledLayer: TilingLayer {
|
||||
return self.layer as! TilingLayer
|
||||
}
|
||||
|
||||
init(image: CGImage, path: String) {
|
||||
self.image = image
|
||||
self.path = path
|
||||
|
||||
self.tileSize = CGSize(width: 256.0, height: 256.0)
|
||||
self.imageSize = CGSize(width: image.width, height: image.height)
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.tiledLayer.contentsScale = UIScreenScale
|
||||
let scale = self.tiledLayer.contentsScale
|
||||
self.tiledLayer.tileSize = CGSize(width: self.tileSize.width * scale, height: self.tileSize.height * scale)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return self.imageSize
|
||||
}
|
||||
|
||||
func setMaximumZoomScale(_ value: CGFloat, normalizedSize: CGSize) {
|
||||
if self.normalizedSize != normalizedSize {
|
||||
self.normalizedSize = normalizedSize
|
||||
|
||||
/*let options: [CFString: Any] = [
|
||||
kCGImageSourceCreateThumbnailFromImageIfAbsent: true,
|
||||
kCGImageSourceCreateThumbnailWithTransform: true,
|
||||
kCGImageSourceShouldCacheImmediately: false,
|
||||
kCGImageSourceThumbnailMaxPixelSize: Int(max(self.imageSize.width / 4.0, self.imageSize.height / 4.0))
|
||||
]
|
||||
|
||||
let startTime = CACurrentMediaTime()
|
||||
if let imageSource = CGImageSourceCreateWithURL(URL(fileURLWithPath: self.path) as CFURL, nil), let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
|
||||
self.cachedScaledImage = UIImage(cgImage: image)
|
||||
}
|
||||
print("create thumbnail: \((CACurrentMediaTime() - startTime) * 1000.0) ms")*/
|
||||
}
|
||||
|
||||
let levels = max(1, Int(zoomLevel(for: value)))
|
||||
self.tiledLayer.levelsOfDetail = levels
|
||||
self.tiledLayer.levelsOfDetailBias = levels - 1
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
guard let normalizedSize = self.normalizedSize else {
|
||||
return
|
||||
}
|
||||
guard let context = UIGraphicsGetCurrentContext() else {
|
||||
return
|
||||
}
|
||||
let image = self.image
|
||||
let cachedScaledImage = self.cachedScaledImage
|
||||
let imageSize = self.imageSize
|
||||
context.setBlendMode(.copy)
|
||||
|
||||
let contentScale = context.ctm.a
|
||||
let normalizedContentScale = contentScale / UIScreenScale
|
||||
|
||||
let normalizedRect = rect
|
||||
|
||||
let normalizationScale = imageSize.width / normalizedSize.width
|
||||
|
||||
let normalizedCroppingRect = CGRect(origin: CGPoint(x: normalizedRect.minX * normalizationScale, y: normalizedRect.minY * normalizationScale), size: CGSize(width: normalizedRect.width * normalizationScale, height: normalizedRect.height * normalizationScale))
|
||||
|
||||
let tileSizes: [CGFloat] = [
|
||||
//8192.0,
|
||||
//4096.0,
|
||||
2048.0,
|
||||
1024.0,
|
||||
512.0,
|
||||
256.0
|
||||
]
|
||||
|
||||
var maximumTileSize: CGFloat = tileSizes[0]
|
||||
for i in (0 ..< tileSizes.count).reversed() {
|
||||
if tileSizes[i] > normalizedCroppingRect.width {
|
||||
break
|
||||
}
|
||||
maximumTileSize = tileSizes[i]
|
||||
}
|
||||
|
||||
let xMinTile = Int(floor(normalizedCroppingRect.minX / maximumTileSize))
|
||||
let xMaxTile = Int(ceil(normalizedCroppingRect.maxX / maximumTileSize))
|
||||
let yMinTile = Int(floor(normalizedCroppingRect.minY / maximumTileSize))
|
||||
let yMaxTile = Int(ceil(normalizedCroppingRect.maxY / maximumTileSize))
|
||||
|
||||
for y in yMinTile ... yMaxTile {
|
||||
let imageMinY = floor(CGFloat(y) * maximumTileSize)
|
||||
var imageMaxY = ceil(CGFloat(y + 1) * maximumTileSize)
|
||||
imageMaxY = min(imageMaxY, imageSize.height)
|
||||
if imageMaxY <= imageMinY {
|
||||
continue
|
||||
}
|
||||
|
||||
for x in xMinTile ... xMaxTile {
|
||||
let imageMinX = floor(CGFloat(x) * maximumTileSize)
|
||||
var imageMaxX = ceil(CGFloat(x + 1) * maximumTileSize)
|
||||
imageMaxX = min(imageMaxX, imageSize.width)
|
||||
if imageMaxX <= imageMinX {
|
||||
continue
|
||||
}
|
||||
|
||||
let imageRect = CGRect(origin: CGPoint(x: imageMinX, y: imageMinY), size: CGSize(width: imageMaxX - imageMinX, height: imageMaxY - imageMinY))
|
||||
|
||||
let drawingRect = CGRect(origin: CGPoint(x: imageRect.minX / normalizationScale, y: imageRect.minY / normalizationScale), size: CGSize(width: imageRect.width / normalizationScale, height: imageRect.height / normalizationScale))
|
||||
|
||||
var tileImage: CGImage?
|
||||
if normalizedContentScale < 1.1, let cachedScaledImage = cachedScaledImage {
|
||||
let cachedScale = cachedScaledImage.size.width / self.imageSize.width
|
||||
let scaledImageRect = CGRect(origin: CGPoint(x: imageRect.minX * cachedScale, y: imageRect.minY * cachedScale), size: CGSize(width: imageRect.width * cachedScale, height: imageRect.height * cachedScale))
|
||||
tileImage = cachedScaledImage.cgImage!.cropping(to: scaledImageRect)
|
||||
} else {
|
||||
tileImage = image.cropping(to: imageRect)
|
||||
}
|
||||
|
||||
if let tileImage = tileImage {
|
||||
var scaledSide = max(imageRect.width, imageRect.height)
|
||||
let targetSide = max(drawingRect.width, drawingRect.height)
|
||||
while true {
|
||||
let maybeSide = round(scaledSide * 0.5)
|
||||
if maybeSide < targetSide {
|
||||
break
|
||||
} else {
|
||||
scaledSide = maybeSide
|
||||
}
|
||||
}
|
||||
|
||||
/*let scaledSize = imageRect.size.fitted(CGSize(width: scaledSide, height: scaledSide))
|
||||
let scaledImage = generateImage(scaledSize, contextGenerator: { size, scaledContext in
|
||||
scaledContext.setBlendMode(.copy)
|
||||
let startTime = CACurrentMediaTime()
|
||||
scaledContext.draw(tileImage, in: CGRect(origin: CGPoint(), size: size))
|
||||
print("draw scaled: \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||
}, opaque: true, scale: UIScreenScale)
|
||||
|
||||
if let scaledImage = scaledImage {
|
||||
scaledImage.draw(in: drawingRect.insetBy(dx: -0.5, dy: -0.5), blendMode: .copy, alpha: 1.0)
|
||||
}*/
|
||||
|
||||
let startTime = CACurrentMediaTime()
|
||||
context.translateBy(x: drawingRect.midX, y: drawingRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -drawingRect.midX, y: -drawingRect.midY)
|
||||
context.draw(tileImage, in: drawingRect.insetBy(dx: -0.5, dy: -0.5))
|
||||
context.translateBy(x: drawingRect.midX, y: drawingRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -drawingRect.midX, y: -drawingRect.midY)
|
||||
print("draw direct: \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func annotate(rect: CGRect, col: Int, row: Int, zoomLevel: CGFloat, scale: CGFloat, context: CGContext) {
|
||||
let lineWidth = 2.0 / scale
|
||||
let halfLineWidth = lineWidth / 2.0
|
||||
//let fontSize = 12.0 / scale
|
||||
|
||||
//NSString *pointString = [NSString stringWithFormat:@"%@x(%@, %@) @%@x", @(zoomLevel), @(col), @(row), @([UIScreen mainScreen].scale)];
|
||||
//CGPoint textOrigin = CGPointMake(CGRectGetMinX(rect) + lineWidth, CGRectGetMinY(rect) + lineWidth);
|
||||
/*[pointString drawAtPoint:textOrigin withAttributes:@{
|
||||
NSFontAttributeName: [UIFont boldSystemFontOfSize:fontSize],
|
||||
NSForegroundColorAttributeName: [UIColor darkGrayColor]
|
||||
}];*/
|
||||
context.setFillColor(UIColor.red.cgColor)
|
||||
context.setLineWidth(lineWidth)
|
||||
context.stroke(rect.insetBy(dx: halfLineWidth, dy: halfLineWidth))
|
||||
}
|
||||
}
|
||||
|
||||
private final class TilingNode: ASDisplayNode {
|
||||
init(image: CGImage, path: String) {
|
||||
super.init()
|
||||
|
||||
self.setViewBlock {
|
||||
return TilingView(image: image, path: path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,16 +137,29 @@ open class ZoomableContentGalleryItemNode: GalleryItemNode, UIScrollViewDelegate
|
||||
return
|
||||
}
|
||||
|
||||
let boundsSize = self.scrollNode.view.bounds.size
|
||||
if contentSize.width.isLessThanOrEqualTo(0.0) || contentSize.height.isLessThanOrEqualTo(0.0) || boundsSize.width.isLessThanOrEqualTo(0.0) || boundsSize.height.isLessThanOrEqualTo(0.0) {
|
||||
return
|
||||
}
|
||||
|
||||
let normalizedContentSize = contentSize.fitted(boundsSize)
|
||||
|
||||
self.ignoreZoom = true
|
||||
self.ignoreZoomTransition = transition
|
||||
self.scrollNode.view.minimumZoomScale = 1.0
|
||||
self.scrollNode.view.maximumZoomScale = 1.0
|
||||
//self.scrollView.normalZoomScale = 1.0
|
||||
self.scrollNode.view.zoomScale = 1.0
|
||||
self.scrollNode.view.contentSize = contentSize
|
||||
|
||||
contentNode.transform = CATransform3DIdentity
|
||||
contentNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
||||
if contentNode.view is TilingView {
|
||||
contentNode.frame = CGRect(origin: CGPoint(), size: normalizedContentSize)
|
||||
self.scrollNode.view.contentSize = normalizedContentSize
|
||||
contentNode.transform = CATransform3DIdentity
|
||||
} else {
|
||||
self.scrollNode.view.contentSize = contentSize
|
||||
contentNode.transform = CATransform3DIdentity
|
||||
contentNode.frame = CGRect(origin: CGPoint(), size: contentSize)
|
||||
}
|
||||
|
||||
self.centerScrollViewContents(transition: transition)
|
||||
self.ignoreZoom = false
|
||||
@ -165,26 +178,54 @@ open class ZoomableContentGalleryItemNode: GalleryItemNode, UIScrollViewDelegate
|
||||
return
|
||||
}
|
||||
|
||||
let scaleWidth = boundsSize.width / contentSize.width
|
||||
let scaleHeight = boundsSize.height / contentSize.height
|
||||
let minScale = min(scaleWidth, scaleHeight)
|
||||
var maxScale = max(scaleWidth, scaleHeight)
|
||||
maxScale = max(maxScale, minScale * 3.0)
|
||||
var minScale: CGFloat
|
||||
var maxScale: CGFloat
|
||||
|
||||
if (abs(maxScale - minScale) < 0.01) {
|
||||
maxScale = minScale
|
||||
}
|
||||
if contentNode.view is TilingView {
|
||||
let normalizedContentSize = contentSize.fitted(boundsSize)
|
||||
|
||||
if !self.scrollNode.view.minimumZoomScale.isEqual(to: minScale) {
|
||||
self.scrollNode.view.minimumZoomScale = minScale
|
||||
}
|
||||
let scaleWidth = boundsSize.width / normalizedContentSize.width
|
||||
let scaleHeight = boundsSize.height / normalizedContentSize.height
|
||||
minScale = min(scaleWidth, scaleHeight)
|
||||
minScale = 1.0
|
||||
|
||||
/*if !self.scrollView.normalZoomScale.isEqual(to: minScale) {
|
||||
self.scrollView.normalZoomScale = minScale
|
||||
}*/
|
||||
maxScale = max(scaleWidth, scaleHeight)
|
||||
maxScale = max(maxScale, minScale * 4.0)
|
||||
|
||||
if !self.scrollNode.view.maximumZoomScale.isEqual(to: maxScale) {
|
||||
self.scrollNode.view.maximumZoomScale = maxScale
|
||||
if (abs(maxScale - minScale) < 0.01) {
|
||||
maxScale = minScale
|
||||
}
|
||||
|
||||
if !self.scrollNode.view.minimumZoomScale.isEqual(to: minScale) {
|
||||
self.scrollNode.view.minimumZoomScale = minScale
|
||||
}
|
||||
|
||||
if !self.scrollNode.view.maximumZoomScale.isEqual(to: maxScale) {
|
||||
self.scrollNode.view.maximumZoomScale = maxScale
|
||||
}
|
||||
|
||||
if let contentView = contentNode.view as? TilingView {
|
||||
contentView.setMaximumZoomScale(maxScale, normalizedSize: normalizedContentSize)
|
||||
}
|
||||
} else {
|
||||
let scaleWidth = boundsSize.width / contentSize.width
|
||||
let scaleHeight = boundsSize.height / contentSize.height
|
||||
let minScale = min(scaleWidth, scaleHeight)
|
||||
|
||||
maxScale = max(scaleWidth, scaleHeight)
|
||||
maxScale = max(maxScale, minScale * 3.0)
|
||||
|
||||
if (abs(maxScale - minScale) < 0.01) {
|
||||
maxScale = minScale
|
||||
}
|
||||
|
||||
if !self.scrollNode.view.minimumZoomScale.isEqual(to: minScale) {
|
||||
self.scrollNode.view.minimumZoomScale = minScale
|
||||
}
|
||||
|
||||
if !self.scrollNode.view.maximumZoomScale.isEqual(to: maxScale) {
|
||||
self.scrollNode.view.maximumZoomScale = maxScale
|
||||
}
|
||||
}
|
||||
|
||||
var contentFrame = contentNode.view.frame
|
||||
|
@ -300,9 +300,7 @@
|
||||
[buffer appendInt32:(int32_t)0xda9b0d0d];
|
||||
[buffer appendInt32:(int32_t)[_serialization currentLayer]];
|
||||
|
||||
//initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
|
||||
|
||||
//inputClientProxy address:string port:int = InputClientProxy;
|
||||
//initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
|
||||
|
||||
int32_t flags = 0;
|
||||
if (_apiEnvironment.socksProxySettings.secret != nil) {
|
||||
@ -312,7 +310,7 @@
|
||||
flags |= (1 << 1);
|
||||
}
|
||||
|
||||
[buffer appendInt32:(int32_t)0x785188b8];
|
||||
[buffer appendInt32:(int32_t)0xc1cd5ea9];
|
||||
[buffer appendInt32:flags];
|
||||
[buffer appendInt32:(int32_t)_apiEnvironment.apiId];
|
||||
[buffer appendTLString:_apiEnvironment.deviceModel];
|
||||
|
@ -10,6 +10,11 @@ public final class InteractivePhoneFormatter {
|
||||
public func updateText(_ text: String) -> (String?, String) {
|
||||
self.formatter.clear()
|
||||
let string = self.formatter.inputString(text)
|
||||
return (self.formatter.regionPrefix, string ?? "")
|
||||
|
||||
var regionPrefix = self.formatter.regionPrefix
|
||||
if let string = string, string.hasPrefix("+383") {
|
||||
regionPrefix = "+383"
|
||||
}
|
||||
return (regionPrefix, string ?? "")
|
||||
}
|
||||
}
|
||||
|
@ -2877,6 +2877,7 @@ public final class Postbox {
|
||||
let disposable = MetaDisposable()
|
||||
self.queue.async {
|
||||
disposable.set(self.viewTracker.unsentMessageIdsViewSignal().start(next: { view in
|
||||
postboxLog("unsentMessageIdsView contents: \(view.ids)")
|
||||
subscriber.putNext(view)
|
||||
}))
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ final class UnsentMessageHistoryView {
|
||||
|
||||
func refreshDueToExternalTransaction(fetchUnsentMessageIds: () -> [MessageId]) -> Bool {
|
||||
let ids = Set(fetchUnsentMessageIds())
|
||||
postboxLog("UnsentMessageHistoryView: refreshDueToExternalTransaction: \(ids)")
|
||||
if ids != self.ids {
|
||||
self.ids = ids
|
||||
return true
|
||||
@ -20,6 +21,7 @@ final class UnsentMessageHistoryView {
|
||||
func replay(_ operations: [IntermediateMessageHistoryUnsentOperation]) -> Bool {
|
||||
var updated = false
|
||||
for operation in operations {
|
||||
postboxLog("UnsentMessageHistoryView: operation: \(operation)")
|
||||
switch operation {
|
||||
case let .Insert(id):
|
||||
if !self.ids.contains(id) {
|
||||
|
@ -546,6 +546,7 @@ final class ViewTracker {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.queue.async {
|
||||
postboxLog("unsentMessageIdsViewSignal started with \(self.unsentMessageView.ids)")
|
||||
subscriber.putNext(UnsentMessageIdsView(self.unsentMessageView.ids))
|
||||
|
||||
let pipe = ValuePipe<UnsentMessageIdsView>()
|
||||
|
@ -1019,6 +1019,7 @@ public class Account {
|
||||
self.managedServiceViewsDisposable.set(serviceTasksMaster.start())
|
||||
|
||||
let pendingMessageManager = self.pendingMessageManager
|
||||
Logger.shared.log("Account", "Begin watching unsent message ids")
|
||||
self.managedOperationsDisposable.add(postbox.unsentMessageIdsView().start(next: { [weak pendingMessageManager] view in
|
||||
pendingMessageManager?.updatePendingMessageIds(view.ids)
|
||||
}))
|
||||
|
@ -10,20 +10,24 @@ struct PeerChatInfo {
|
||||
var notificationSettings: PeerNotificationSettings
|
||||
}
|
||||
|
||||
struct AccountStateChannelState: Equatable {
|
||||
var pts: Int32
|
||||
}
|
||||
|
||||
final class AccountInitialState {
|
||||
let state: AuthorizedAccountState.State
|
||||
let peerIds: Set<PeerId>
|
||||
let chatStates: [PeerId: PeerChatState]
|
||||
let channelStates: [PeerId: AccountStateChannelState]
|
||||
let peerChatInfos: [PeerId: PeerChatInfo]
|
||||
let peerIdsRequiringLocalChatState: Set<PeerId>
|
||||
let locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]
|
||||
let cloudReadStates: [PeerId: PeerReadState]
|
||||
let channelsToPollExplicitely: Set<PeerId>
|
||||
|
||||
init(state: AuthorizedAccountState.State, peerIds: Set<PeerId>, peerIdsRequiringLocalChatState: Set<PeerId>, chatStates: [PeerId: PeerChatState], peerChatInfos: [PeerId: PeerChatInfo], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]], cloudReadStates: [PeerId: PeerReadState], channelsToPollExplicitely: Set<PeerId>) {
|
||||
init(state: AuthorizedAccountState.State, peerIds: Set<PeerId>, peerIdsRequiringLocalChatState: Set<PeerId>, channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]], cloudReadStates: [PeerId: PeerReadState], channelsToPollExplicitely: Set<PeerId>) {
|
||||
self.state = state
|
||||
self.peerIds = peerIds
|
||||
self.chatStates = chatStates
|
||||
self.channelStates = channelStates
|
||||
self.peerIdsRequiringLocalChatState = peerIdsRequiringLocalChatState
|
||||
self.peerChatInfos = peerChatInfos
|
||||
self.locallyGeneratedMessageTimestamps = locallyGeneratedMessageTimestamps
|
||||
@ -72,7 +76,9 @@ enum AccountStateMutationOperation {
|
||||
case ResetMessageTagSummary(PeerId, MessageId.Namespace, Int32, MessageHistoryTagNamespaceCountValidityRange)
|
||||
case ReadGroupFeedInbox(PeerGroupId, MessageIndex)
|
||||
case UpdateState(AuthorizedAccountState.State)
|
||||
case UpdateChannelState(PeerId, ChannelState)
|
||||
case UpdateChannelState(PeerId, Int32)
|
||||
case UpdateChannelInvalidationPts(PeerId, Int32)
|
||||
case UpdateChannelSynchronizedUntilMessage(PeerId, MessageId.Id)
|
||||
case UpdateNotificationSettings(AccountStateNotificationSettingsSubject, PeerNotificationSettings)
|
||||
case UpdateGlobalNotificationSettings(AccountStateGlobalNotificationSettingsSubject, MessageNotificationSettings)
|
||||
case MergeApiChats([Api.Chat])
|
||||
@ -110,7 +116,7 @@ struct AccountMutableState {
|
||||
|
||||
var state: AuthorizedAccountState.State
|
||||
var peers: [PeerId: Peer]
|
||||
var chatStates: [PeerId: PeerChatState]
|
||||
var channelStates: [PeerId: AccountStateChannelState]
|
||||
var peerChatInfos: [PeerId: PeerChatInfo]
|
||||
var referencedMessageIds: Set<MessageId>
|
||||
var storedMessages: Set<MessageId>
|
||||
@ -139,7 +145,7 @@ struct AccountMutableState {
|
||||
self.referencedMessageIds = initialReferencedMessageIds
|
||||
self.storedMessages = initialStoredMessages
|
||||
self.readInboxMaxIds = initialReadInboxMaxIds
|
||||
self.chatStates = initialState.chatStates
|
||||
self.channelStates = initialState.channelStates
|
||||
self.peerChatInfos = initialState.peerChatInfos
|
||||
self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp
|
||||
self.branchOperationIndex = 0
|
||||
@ -147,12 +153,12 @@ struct AccountMutableState {
|
||||
self.updatedOutgoingUniqueMessageIds = [:]
|
||||
}
|
||||
|
||||
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], chatStates: [PeerId: PeerChatState], peerChatInfos: [PeerId: PeerChatInfo], referencedMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: Set<MessageId.Namespace>], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], branchOperationIndex: Int) {
|
||||
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: Set<MessageId.Namespace>], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], branchOperationIndex: Int) {
|
||||
self.initialState = initialState
|
||||
self.operations = operations
|
||||
self.state = state
|
||||
self.peers = peers
|
||||
self.chatStates = chatStates
|
||||
self.channelStates = channelStates
|
||||
self.referencedMessageIds = referencedMessageIds
|
||||
self.storedMessages = storedMessages
|
||||
self.peerChatInfos = peerChatInfos
|
||||
@ -165,7 +171,7 @@ struct AccountMutableState {
|
||||
}
|
||||
|
||||
func branch() -> AccountMutableState {
|
||||
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, chatStates: self.chatStates, peerChatInfos: self.peerChatInfos, referencedMessageIds: self.referencedMessageIds, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, branchOperationIndex: self.operations.count)
|
||||
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedMessageIds: self.referencedMessageIds, storedMessages: self.storedMessages, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, branchOperationIndex: self.operations.count)
|
||||
}
|
||||
|
||||
mutating func merge(_ other: AccountMutableState) {
|
||||
@ -269,8 +275,16 @@ struct AccountMutableState {
|
||||
self.addOperation(.UpdateState(state))
|
||||
}
|
||||
|
||||
mutating func updateChannelState(_ peerId: PeerId, state: ChannelState) {
|
||||
self.addOperation(.UpdateChannelState(peerId, state))
|
||||
mutating func updateChannelState(_ peerId: PeerId, pts: Int32) {
|
||||
self.addOperation(.UpdateChannelState(peerId, pts))
|
||||
}
|
||||
|
||||
mutating func updateChannelInvalidationPts(_ peerId: PeerId, invalidationPts: Int32) {
|
||||
self.addOperation(.UpdateChannelInvalidationPts(peerId, invalidationPts))
|
||||
}
|
||||
|
||||
mutating func updateChannelSynchronizedUntilMessage(_ peerId: PeerId, id: MessageId.Id) {
|
||||
self.addOperation(.UpdateChannelSynchronizedUntilMessage(peerId, id))
|
||||
}
|
||||
|
||||
mutating func updateNotificationSettings(_ subject: AccountStateNotificationSettingsSubject, notificationSettings: PeerNotificationSettings) {
|
||||
@ -464,8 +478,12 @@ struct AccountMutableState {
|
||||
}
|
||||
case let .UpdateState(state):
|
||||
self.state = state
|
||||
case let .UpdateChannelState(peerId, channelState):
|
||||
self.chatStates[peerId] = channelState
|
||||
case let .UpdateChannelState(peerId, pts):
|
||||
self.channelStates[peerId] = AccountStateChannelState(pts: pts)
|
||||
case .UpdateChannelInvalidationPts:
|
||||
break
|
||||
case .UpdateChannelSynchronizedUntilMessage:
|
||||
break
|
||||
case let .UpdateNotificationSettings(subject, notificationSettings):
|
||||
if case let .peer(peerId) = subject {
|
||||
if var currentInfo = self.peerChatInfos[peerId] {
|
||||
|
@ -109,6 +109,12 @@ private func peerIdsRequiringLocalChatStateFromUpdateGroups(_ groups: [UpdateGro
|
||||
|
||||
for group in groups {
|
||||
peerIds.formUnion(peerIdsRequiringLocalChatStateFromUpdates(group.updates))
|
||||
switch group {
|
||||
case let .ensurePeerHasLocalState(peerId):
|
||||
peerIds.insert(peerId)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return peerIds
|
||||
@ -350,7 +356,7 @@ private func locallyGeneratedMessageTimestampsFromDifference(_ difference: Api.u
|
||||
|
||||
private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<PeerId>, activeChannelIds: Set<PeerId>, associatedMessageIds: Set<MessageId>, peerIdsRequiringLocalChatState: Set<PeerId>, locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) -> AccountMutableState {
|
||||
var peers: [PeerId: Peer] = [:]
|
||||
var chatStates: [PeerId: PeerChatState] = [:]
|
||||
var channelStates: [PeerId: AccountStateChannelState] = [:]
|
||||
|
||||
var channelsToPollExplicitely = Set<PeerId>()
|
||||
|
||||
@ -361,11 +367,11 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<Pe
|
||||
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
if let channelState = transaction.getPeerChatState(peerId) as? ChannelState {
|
||||
chatStates[peerId] = channelState
|
||||
channelStates[peerId] = AccountStateChannelState(pts: channelState.pts)
|
||||
}
|
||||
} else if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
if let chatState = transaction.getPeerChatState(peerId) as? RegularChatState {
|
||||
chatStates[peerId] = chatState
|
||||
if let _ = transaction.getPeerChatState(peerId) as? RegularChatState {
|
||||
//chatStates[peerId] = chatState
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -428,7 +434,7 @@ private func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<Pe
|
||||
}
|
||||
}
|
||||
|
||||
return AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, chatStates: chatStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedMessageIds: associatedMessageIds, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
|
||||
return AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedMessageIds: associatedMessageIds, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
|
||||
}
|
||||
|
||||
func initialStateWithUpdateGroups(postbox: Postbox, groups: [UpdateGroup]) -> Signal<AccountMutableState, NoError> {
|
||||
@ -766,7 +772,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
case let .updateChannelTooLong(_, channelId, channelPts):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
if !channelsToPoll.contains(peerId) {
|
||||
if let channelPts = channelPts, let channelState = state.chatStates[peerId] as? ChannelState, channelState.pts >= channelPts {
|
||||
if let channelPts = channelPts, let channelState = state.channelStates[peerId], channelState.pts >= channelPts {
|
||||
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip updateChannelTooLong by pts")
|
||||
} else {
|
||||
channelsToPoll.insert(peerId)
|
||||
@ -774,12 +780,12 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
}
|
||||
case let .updateDeleteChannelMessages(channelId, messages, pts: pts, ptsCount):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
if let previousState = updatedState.chatStates[peerId] as? ChannelState {
|
||||
if let previousState = updatedState.channelStates[peerId] {
|
||||
if previousState.pts >= pts {
|
||||
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old delete update")
|
||||
} else if previousState.pts + ptsCount == pts {
|
||||
updatedState.deleteMessages(messages.map({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }))
|
||||
updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts))
|
||||
updatedState.updateChannelState(peerId, pts: pts)
|
||||
} else {
|
||||
if !channelsToPoll.contains(peerId) {
|
||||
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) delete pts hole")
|
||||
@ -796,7 +802,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
case let .updateEditChannelMessage(apiMessage, pts, ptsCount):
|
||||
if let message = StoreMessage(apiMessage: apiMessage), case let .Id(messageId) = message.id {
|
||||
let peerId = messageId.peerId
|
||||
if let previousState = updatedState.chatStates[peerId] as? ChannelState {
|
||||
if let previousState = updatedState.channelStates[peerId] {
|
||||
if previousState.pts >= pts {
|
||||
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old edit update")
|
||||
} else if previousState.pts + ptsCount == pts {
|
||||
@ -808,7 +814,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
var attributes = message.attributes
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
|
||||
updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes))
|
||||
updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts))
|
||||
updatedState.updateChannelState(peerId, pts: pts)
|
||||
} else {
|
||||
if !channelsToPoll.contains(peerId) {
|
||||
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) edit message pts hole")
|
||||
@ -827,7 +833,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
}
|
||||
case let .updateChannelWebPage(channelId, apiWebpage, pts, ptsCount):
|
||||
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
|
||||
if let previousState = updatedState.chatStates[peerId] as? ChannelState {
|
||||
if let previousState = updatedState.channelStates[peerId] {
|
||||
if previousState.pts >= pts {
|
||||
} else if previousState.pts + ptsCount == pts {
|
||||
switch apiWebpage {
|
||||
@ -839,7 +845,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
}
|
||||
}
|
||||
|
||||
updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts))
|
||||
updatedState.updateChannelState(peerId, pts: pts)
|
||||
} else {
|
||||
if !channelsToPoll.contains(peerId) {
|
||||
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) updateWebPage pts hole")
|
||||
@ -874,7 +880,7 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
}
|
||||
case let .updateNewChannelMessage(apiMessage, pts, ptsCount):
|
||||
if let message = StoreMessage(apiMessage: apiMessage) {
|
||||
if let previousState = updatedState.chatStates[message.id.peerId] as? ChannelState {
|
||||
if let previousState = updatedState.channelStates[message.id.peerId] {
|
||||
if previousState.pts >= pts {
|
||||
let messageText: String
|
||||
if Logger.shared.redactSensitiveData {
|
||||
@ -892,7 +898,10 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo
|
||||
var attributes = message.attributes
|
||||
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
|
||||
updatedState.addMessages([message.withUpdatedAttributes(attributes)], location: .UpperHistoryBlock)
|
||||
updatedState.updateChannelState(message.id.peerId, state: previousState.withUpdatedPts(pts))
|
||||
updatedState.updateChannelState(message.id.peerId, pts: pts)
|
||||
if case let .Id(id) = message.id {
|
||||
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
|
||||
}
|
||||
} else {
|
||||
if !channelsToPoll.contains(message.id.peerId) {
|
||||
Logger.shared.log("State", "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) message pts hole")
|
||||
@ -1581,9 +1590,9 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable
|
||||
func keepPollingChannel(postbox: Postbox, network: Network, peerId: PeerId, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||
if let accountState = (transaction.getState() as? AuthorizedAccountState)?.state, let peer = transaction.getPeer(peerId) {
|
||||
var chatStates: [PeerId: PeerChatState] = [:]
|
||||
var channelStates: [PeerId: AccountStateChannelState] = [:]
|
||||
if let channelState = transaction.getPeerChatState(peerId) as? ChannelState {
|
||||
chatStates[peerId] = channelState
|
||||
channelStates[peerId] = AccountStateChannelState(pts: channelState.pts)
|
||||
}
|
||||
let initialPeers: [PeerId: Peer] = [peerId: peer]
|
||||
var peerChatInfos: [PeerId: PeerChatInfo] = [:]
|
||||
@ -1600,7 +1609,7 @@ func keepPollingChannel(postbox: Postbox, network: Network, peerId: PeerId, stat
|
||||
peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings)
|
||||
}
|
||||
}
|
||||
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), chatStates: chatStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
|
||||
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedMessageIds: Set(), initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
|
||||
return pollChannel(network: network, peer: peer, state: initialState)
|
||||
|> mapToSignal { (finalState, _, timeout) -> Signal<Void, NoError> in
|
||||
return resolveAssociatedMessages(network: network, state: finalState)
|
||||
@ -1651,7 +1660,9 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl
|
||||
var storeMessages: [StoreMessage] = []
|
||||
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:]
|
||||
var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:]
|
||||
var channelStates: [PeerId: ChannelState] = [:]
|
||||
var channelStates: [PeerId: AccountStateChannelState] = [:]
|
||||
var invalidateChannelStates: [PeerId: Int32] = [:]
|
||||
var channelSynchronizedUntilMessage: [PeerId: MessageId.Id] = [:]
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
|
||||
if let result = result {
|
||||
@ -1708,7 +1719,8 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl
|
||||
}
|
||||
|
||||
if let apiChannelPts = apiChannelPts {
|
||||
channelStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: apiChannelPts, synchronizedUntilMessageId: nil)
|
||||
channelStates[peerId] = AccountStateChannelState(pts: apiChannelPts)
|
||||
invalidateChannelStates[peerId] = apiChannelPts
|
||||
}
|
||||
|
||||
notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings)
|
||||
@ -1738,6 +1750,7 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl
|
||||
for message in storeMessages {
|
||||
if case let .Id(id) = message.id, id.namespace == Namespaces.Message.Cloud {
|
||||
updatedState.setNeedsHoleFromPreviousState(peerId: id.peerId, namespace: id.namespace)
|
||||
channelSynchronizedUntilMessage[id.peerId] = id.id
|
||||
}
|
||||
}
|
||||
|
||||
@ -1760,7 +1773,13 @@ private func resetChannels(network: Network, peers: [Peer], state: AccountMutabl
|
||||
}
|
||||
|
||||
for (peerId, channelState) in channelStates {
|
||||
updatedState.updateChannelState(peerId, state: channelState)
|
||||
updatedState.updateChannelState(peerId, pts: channelState.pts)
|
||||
}
|
||||
for (peerId, pts) in invalidateChannelStates {
|
||||
updatedState.updateChannelInvalidationPts(peerId, invalidationPts: pts)
|
||||
}
|
||||
for (peerId, id) in channelSynchronizedUntilMessage {
|
||||
updatedState.updateChannelSynchronizedUntilMessage(peerId, id: id)
|
||||
}
|
||||
|
||||
for (peerId, settings) in notificationSettings {
|
||||
@ -1786,7 +1805,7 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
|
||||
#endif
|
||||
|
||||
let pollPts: Int32
|
||||
if let channelState = state.chatStates[peer.id] as? ChannelState {
|
||||
if let channelState = state.channelStates[peer.id] {
|
||||
pollPts = channelState.pts
|
||||
} else {
|
||||
pollPts = 1
|
||||
@ -1808,13 +1827,13 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
|
||||
switch difference {
|
||||
case let .channelDifference(_, pts, timeout, newMessages, otherUpdates, chats, users):
|
||||
apiTimeout = timeout
|
||||
let channelState: ChannelState
|
||||
if let previousState = updatedState.chatStates[peer.id] as? ChannelState {
|
||||
channelState = previousState.withUpdatedPts(pts)
|
||||
let channelPts: Int32
|
||||
if let _ = updatedState.channelStates[peer.id] {
|
||||
channelPts = pts
|
||||
} else {
|
||||
channelState = ChannelState(pts: pts, invalidatedPts: nil, synchronizedUntilMessageId: nil)
|
||||
channelPts = pts
|
||||
}
|
||||
updatedState.updateChannelState(peer.id, state: channelState)
|
||||
updatedState.updateChannelState(peer.id, pts: channelPts)
|
||||
|
||||
updatedState.mergeChats(chats)
|
||||
updatedState.mergeUsers(users)
|
||||
@ -1827,6 +1846,9 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
|
||||
}
|
||||
}
|
||||
updatedState.addMessages([message], location: .UpperHistoryBlock)
|
||||
if case let .Id(id) = message.id {
|
||||
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
for update in otherUpdates {
|
||||
@ -1889,13 +1911,13 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
|
||||
case let .channelDifferenceEmpty(_, pts, timeout):
|
||||
apiTimeout = timeout
|
||||
|
||||
let channelState: ChannelState
|
||||
if let previousState = updatedState.chatStates[peer.id] as? ChannelState {
|
||||
channelState = previousState.withUpdatedPts(pts)
|
||||
let channelPts: Int32
|
||||
if let previousState = updatedState.channelStates[peer.id] {
|
||||
channelPts = pts
|
||||
} else {
|
||||
channelState = ChannelState(pts: pts, invalidatedPts: nil, synchronizedUntilMessageId: nil)
|
||||
channelPts = pts
|
||||
}
|
||||
updatedState.updateChannelState(peer.id, state: channelState)
|
||||
updatedState.updateChannelState(peer.id, pts: channelPts)
|
||||
case let .channelDifferenceTooLong(_, timeout, dialog, messages, chats, users):
|
||||
apiTimeout = timeout
|
||||
|
||||
@ -1911,8 +1933,8 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
|
||||
}
|
||||
|
||||
if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount) = parameters {
|
||||
let channelState = ChannelState(pts: pts, invalidatedPts: pts, synchronizedUntilMessageId: nil)
|
||||
updatedState.updateChannelState(peer.peerId, state: channelState)
|
||||
updatedState.updateChannelState(peer.peerId, pts: pts)
|
||||
updatedState.updateChannelInvalidationPts(peer.peerId, invalidationPts: pts)
|
||||
|
||||
updatedState.mergeChats(chats)
|
||||
updatedState.mergeUsers(users)
|
||||
@ -1930,6 +1952,7 @@ private func pollChannel(network: Network, peer: Peer, state: AccountMutableStat
|
||||
let location: AddMessagesLocation
|
||||
if case let .Id(id) = message.id, id.id == topMessage {
|
||||
location = .UpperHistoryBlock
|
||||
updatedState.updateChannelSynchronizedUntilMessage(id.peerId, id: id.id)
|
||||
} else {
|
||||
location = .Random
|
||||
}
|
||||
@ -2001,9 +2024,9 @@ private func verifyTransaction(_ transaction: Transaction, finalState: AccountMu
|
||||
for peerId in channelsWithUpdatedStates {
|
||||
let currentState = transaction.getPeerChatState(peerId)
|
||||
var previousStateMatches = false
|
||||
let previousState = finalState.initialState.chatStates[peerId] as? ChannelState
|
||||
if let currentState = currentState, let previousState = previousState {
|
||||
if currentState.equals(previousState) {
|
||||
let previousState = finalState.initialState.channelStates[peerId]
|
||||
if let currentState = currentState as? ChannelState, let previousState = previousState {
|
||||
if currentState.pts == previousState.pts {
|
||||
previousStateMatches = true
|
||||
}
|
||||
} else if currentState == nil && previousState == nil {
|
||||
@ -2042,7 +2065,9 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
||||
var result: [AccountStateMutationOperation] = []
|
||||
|
||||
var updatedState: AuthorizedAccountState.State?
|
||||
var updatedChannelStates: [PeerId: ChannelState] = [:]
|
||||
var updatedChannelStates: [PeerId: AccountStateChannelState] = [:]
|
||||
var invalidateChannelPts: [PeerId: Int32] = [:]
|
||||
var updateChannelSynchronizedUntilMessage: [PeerId: MessageId.Id] = [:]
|
||||
|
||||
var currentAddMessages: OptimizeAddMessagesState?
|
||||
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||
@ -2059,8 +2084,12 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
||||
result.append(operation)
|
||||
case let .UpdateState(state):
|
||||
updatedState = state
|
||||
case let .UpdateChannelState(peerId, state):
|
||||
updatedChannelStates[peerId] = state
|
||||
case let .UpdateChannelState(peerId, pts):
|
||||
updatedChannelStates[peerId] = AccountStateChannelState(pts: pts)
|
||||
case let .UpdateChannelInvalidationPts(peerId, pts):
|
||||
invalidateChannelPts[peerId] = pts
|
||||
case let .UpdateChannelSynchronizedUntilMessage(peerId, id):
|
||||
updateChannelSynchronizedUntilMessage[peerId] = id
|
||||
case let .AddMessages(messages, location):
|
||||
if let currentAddMessages = currentAddMessages, currentAddMessages.location == location {
|
||||
currentAddMessages.messages.append(contentsOf: messages)
|
||||
@ -2091,7 +2120,15 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
||||
}
|
||||
|
||||
for (peerId, state) in updatedChannelStates {
|
||||
result.append(.UpdateChannelState(peerId, state))
|
||||
result.append(.UpdateChannelState(peerId, state.pts))
|
||||
}
|
||||
|
||||
for (peerId, pts) in invalidateChannelPts {
|
||||
result.append(.UpdateChannelInvalidationPts(peerId, pts))
|
||||
}
|
||||
|
||||
for (peerId, id) in updateChannelSynchronizedUntilMessage {
|
||||
result.append(.UpdateChannelSynchronizedUntilMessage(peerId, id))
|
||||
}
|
||||
|
||||
return result
|
||||
@ -2138,8 +2175,18 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
|
||||
for (peerId, namespaces) in finalState.state.namespacesWithHolesFromPreviousState {
|
||||
for namespace in namespaces {
|
||||
if let id = transaction.getTopPeerMessageId(peerId: peerId, namespace: namespace) {
|
||||
holesFromPreviousStateMessageIds.append(MessageId(peerId: id.peerId, namespace: id.namespace, id: id.id + 1))
|
||||
var topId: Int32?
|
||||
if namespace == Namespaces.Message.Cloud, let channelState = transaction.getPeerChatState(peerId) as? ChannelState {
|
||||
if let synchronizedUntilMessageId = channelState.synchronizedUntilMessageId {
|
||||
topId = synchronizedUntilMessageId + 1
|
||||
}
|
||||
}
|
||||
if topId == nil {
|
||||
topId = transaction.getTopPeerMessageId(peerId: peerId, namespace: namespace)?.id
|
||||
}
|
||||
|
||||
if let id = topId {
|
||||
holesFromPreviousStateMessageIds.append(MessageId(peerId: peerId, namespace: namespace, id: id + 1))
|
||||
} else {
|
||||
holesFromPreviousStateMessageIds.append(MessageId(peerId: peerId, namespace: namespace, id: 1))
|
||||
}
|
||||
@ -2488,9 +2535,21 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
|
||||
let currentState = transaction.getState() as! AuthorizedAccountState
|
||||
transaction.setState(currentState.changedState(state))
|
||||
Logger.shared.log("State", "apply state \(state)")
|
||||
case let .UpdateChannelState(peerId, channelState):
|
||||
transaction.setPeerChatState(peerId, state: channelState)
|
||||
Logger.shared.log("State", "apply channel state \(peerId): \(channelState)")
|
||||
case let .UpdateChannelState(peerId, pts):
|
||||
var state = (transaction.getPeerChatState(peerId) as? ChannelState) ?? ChannelState(pts: pts, invalidatedPts: nil, synchronizedUntilMessageId: nil)
|
||||
state = state.withUpdatedPts(pts)
|
||||
transaction.setPeerChatState(peerId, state: state)
|
||||
Logger.shared.log("State", "apply channel state \(peerId): \(state)")
|
||||
case let .UpdateChannelInvalidationPts(peerId, pts):
|
||||
var state = (transaction.getPeerChatState(peerId) as? ChannelState) ?? ChannelState(pts: 0, invalidatedPts: pts, synchronizedUntilMessageId: nil)
|
||||
state = state.withUpdatedInvalidatedPts(pts)
|
||||
transaction.setPeerChatState(peerId, state: state)
|
||||
Logger.shared.log("State", "apply channel invalidation pts \(peerId): \(state)")
|
||||
case let .UpdateChannelSynchronizedUntilMessage(peerId, id):
|
||||
var state = (transaction.getPeerChatState(peerId) as? ChannelState) ?? ChannelState(pts: 0, invalidatedPts: nil, synchronizedUntilMessageId: id)
|
||||
state = state.withUpdatedSynchronizedUntilMessageId(id)
|
||||
transaction.setPeerChatState(peerId, state: state)
|
||||
Logger.shared.log("State", "apply channel synchronized until message \(peerId): \(state)")
|
||||
case let .UpdateNotificationSettings(subject, notificationSettings):
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
|
@ -219,6 +219,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
||||
}
|
||||
|
||||
stateManager.addUpdates(result)
|
||||
stateManager.addUpdateGroups([.ensurePeerHasLocalState(id: message.id.peerId)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,5 +356,6 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 200)
|
||||
}
|
||||
stateManager.addUpdates(result)
|
||||
stateManager.addUpdateGroups([.ensurePeerHasLocalState(id: messages[0].id.peerId)])
|
||||
}
|
||||
}
|
||||
|
@ -351,7 +351,15 @@ private final class CallSessionManagerContext {
|
||||
let internalId = CallSessionInternalId()
|
||||
let context = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b))
|
||||
self.contexts[internalId] = context
|
||||
context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start())
|
||||
let queue = self.queue
|
||||
context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start(error: { [weak self] _ in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil))
|
||||
}
|
||||
}))
|
||||
self.contextIdByStableId[stableId] = internalId
|
||||
self.contextUpdated(internalId: internalId)
|
||||
self.ringingStatesUpdated()
|
||||
|
@ -564,7 +564,7 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: PeerPresence] = [:]
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
var channelStates: [PeerId: ChannelState] = [:]
|
||||
var channelStates: [PeerId: Int32] = [:]
|
||||
|
||||
switch result {
|
||||
case let .peerDialogs(dialogs, messages, chats, users, _):
|
||||
@ -644,9 +644,10 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
||||
transaction.replaceMessageTagSummary(peerId: peerId, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, maxId: topMessage)
|
||||
|
||||
if let pts = pts {
|
||||
let channelState = ChannelState(pts: pts, invalidatedPts: pts, synchronizedUntilMessageId: nil)
|
||||
transaction.setPeerChatState(peerId, state: channelState)
|
||||
channelStates[peer.peerId] = channelState
|
||||
if transaction.getPeerChatState(peerId) == nil {
|
||||
transaction.setPeerChatState(peerId, state: ChannelState(pts: pts, invalidatedPts: nil, synchronizedUntilMessageId: nil))
|
||||
}
|
||||
channelStates[peer.peerId] = pts
|
||||
}
|
||||
case .dialogFolder:
|
||||
assertionFailure()
|
||||
@ -659,9 +660,9 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
||||
if let storeMessage = StoreMessage(apiMessage: message) {
|
||||
var updatedStoreMessage = storeMessage
|
||||
if case let .Id(id) = storeMessage.id {
|
||||
if let channelState = channelStates[id.peerId] {
|
||||
if let channelPts = channelStates[id.peerId] {
|
||||
var updatedAttributes = storeMessage.attributes
|
||||
updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||
updatedStoreMessage = updatedStoreMessage.withUpdatedAttributes(updatedAttributes)
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ struct ParsedDialogs {
|
||||
let notificationSettings: [PeerId: PeerNotificationSettings]
|
||||
let readStates: [PeerId: [MessageId.Namespace: PeerReadState]]
|
||||
let mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
|
||||
let chatStates: [PeerId: PeerChatState]
|
||||
let channelStates: [PeerId: Int32]
|
||||
let topMessageIds: [PeerId: MessageId]
|
||||
let storeMessages: [StoreMessage]
|
||||
|
||||
@ -50,7 +50,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:]
|
||||
var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:]
|
||||
var chatStates: [PeerId: PeerChatState] = [:]
|
||||
var channelStates: [PeerId: Int32] = [:]
|
||||
var topMessageIds: [PeerId: MessageId] = [:]
|
||||
|
||||
var storeMessages: [StoreMessage] = []
|
||||
@ -131,7 +131,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
||||
}
|
||||
|
||||
if let apiChannelPts = apiChannelPts {
|
||||
chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil, synchronizedUntilMessageId: nil)
|
||||
channelStates[peerId] = apiChannelPts
|
||||
}
|
||||
|
||||
notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings)
|
||||
@ -149,9 +149,9 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
||||
if let storeMessage = StoreMessage(apiMessage: message) {
|
||||
var updatedStoreMessage = storeMessage
|
||||
if case let .Id(id) = storeMessage.id {
|
||||
if let channelState = chatStates[id.peerId] as? ChannelState {
|
||||
if let channelPts = channelStates[id.peerId] {
|
||||
var updatedAttributes = storeMessage.attributes
|
||||
updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelState.pts))
|
||||
updatedAttributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
|
||||
updatedStoreMessage = updatedStoreMessage.withUpdatedAttributes(updatedAttributes)
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
||||
notificationSettings: notificationSettings,
|
||||
readStates: readStates,
|
||||
mentionTagSummaries: mentionTagSummaries,
|
||||
chatStates: chatStates,
|
||||
channelStates: channelStates,
|
||||
topMessageIds: topMessageIds,
|
||||
storeMessages: storeMessages,
|
||||
|
||||
@ -190,7 +190,7 @@ struct FetchedChatList {
|
||||
let notificationSettings: [PeerId: PeerNotificationSettings]
|
||||
let readStates: [PeerId: [MessageId.Namespace: PeerReadState]]
|
||||
let mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
|
||||
let chatStates: [PeerId: PeerChatState]
|
||||
let channelStates: [PeerId: Int32]
|
||||
let storeMessages: [StoreMessage]
|
||||
let topMessageIds: [PeerId: MessageId]
|
||||
|
||||
@ -295,7 +295,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:]
|
||||
var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:]
|
||||
var chatStates: [PeerId: PeerChatState] = [:]
|
||||
var channelStates: [PeerId: Int32] = [:]
|
||||
var storeMessages: [StoreMessage] = []
|
||||
var topMessageIds: [PeerId: MessageId] = [:]
|
||||
|
||||
@ -304,7 +304,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
notificationSettings.merge(parsedRemoteChats.notificationSettings, uniquingKeysWith: { _, updated in updated })
|
||||
readStates.merge(parsedRemoteChats.readStates, uniquingKeysWith: { _, updated in updated })
|
||||
mentionTagSummaries.merge(parsedRemoteChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
chatStates.merge(parsedRemoteChats.chatStates, uniquingKeysWith: { _, updated in updated })
|
||||
channelStates.merge(parsedRemoteChats.channelStates, uniquingKeysWith: { _, updated in updated })
|
||||
storeMessages.append(contentsOf: parsedRemoteChats.storeMessages)
|
||||
topMessageIds.merge(parsedRemoteChats.topMessageIds, uniquingKeysWith: { _, updated in updated })
|
||||
|
||||
@ -314,7 +314,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
notificationSettings.merge(parsedPinnedChats.notificationSettings, uniquingKeysWith: { _, updated in updated })
|
||||
readStates.merge(parsedPinnedChats.readStates, uniquingKeysWith: { _, updated in updated })
|
||||
mentionTagSummaries.merge(parsedPinnedChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
chatStates.merge(parsedPinnedChats.chatStates, uniquingKeysWith: { _, updated in updated })
|
||||
channelStates.merge(parsedPinnedChats.channelStates, uniquingKeysWith: { _, updated in updated })
|
||||
storeMessages.append(contentsOf: parsedPinnedChats.storeMessages)
|
||||
topMessageIds.merge(parsedPinnedChats.topMessageIds, uniquingKeysWith: { _, updated in updated })
|
||||
}
|
||||
@ -336,7 +336,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
notificationSettings.merge(folderChats.notificationSettings, uniquingKeysWith: { _, updated in updated })
|
||||
readStates.merge(folderChats.readStates, uniquingKeysWith: { _, updated in updated })
|
||||
mentionTagSummaries.merge(folderChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
chatStates.merge(folderChats.chatStates, uniquingKeysWith: { _, updated in updated })
|
||||
channelStates.merge(folderChats.channelStates, uniquingKeysWith: { _, updated in updated })
|
||||
storeMessages.append(contentsOf: folderChats.storeMessages)
|
||||
}
|
||||
|
||||
@ -369,7 +369,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
notificationSettings: notificationSettings,
|
||||
readStates: readStates,
|
||||
mentionTagSummaries: mentionTagSummaries,
|
||||
chatStates: chatStates,
|
||||
channelStates: channelStates,
|
||||
storeMessages: storeMessages,
|
||||
topMessageIds: topMessageIds,
|
||||
|
||||
|
@ -479,15 +479,11 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId
|
||||
}
|
||||
}
|
||||
|
||||
for (peerId, chatState) in fetchedChats.chatStates {
|
||||
if let chatState = chatState as? ChannelState {
|
||||
if let current = transaction.getPeerChatState(peerId) as? ChannelState {
|
||||
transaction.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts))
|
||||
} else {
|
||||
transaction.setPeerChatState(peerId, state: chatState)
|
||||
}
|
||||
for (peerId, pts) in fetchedChats.channelStates {
|
||||
if let current = transaction.getPeerChatState(peerId) as? ChannelState {
|
||||
transaction.setPeerChatState(peerId, state: current.withUpdatedPts(pts))
|
||||
} else {
|
||||
transaction.setPeerChatState(peerId, state: chatState)
|
||||
transaction.setPeerChatState(peerId, state: ChannelState(pts: pts, invalidatedPts: nil, synchronizedUntilMessageId: nil))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,70 +2,134 @@ import Foundation
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
private final class ManagedSynchronizePeerReadStatesState {
|
||||
private var synchronizeDisposables: [PeerId: (PeerReadStateSynchronizationOperation, Disposable)] = [:]
|
||||
private final class SynchronizePeerReadStatesContextImpl {
|
||||
private final class Operation {
|
||||
let operation: PeerReadStateSynchronizationOperation
|
||||
let disposable: Disposable
|
||||
|
||||
func clearDisposables() -> [Disposable] {
|
||||
let disposables = Array(self.synchronizeDisposables.values.map({ $0.1 }))
|
||||
self.synchronizeDisposables.removeAll()
|
||||
return disposables
|
||||
init(
|
||||
operation: PeerReadStateSynchronizationOperation,
|
||||
disposable: Disposable
|
||||
) {
|
||||
self.operation = operation
|
||||
self.disposable = disposable
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
func update(operations: [PeerId: PeerReadStateSynchronizationOperation]) -> (removed: [Disposable], added: [(PeerId, PeerReadStateSynchronizationOperation, MetaDisposable)]) {
|
||||
var removed: [Disposable] = []
|
||||
var added: [(PeerId, PeerReadStateSynchronizationOperation, MetaDisposable)] = []
|
||||
private let queue: Queue
|
||||
private let network: Network
|
||||
private let postbox: Postbox
|
||||
private let stateManager: AccountStateManager
|
||||
|
||||
for (peerId, (operation, disposable)) in self.synchronizeDisposables {
|
||||
if operations[peerId] != operation {
|
||||
removed.append(disposable)
|
||||
self.synchronizeDisposables.removeValue(forKey: peerId)
|
||||
private var disposable: Disposable?
|
||||
|
||||
private var currentState: [PeerId : PeerReadStateSynchronizationOperation] = [:]
|
||||
private var activeOperations: [PeerId: Operation] = [:]
|
||||
private var pendingOperations: [PeerId: PeerReadStateSynchronizationOperation] = [:]
|
||||
|
||||
init(queue: Queue, network: Network, postbox: Postbox, stateManager: AccountStateManager) {
|
||||
self.queue = queue
|
||||
self.network = network
|
||||
self.postbox = postbox
|
||||
self.stateManager = stateManager
|
||||
|
||||
self.disposable = (postbox.synchronizePeerReadStatesView()
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] view in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.currentState = view.operations
|
||||
strongSelf.update()
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
}
|
||||
|
||||
func dispose() {
|
||||
}
|
||||
|
||||
private func update() {
|
||||
let peerIds = Set(self.currentState.keys).union(Set(self.pendingOperations.keys))
|
||||
|
||||
for peerId in peerIds {
|
||||
var maybeOperation: PeerReadStateSynchronizationOperation?
|
||||
if let operation = self.currentState[peerId] {
|
||||
maybeOperation = operation
|
||||
} else if let operation = self.pendingOperations[peerId] {
|
||||
maybeOperation = operation
|
||||
self.pendingOperations.removeValue(forKey: peerId)
|
||||
}
|
||||
|
||||
if let operation = maybeOperation {
|
||||
if let current = self.activeOperations[peerId] {
|
||||
if current.operation != operation {
|
||||
self.pendingOperations[peerId] = operation
|
||||
}
|
||||
} else {
|
||||
let operationDisposable = MetaDisposable()
|
||||
let activeOperation = Operation(
|
||||
operation: operation,
|
||||
disposable: operationDisposable
|
||||
)
|
||||
self.activeOperations[peerId] = activeOperation
|
||||
let signal: Signal<Never, NoError>
|
||||
switch operation {
|
||||
case .Validate:
|
||||
signal = synchronizePeerReadState(network: self.network, postbox: self.postbox, stateManager: self.stateManager, peerId: peerId, push: false, validate: true)
|
||||
|> ignoreValues
|
||||
case let .Push(_, thenSync):
|
||||
signal = synchronizePeerReadState(network: self.network, postbox: self.postbox, stateManager: stateManager, peerId: peerId, push: true, validate: thenSync)
|
||||
|> ignoreValues
|
||||
}
|
||||
operationDisposable.set((signal
|
||||
|> deliverOn(self.queue)).start(completed: { [weak self, weak activeOperation] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let activeOperation = activeOperation {
|
||||
if let current = strongSelf.activeOperations[peerId], current === activeOperation {
|
||||
strongSelf.activeOperations.removeValue(forKey: peerId)
|
||||
strongSelf.update()
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (peerId, operation) in operations {
|
||||
if self.synchronizeDisposables[peerId] == nil {
|
||||
let disposable = MetaDisposable()
|
||||
self.synchronizeDisposables[peerId] = (operation, disposable)
|
||||
added.append((peerId, operation, disposable))
|
||||
}
|
||||
private final class SynchronizePeerReadStatesStatesContext {
|
||||
private let queue: Queue
|
||||
private let impl: QueueLocalObject<SynchronizePeerReadStatesContextImpl>
|
||||
|
||||
init(network: Network, postbox: Postbox, stateManager: AccountStateManager) {
|
||||
self.queue = Queue()
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return SynchronizePeerReadStatesContextImpl(queue: queue, network: network, postbox: postbox, stateManager: stateManager)
|
||||
})
|
||||
}
|
||||
|
||||
func dispose() {
|
||||
self.impl.with { impl in
|
||||
impl.dispose()
|
||||
}
|
||||
|
||||
return (removed, added)
|
||||
}
|
||||
}
|
||||
|
||||
func managedSynchronizePeerReadStates(network: Network, postbox: Postbox, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||
return Signal { _ in
|
||||
let state = Atomic(value: ManagedSynchronizePeerReadStatesState())
|
||||
|
||||
let disposable = postbox.synchronizePeerReadStatesView().start(next: { view in
|
||||
let (removed, added) = state.with { state -> (removed: [Disposable], added: [(PeerId, PeerReadStateSynchronizationOperation, MetaDisposable)]) in
|
||||
return state.update(operations: view.operations)
|
||||
}
|
||||
|
||||
for disposable in removed {
|
||||
disposable.dispose()
|
||||
}
|
||||
|
||||
for (peerId, operation, disposable) in added {
|
||||
let synchronizeOperation: Signal<Void, NoError>
|
||||
switch operation {
|
||||
case .Validate:
|
||||
synchronizeOperation = synchronizePeerReadState(network: network, postbox: postbox, stateManager: stateManager, peerId: peerId, push: false, validate: true)
|
||||
case let .Push(_, thenSync):
|
||||
synchronizeOperation = synchronizePeerReadState(network: network, postbox: postbox, stateManager: stateManager, peerId: peerId, push: true, validate: thenSync)
|
||||
}
|
||||
disposable.set(synchronizeOperation.start())
|
||||
}
|
||||
})
|
||||
let context = SynchronizePeerReadStatesStatesContext(network: network, postbox: postbox, stateManager: stateManager)
|
||||
|
||||
return ActionDisposable {
|
||||
disposable.dispose()
|
||||
for disposable in state.with({ state -> [Disposable] in
|
||||
state.clearDisposables()
|
||||
}) {
|
||||
disposable.dispose()
|
||||
}
|
||||
context.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
||||
|> mapToSignal { dialogs -> Signal<Void, NoError> in
|
||||
var storeMessages: [StoreMessage] = []
|
||||
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:]
|
||||
var chatStates: [PeerId: PeerChatState] = [:]
|
||||
var channelStates: [PeerId: Int32] = [:]
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
|
||||
var remoteItemIds: [PinnedItemId] = []
|
||||
@ -200,7 +200,7 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
||||
readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount, markedUnread: apiMarkedUnread)
|
||||
|
||||
if let apiChannelPts = apiChannelPts {
|
||||
chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil, synchronizedUntilMessageId: nil)
|
||||
channelStates[peerId] = apiChannelPts
|
||||
}
|
||||
|
||||
notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings)
|
||||
@ -245,15 +245,11 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
||||
|
||||
transaction.resetIncomingReadStates(readStates)
|
||||
|
||||
for (peerId, chatState) in chatStates {
|
||||
if let chatState = chatState as? ChannelState {
|
||||
if let _ = transaction.getPeerChatState(peerId) as? ChannelState {
|
||||
// skip changing state
|
||||
} else {
|
||||
transaction.setPeerChatState(peerId, state: chatState)
|
||||
}
|
||||
for (peerId, pts) in channelStates {
|
||||
if let _ = transaction.getPeerChatState(peerId) as? ChannelState {
|
||||
// skip changing state
|
||||
} else {
|
||||
transaction.setPeerChatState(peerId, state: chatState)
|
||||
transaction.setPeerChatState(peerId, state: ChannelState(pts: pts, invalidatedPts: nil, synchronizedUntilMessageId: nil))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,6 +160,7 @@ public final class PendingMessageManager {
|
||||
var transformOutgoingMessageMedia: TransformOutgoingMessageMedia?
|
||||
|
||||
init(network: Network, postbox: Postbox, accountPeerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, stateManager: AccountStateManager, localInputActivityManager: PeerInputActivityManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext) {
|
||||
Logger.shared.log("PendingMessageManager", "create instance")
|
||||
self.network = network
|
||||
self.postbox = postbox
|
||||
self.accountPeerId = accountPeerId
|
||||
@ -176,10 +177,16 @@ public final class PendingMessageManager {
|
||||
|
||||
func updatePendingMessageIds(_ messageIds: Set<MessageId>) {
|
||||
self.queue.async {
|
||||
Logger.shared.log("PendingMessageManager", "update: \(messageIds)")
|
||||
|
||||
let addedMessageIds = messageIds.subtracting(self.pendingMessageIds)
|
||||
let removedMessageIds = self.pendingMessageIds.subtracting(messageIds)
|
||||
let removedSecretMessageIds = Set(removedMessageIds.filter({ $0.peerId.namespace == Namespaces.Peer.SecretChat }))
|
||||
|
||||
if !removedMessageIds.isEmpty {
|
||||
Logger.shared.log("PendingMessageManager", "removed messages: \(removedMessageIds)")
|
||||
}
|
||||
|
||||
var updateUploadingPeerIds = Set<PeerId>()
|
||||
var updateUploadingGroupIds = Set<Int64>()
|
||||
for id in removedMessageIds {
|
||||
@ -207,6 +214,7 @@ public final class PendingMessageManager {
|
||||
}
|
||||
|
||||
if !addedMessageIds.isEmpty {
|
||||
Logger.shared.log("PendingMessageManager", "added messages: \(addedMessageIds)")
|
||||
self.beginSendingMessages(Array(addedMessageIds).sorted())
|
||||
}
|
||||
|
||||
@ -251,6 +259,8 @@ public final class PendingMessageManager {
|
||||
peersWithPendingMessages.insert(id.peerId)
|
||||
}
|
||||
|
||||
Logger.shared.log("PendingMessageManager", "pending messages: \(self.pendingMessageIds)")
|
||||
|
||||
self._hasPendingMessages.set(peersWithPendingMessages)
|
||||
}
|
||||
}
|
||||
@ -333,6 +343,8 @@ public final class PendingMessageManager {
|
||||
}
|
||||
}
|
||||
|
||||
Logger.shared.log("PendingMessageManager", "begin sending: \(ids)")
|
||||
|
||||
let disposable = MetaDisposable()
|
||||
let messages = self.postbox.messagesAtIds(ids)
|
||||
|> deliverOn(self.queue)
|
||||
@ -346,6 +358,8 @@ public final class PendingMessageManager {
|
||||
if let strongSelf = self {
|
||||
assert(strongSelf.queue.isCurrent())
|
||||
|
||||
Logger.shared.log("PendingMessageManager", "begin sending, continued: \(ids)")
|
||||
|
||||
for message in messages.filter({ !$0.flags.contains(.Sending) }).sorted(by: { $0.id < $1.id }) {
|
||||
guard let messageContext = strongSelf.messageContexts[message.id] else {
|
||||
continue
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import TelegramApi
|
||||
import Postbox
|
||||
|
||||
enum UpdateGroup {
|
||||
case withPts(updates: [Api.Update], users: [Api.User], chats: [Api.Chat])
|
||||
@ -9,6 +10,7 @@ enum UpdateGroup {
|
||||
case reset
|
||||
case updatePts(pts: Int32, ptsCount: Int32)
|
||||
case updateChannelPts(channelId: Int32, pts: Int32, ptsCount: Int32)
|
||||
case ensurePeerHasLocalState(id: PeerId)
|
||||
|
||||
var updates: [Api.Update] {
|
||||
switch self {
|
||||
@ -20,7 +22,7 @@ enum UpdateGroup {
|
||||
return updates
|
||||
case let .withSeq(updates, _, _, _, _):
|
||||
return updates
|
||||
case .reset, .updatePts, .updateChannelPts:
|
||||
case .reset, .updatePts, .updateChannelPts, .ensurePeerHasLocalState:
|
||||
return []
|
||||
}
|
||||
}
|
||||
@ -35,7 +37,7 @@ enum UpdateGroup {
|
||||
return users
|
||||
case let .withSeq(_, _, _, users, _):
|
||||
return users
|
||||
case .reset, .updatePts, .updateChannelPts:
|
||||
case .reset, .updatePts, .updateChannelPts, .ensurePeerHasLocalState:
|
||||
return []
|
||||
}
|
||||
}
|
||||
@ -50,7 +52,7 @@ enum UpdateGroup {
|
||||
return chats
|
||||
case let .withSeq(_, _, _, _, chats):
|
||||
return chats
|
||||
case .reset, .updatePts, .updateChannelPts:
|
||||
case .reset, .updatePts, .updateChannelPts, .ensurePeerHasLocalState:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -8000,6 +8000,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var parsedUrlValue: URL?
|
||||
if let parsed = URL(string: url) {
|
||||
parsedUrlValue = parsed
|
||||
} else if let parsed = URL(string: "https://" + url) {
|
||||
parsedUrlValue = parsed
|
||||
} else if let encoded = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), let parsed = URL(string: encoded) {
|
||||
parsedUrlValue = parsed
|
||||
}
|
||||
@ -8012,7 +8014,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
latin.insert(charactersIn: "a"..."z")
|
||||
latin.insert(charactersIn: "0"..."9")
|
||||
var punctuation = CharacterSet()
|
||||
punctuation.insert(charactersIn: ".-")
|
||||
punctuation.insert(charactersIn: ".-/+")
|
||||
var hasLatin = false
|
||||
var hasNonLatin = false
|
||||
for c in rawHost {
|
||||
|
@ -49,13 +49,26 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: LimitsCo
|
||||
}
|
||||
} else if message.id.peerId.namespace == Namespaces.Peer.SecretChat || message.id.namespace != Namespaces.Message.Cloud {
|
||||
hasEditRights = false
|
||||
} else if let author = message.author, author.id == accountPeerId {
|
||||
} else if let author = message.author, author.id == accountPeerId, let peer = message.peers[message.id.peerId] {
|
||||
hasEditRights = true
|
||||
if let peer = peer as? TelegramChannel {
|
||||
switch peer.info {
|
||||
case .broadcast:
|
||||
if peer.hasPermission(.editAllMessages) || !message.flags.contains(.Incoming) {
|
||||
unlimitedInterval = true
|
||||
}
|
||||
case .group:
|
||||
if peer.hasPermission(.pinMessages) {
|
||||
unlimitedInterval = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if message.author?.id == message.id.peerId, let peer = message.peers[message.id.peerId] {
|
||||
if let peer = peer as? TelegramChannel {
|
||||
switch peer.info {
|
||||
case .broadcast:
|
||||
if peer.hasPermission(.editAllMessages) || !message.flags.contains(.Incoming) {
|
||||
unlimitedInterval = true
|
||||
hasEditRights = true
|
||||
}
|
||||
case .group:
|
||||
|
@ -809,10 +809,16 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme)
|
||||
|
||||
if view.higher == nil {
|
||||
var hasTopSeparator = true
|
||||
if gridEntries.count == 1, case .search = gridEntries[0] {
|
||||
hasTopSeparator = false
|
||||
}
|
||||
|
||||
var index = 0
|
||||
for item in trendingPacks {
|
||||
if !installedPacks.contains(item.info.id) {
|
||||
gridEntries.append(.trending(TrendingPanePackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread, topSeparator: true)))
|
||||
gridEntries.append(.trending(TrendingPanePackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread, topSeparator: hasTopSeparator)))
|
||||
hasTopSeparator = true
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ final class TrendingPanePackEntry: Identifiable, Comparable {
|
||||
|
||||
func item(account: Account, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem {
|
||||
let info = self.info
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: false, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, installed: self.installed, unread: self.unread, open: {
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: false, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, regularInsets: false, installed: self.installed, unread: self.unread, open: {
|
||||
interaction.openPack(info)
|
||||
}, install: {
|
||||
interaction.installPack(info)
|
||||
|
@ -40,8 +40,9 @@ private final class FeaturedPackEntry: Identifiable, Comparable {
|
||||
let installed: Bool
|
||||
let unread: Bool
|
||||
let topSeparator: Bool
|
||||
let regularInsets: Bool
|
||||
|
||||
init(index: Int, info: StickerPackCollectionInfo, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool) {
|
||||
init(index: Int, info: StickerPackCollectionInfo, theme: PresentationTheme, strings: PresentationStrings, topItems: [StickerPackItem], installed: Bool, unread: Bool, topSeparator: Bool, regularInsets: Bool = false) {
|
||||
self.index = index
|
||||
self.info = info
|
||||
self.theme = theme
|
||||
@ -50,6 +51,7 @@ private final class FeaturedPackEntry: Identifiable, Comparable {
|
||||
self.installed = installed
|
||||
self.unread = unread
|
||||
self.topSeparator = topSeparator
|
||||
self.regularInsets = regularInsets
|
||||
}
|
||||
|
||||
var stableId: ItemCollectionId {
|
||||
@ -81,6 +83,9 @@ private final class FeaturedPackEntry: Identifiable, Comparable {
|
||||
if lhs.topSeparator != rhs.topSeparator {
|
||||
return false
|
||||
}
|
||||
if lhs.regularInsets != rhs.regularInsets {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -88,46 +93,36 @@ private final class FeaturedPackEntry: Identifiable, Comparable {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(account: Account, interaction: FeaturedInteraction, grid: Bool) -> GridItem {
|
||||
func item(account: Account, interaction: FeaturedInteraction, isOther: Bool) -> GridItem {
|
||||
let info = self.info
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: true, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, installed: self.installed, unread: self.unread, open: {
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: true, info: self.info, topItems: self.topItems, grid: false, topSeparator: self.topSeparator, regularInsets: self.regularInsets, installed: self.installed, unread: self.unread, open: {
|
||||
interaction.openPack(info)
|
||||
}, install: {
|
||||
interaction.installPack(info, !self.installed)
|
||||
}, getItemIsPreviewed: { item in
|
||||
return interaction.getItemIsPreviewed(item)
|
||||
}, itemContext: interaction.itemContext)
|
||||
}, itemContext: interaction.itemContext, sectionTitle: isOther ? self.strings.FeaturedStickers_OtherSection : nil)
|
||||
}
|
||||
}
|
||||
|
||||
private enum FeaturedEntryId: Hashable {
|
||||
case search
|
||||
case pack(ItemCollectionId)
|
||||
}
|
||||
|
||||
private enum FeaturedEntry: Identifiable, Comparable {
|
||||
case search(theme: PresentationTheme, strings: PresentationStrings)
|
||||
case pack(FeaturedPackEntry)
|
||||
case pack(FeaturedPackEntry, Bool)
|
||||
|
||||
var stableId: FeaturedEntryId {
|
||||
switch self {
|
||||
case .search:
|
||||
return .search
|
||||
case let .pack(pack):
|
||||
case let .pack(pack, _):
|
||||
return .pack(pack.stableId)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: FeaturedEntry, rhs: FeaturedEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .search(lhsTheme, lhsStrings):
|
||||
if case let .search(rhsTheme, rhsStrings) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .pack(pack):
|
||||
if case .pack(pack) = rhs {
|
||||
case let .pack(pack, isOther):
|
||||
if case .pack(pack, isOther) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -137,26 +132,18 @@ private enum FeaturedEntry: Identifiable, Comparable {
|
||||
|
||||
static func <(lhs: FeaturedEntry, rhs: FeaturedEntry) -> Bool {
|
||||
switch lhs {
|
||||
case .search:
|
||||
return false
|
||||
case let .pack(lhsPack):
|
||||
case let .pack(lhsPack, _):
|
||||
switch rhs {
|
||||
case .search:
|
||||
return false
|
||||
case let .pack(rhsPack):
|
||||
case let .pack(rhsPack, _):
|
||||
return lhsPack < rhsPack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func item(account: Account, interaction: FeaturedInteraction, grid: Bool) -> GridItem {
|
||||
func item(account: Account, interaction: FeaturedInteraction) -> GridItem {
|
||||
switch self {
|
||||
case let .search(theme, strings):
|
||||
return PaneSearchBarPlaceholderItem(theme: theme, strings: strings, type: .stickers, activate: {
|
||||
interaction.openSearch()
|
||||
})
|
||||
case let .pack(pack):
|
||||
return pack.item(account: account, interaction: interaction, grid: grid)
|
||||
case let .pack(pack, isOther):
|
||||
return pack.item(account: account, interaction: interaction, isOther: isOther)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,8 +159,8 @@ private func preparedTransition(from fromEntries: [FeaturedEntry], to toEntries:
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices
|
||||
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction, grid: false), previousIndex: $0.2) }
|
||||
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction, grid: false)) }
|
||||
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction), previousIndex: $0.2) }
|
||||
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction)) }
|
||||
|
||||
return FeaturedTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial)
|
||||
}
|
||||
@ -185,14 +172,14 @@ private func featuredScreenEntries(featuredEntries: [FeaturedStickerPackItem], i
|
||||
for item in featuredEntries {
|
||||
if !existingIds.contains(item.info.id) {
|
||||
existingIds.insert(item.info.id)
|
||||
result.append(.pack(FeaturedPackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread || fixedUnread.contains(item.info.id), topSeparator: index != 0)))
|
||||
result.append(.pack(FeaturedPackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread || fixedUnread.contains(item.info.id), topSeparator: index != 0, regularInsets: true), false))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
for item in additionalPacks {
|
||||
if !existingIds.contains(item.info.id) {
|
||||
existingIds.insert(item.info.id)
|
||||
result.append(.pack(FeaturedPackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread || fixedUnread.contains(item.info.id), topSeparator: index != 0)))
|
||||
result.append(.pack(FeaturedPackEntry(index: index, info: item.info, theme: theme, strings: strings, topItems: item.topItems, installed: installedPacks.contains(item.info.id), unread: item.unread || fixedUnread.contains(item.info.id), topSeparator: index != 0, regularInsets: true), true))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
@ -238,6 +225,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
self.sendSticker = sendSticker
|
||||
|
||||
self.gridNode = GridNode()
|
||||
self.gridNode.floatingSections = true
|
||||
|
||||
super.init()
|
||||
|
||||
@ -1040,7 +1028,7 @@ private enum FeaturedSearchEntry: Identifiable, Comparable {
|
||||
interaction.sendSticker(.standalone(media: stickerItem.file), node, rect)
|
||||
})
|
||||
case let .global(_, info, topItems, installed, topSeparator):
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, installed: installed, unread: false, open: {
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
|
||||
interaction.open(info)
|
||||
}, install: {
|
||||
interaction.install(info, topItems, !installed)
|
||||
|
@ -106,7 +106,7 @@ private enum StickerSearchEntry: Identifiable, Comparable {
|
||||
case let .global(_, info, topItems, installed, topSeparator):
|
||||
let itemContext = StickerPaneSearchGlobalItemContext()
|
||||
itemContext.canPlayMedia = true
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, installed: installed, unread: false, open: {
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
|
||||
interaction.open(info)
|
||||
}, install: {
|
||||
interaction.install(info, topItems, !installed)
|
||||
|
@ -8,27 +8,65 @@ import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import StickerPackPreviewUI
|
||||
import ListSectionHeaderNode
|
||||
|
||||
final class StickerPaneSearchGlobalSection: GridSection {
|
||||
let height: CGFloat = 0.0
|
||||
let title: String?
|
||||
let theme: PresentationTheme
|
||||
|
||||
var hashValue: Int {
|
||||
return 0
|
||||
var height: CGFloat {
|
||||
if let _ = self.title {
|
||||
return 28.0
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
var hashValue: Int {
|
||||
if let _ = self.title {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
init(title: String?, theme: PresentationTheme) {
|
||||
self.title = title
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
func isEqual(to: GridSection) -> Bool {
|
||||
if to is StickerPaneSearchGlobalSection {
|
||||
return true
|
||||
if let to = to as? StickerPaneSearchGlobalSection {
|
||||
return to.hashValue == self.hashValue
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func node() -> ASDisplayNode {
|
||||
return ASDisplayNode()
|
||||
return StickerPaneSearchGlobalSectionNode(theme: self.theme, title: self.title ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
private final class StickerPaneSearchGlobalSectionNode: ASDisplayNode {
|
||||
private let node: ListSectionHeaderNode
|
||||
|
||||
init(theme: PresentationTheme, title: String) {
|
||||
self.node = ListSectionHeaderNode(theme: theme)
|
||||
|
||||
super.init()
|
||||
|
||||
if !title.isEmpty {
|
||||
self.node.title = title
|
||||
self.addSubnode(self.node)
|
||||
}
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
self.node.frame = self.bounds
|
||||
self.node.updateLayout(size: self.bounds.size, leftInset: 0.0, rightInset: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +83,7 @@ final class StickerPaneSearchGlobalItem: GridItem {
|
||||
let topItems: [StickerPackItem]
|
||||
let grid: Bool
|
||||
let topSeparator: Bool
|
||||
let regularInsets: Bool
|
||||
let installed: Bool
|
||||
let installing: Bool
|
||||
let unread: Bool
|
||||
@ -53,12 +92,22 @@ final class StickerPaneSearchGlobalItem: GridItem {
|
||||
let getItemIsPreviewed: (StickerPackItem) -> Bool
|
||||
let itemContext: StickerPaneSearchGlobalItemContext
|
||||
|
||||
let section: GridSection? = StickerPaneSearchGlobalSection()
|
||||
let section: GridSection?
|
||||
var fillsRowWithHeight: CGFloat? {
|
||||
return self.grid ? nil : (128.0 + (self.topSeparator ? 12.0 : 0.0))
|
||||
var additionalHeight: CGFloat = 0.0
|
||||
if self.regularInsets {
|
||||
additionalHeight = 12.0 + 12.0
|
||||
} else {
|
||||
additionalHeight += 12.0
|
||||
if self.topSeparator {
|
||||
additionalHeight += 12.0
|
||||
}
|
||||
}
|
||||
|
||||
return self.grid ? nil : (128.0 + additionalHeight)
|
||||
}
|
||||
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, topSeparator: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext) {
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) {
|
||||
self.account = account
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
@ -67,6 +116,7 @@ final class StickerPaneSearchGlobalItem: GridItem {
|
||||
self.topItems = topItems
|
||||
self.grid = grid
|
||||
self.topSeparator = topSeparator
|
||||
self.regularInsets = regularInsets
|
||||
self.installed = installed
|
||||
self.installing = installing
|
||||
self.unread = unread
|
||||
@ -74,6 +124,7 @@ final class StickerPaneSearchGlobalItem: GridItem {
|
||||
self.install = install
|
||||
self.getItemIsPreviewed = getItemIsPreviewed
|
||||
self.itemContext = itemContext
|
||||
self.section = StickerPaneSearchGlobalSection(title: sectionTitle, theme: theme)
|
||||
}
|
||||
|
||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
@ -279,13 +330,21 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
|
||||
|
||||
let params = ListViewItemLayoutParams(width: size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: size.height)
|
||||
|
||||
var topOffset: CGFloat = 12.0
|
||||
if item.topSeparator {
|
||||
let topSeparatorOffset: CGFloat
|
||||
var topOffset: CGFloat = 0.0
|
||||
if item.regularInsets {
|
||||
topOffset = 12.0
|
||||
topSeparatorOffset = -UIScreenPixel
|
||||
} else {
|
||||
topSeparatorOffset = 16.0
|
||||
topOffset += 12.0
|
||||
if item.topSeparator {
|
||||
topOffset += 12.0
|
||||
}
|
||||
}
|
||||
|
||||
self.topSeparatorNode.isHidden = !item.topSeparator
|
||||
self.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: CGSize(width: params.width - 16.0 * 2.0, height: UIScreenPixel))
|
||||
self.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 16.0, y: topSeparatorOffset), size: CGSize(width: params.width - 16.0 * 2.0, height: UIScreenPixel))
|
||||
if item.listAppearance {
|
||||
self.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||
} else {
|
||||
|
Binary file not shown.
@ -449,12 +449,12 @@ public final class WalletStrings: Equatable {
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[219]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[220]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[221]! }
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
}
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
|
Loading…
x
Reference in New Issue
Block a user