Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-04-23 05:44:07 +04:00
commit 56633f8f35
34 changed files with 2694 additions and 2128 deletions

View File

@ -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";

View File

@ -1 +1 @@
11.4
11.4.1

View File

@ -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];

View File

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

View File

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

View File

@ -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
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,10 +178,41 @@ open class ZoomableContentGalleryItemNode: GalleryItemNode, UIScrollViewDelegate
return
}
var minScale: CGFloat
var maxScale: CGFloat
if contentNode.view is TilingView {
let normalizedContentSize = contentSize.fitted(boundsSize)
let scaleWidth = boundsSize.width / normalizedContentSize.width
let scaleHeight = boundsSize.height / normalizedContentSize.height
minScale = min(scaleWidth, scaleHeight)
minScale = 1.0
maxScale = max(scaleWidth, scaleHeight)
maxScale = max(maxScale, minScale * 4.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
}
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)
var maxScale = max(scaleWidth, scaleHeight)
maxScale = max(scaleWidth, scaleHeight)
maxScale = max(maxScale, minScale * 3.0)
if (abs(maxScale - minScale) < 0.01) {
@ -179,13 +223,10 @@ open class ZoomableContentGalleryItemNode: GalleryItemNode, UIScrollViewDelegate
self.scrollNode.view.minimumZoomScale = minScale
}
/*if !self.scrollView.normalZoomScale.isEqual(to: minScale) {
self.scrollView.normalZoomScale = minScale
}*/
if !self.scrollNode.view.maximumZoomScale.isEqual(to: maxScale) {
self.scrollNode.view.maximumZoomScale = maxScale
}
}
var contentFrame = contentNode.view.frame

View File

@ -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];

View File

@ -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 ?? "")
}
}

View File

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

View File

@ -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) {

View File

@ -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>()

View File

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

View File

@ -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] {

View File

@ -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):

View File

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

View File

@ -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()

View File

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

View File

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

View File

@ -479,15 +479,11 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId
}
}
for (peerId, chatState) in fetchedChats.chatStates {
if let chatState = chatState as? ChannelState {
for (peerId, pts) in fetchedChats.channelStates {
if let current = transaction.getPeerChatState(peerId) as? ChannelState {
transaction.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts))
transaction.setPeerChatState(peerId, state: current.withUpdatedPts(pts))
} else {
transaction.setPeerChatState(peerId, state: chatState)
}
} else {
transaction.setPeerChatState(peerId, state: chatState)
transaction.setPeerChatState(peerId, state: ChannelState(pts: pts, invalidatedPts: nil, synchronizedUntilMessageId: nil))
}
}

View File

@ -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
}
func update(operations: [PeerId: PeerReadStateSynchronizationOperation]) -> (removed: [Disposable], added: [(PeerId, PeerReadStateSynchronizationOperation, MetaDisposable)]) {
var removed: [Disposable] = []
var added: [(PeerId, PeerReadStateSynchronizationOperation, MetaDisposable)] = []
for (peerId, (operation, disposable)) in self.synchronizeDisposables {
if operations[peerId] != operation {
removed.append(disposable)
self.synchronizeDisposables.removeValue(forKey: peerId)
deinit {
self.disposable.dispose()
}
}
for (peerId, operation) in operations {
if self.synchronizeDisposables[peerId] == nil {
let disposable = MetaDisposable()
self.synchronizeDisposables[peerId] = (operation, disposable)
added.append((peerId, operation, disposable))
private let queue: Queue
private let network: Network
private let postbox: Postbox
private let stateManager: AccountStateManager
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()
}
}
}))
}
}
}
}
}
return (removed, added)
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()
}
}
}
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()
}
}
}

View File

@ -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 {
for (peerId, pts) in channelStates {
if let _ = transaction.getPeerChatState(peerId) as? ChannelState {
// skip changing state
} else {
transaction.setPeerChatState(peerId, state: chatState)
}
} else {
transaction.setPeerChatState(peerId, state: chatState)
transaction.setPeerChatState(peerId, state: ChannelState(pts: pts, invalidatedPts: nil, synchronizedUntilMessageId: nil))
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
}
}
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) {
return self.grid ? nil : (128.0 + additionalHeight)
}
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
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 {

View File

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