From f07c30bd5b8fbccf49a0008f35502c9a558e8446 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 15 Nov 2022 13:40:31 +0400 Subject: [PATCH 01/12] Make DrawingContext initializer failable --- .../Sources/DirectAnimatedStickerNode.swift | 19 +- .../Sources/VideoStickerFrameSource.swift | 4 +- .../AvatarNode/Sources/PeerAvatar.swift | 35 +- .../Sources/ReactionImageComponent.swift | 21 +- .../Sources/DirectMediaImageCache.swift | 14 +- submodules/Display/Source/GenerateImage.swift | 25 +- submodules/Display/Source/ImageCorners.swift | 2 +- .../Display/Source/StatusBarProxyNode.swift | 4 +- .../Sources/GalleryVideoDecoration.swift | 4 +- .../Sources/SoftwareGradientBackground.swift | 2 +- .../Sources/ImageTransparency.swift | 14 +- .../Sources/MapResources.swift | 4 +- .../Sources/VenueIconResources.swift | 4 +- .../Sources/ManagedAnimationNode.swift | 4 +- .../Sources/OpenInAppIconResources.swift | 8 +- .../Sources/PasscodeBackground.swift | 4 +- submodules/Pdf/Sources/PDF.swift | 4 +- .../Sources/PhotoResources.swift | 552 ++++++++++-------- .../Sources/PhoneDemoComponent.swift | 4 +- submodules/QrCode/Sources/QrCode.swift | 8 +- .../Themes/SettingsThemeWallpaperNode.swift | 8 +- .../Themes/ThemeSettingsThemeItem.swift | 4 +- .../Sources/StickerResources.swift | 81 +-- .../Sources/AnimatedStickerUtils.swift | 9 +- .../Sources/ChatMessageBubbleImages.swift | 10 +- .../Sources/PresentationData.swift | 2 +- .../Sources/LottieAnimationCache.swift | 4 +- .../Sources/MultiAnimationRenderer.swift | 8 +- .../TelegramUI/Sources/ChatBotInfoItem.swift | 4 +- .../ChatMessageInteractiveMediaNode.swift | 4 +- .../TelegramUI/Sources/EmojiResources.swift | 4 +- .../Sources/FetchCachedRepresentations.swift | 8 +- .../Sources/SpotlightContacts.swift | 21 +- .../Sources/ChatBubbleVideoDecoration.swift | 4 +- .../Sources/YoutubeEmbedImplementation.swift | 4 +- .../Sources/OngoingCallThreadLocalContext.mm | 3 + .../Sources/WallpaperBackgroundNode.swift | 4 +- .../Sources/WallpaperResources.swift | 117 ++-- .../WebSearchUI/Sources/WebSearchItem.swift | 4 +- 39 files changed, 624 insertions(+), 415 deletions(-) diff --git a/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift b/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift index 38f1c5267f..ca911ce2c6 100644 --- a/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift +++ b/submodules/AnimatedStickerNode/Sources/DirectAnimatedStickerNode.swift @@ -304,18 +304,19 @@ public final class DirectAnimatedStickerNode: ASDisplayNode, AnimatedStickerNode if !task.isCancelled { if let lottieInstance = lottieInstance { - let drawingContext = DrawingContext(size: playbackSize, scale: 1.0, opaque: false, clear: false) - lottieInstance.renderFrame(with: Int32(frameIndex), into: drawingContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(drawingContext.scaledSize.width), height: Int32(drawingContext.scaledSize.height), bytesPerRow: Int32(drawingContext.bytesPerRow)) - - image = drawingContext.generateImage() - } else if let videoSource = videoSource { - if let frame = videoSource.takeFrame(draw: true) { - let drawingContext = DrawingContext(size: CGSize(width: frame.width, height: frame.height), scale: 1.0, opaque: false, clear: false, bytesPerRow: frame.bytesPerRow) - - frame.data.copyBytes(to: drawingContext.bytes.assumingMemoryBound(to: UInt8.self), from: 0 ..< min(frame.data.count, drawingContext.length)) + if let drawingContext = DrawingContext(size: playbackSize, scale: 1.0, opaque: false, clear: false) { + lottieInstance.renderFrame(with: Int32(frameIndex), into: drawingContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(drawingContext.scaledSize.width), height: Int32(drawingContext.scaledSize.height), bytesPerRow: Int32(drawingContext.bytesPerRow)) image = drawingContext.generateImage() } + } else if let videoSource = videoSource { + if let frame = videoSource.takeFrame(draw: true) { + if let drawingContext = DrawingContext(size: CGSize(width: frame.width, height: frame.height), scale: 1.0, opaque: false, clear: false, bytesPerRow: frame.bytesPerRow) { + frame.data.copyBytes(to: drawingContext.bytes.assumingMemoryBound(to: UInt8.self), from: 0 ..< min(frame.data.count, drawingContext.length)) + + image = drawingContext.generateImage() + } + } } } diff --git a/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift b/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift index 3d8516d823..3331868289 100644 --- a/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift +++ b/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift @@ -345,7 +345,9 @@ final class VideoStickerDirectFrameSource: AnimatedStickerFrameSource { self.currentFrame += 1 if draw { if let image = self.image { - let context = DrawingContext(size: CGSize(width: self.width, height: self.height), scale: 1.0, opaque: false, clear: true, bytesPerRow: self.bytesPerRow) + guard let context = DrawingContext(size: CGSize(width: self.width, height: self.height), scale: 1.0, opaque: false, clear: true, bytesPerRow: self.bytesPerRow) else { + return nil + } context.withFlippedContext { c in c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: context.size)) } diff --git a/submodules/AvatarNode/Sources/PeerAvatar.swift b/submodules/AvatarNode/Sources/PeerAvatar.swift index 3ac553f992..a9b5701f9f 100644 --- a/submodules/AvatarNode/Sources/PeerAvatar.swift +++ b/submodules/AvatarNode/Sources/PeerAvatar.swift @@ -177,25 +177,26 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut } if shouldBlur { let imageContextSize = size.width > 200.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0) - let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, clear: true) - imageContext.withFlippedContext { c in - c.draw(dataImage, in: CGRect(origin: CGPoint(), size: imageContextSize)) + if let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, clear: true) { + imageContext.withFlippedContext { c in + c.draw(dataImage, in: CGRect(origin: CGPoint(), size: imageContextSize)) + + context.setBlendMode(.saturation) + context.setFillColor(UIColor(rgb: 0xffffff, alpha: 1.0).cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + context.setBlendMode(.copy) + } - context.setBlendMode(.saturation) - context.setFillColor(UIColor(rgb: 0xffffff, alpha: 1.0).cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - context.setBlendMode(.copy) + telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) + if size.width > 200.0 { + telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) + telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) + telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) + telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) + } + + dataImage = imageContext.generateImage()!.cgImage! } - - telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) - if size.width > 200.0 { - telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) - telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) - telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) - telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes) - } - - dataImage = imageContext.generateImage()!.cgImage! } context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset)) diff --git a/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift b/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift index eac694d4dd..413f41ec20 100644 --- a/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift +++ b/submodules/Components/ReactionImageComponent/Sources/ReactionImageComponent.swift @@ -45,16 +45,17 @@ public func reactionStaticImage(context: AccountContext, animation: TelegramMedi } func add(with drawingBlock: (AnimationCacheItemDrawingSurface) -> Double?, proposedWidth: Int, proposedHeight: Int, insertKeyframe: Bool) { - let renderContext = DrawingContext(size: CGSize(width: proposedWidth, height: proposedHeight), scale: 1.0, clear: true) - let _ = drawingBlock(AnimationCacheItemDrawingSurface( - argb: renderContext.bytes.assumingMemoryBound(to: UInt8.self), - width: Int(renderContext.scaledSize.width), - height: Int(renderContext.scaledSize.height), - bytesPerRow: renderContext.bytesPerRow, - length: renderContext.length - )) - if let image = renderContext.generateImage() { - self.frameReceived(image) + if let renderContext = DrawingContext(size: CGSize(width: proposedWidth, height: proposedHeight), scale: 1.0, clear: true) { + let _ = drawingBlock(AnimationCacheItemDrawingSurface( + argb: renderContext.bytes.assumingMemoryBound(to: UInt8.self), + width: Int(renderContext.scaledSize.width), + height: Int(renderContext.scaledSize.height), + bytesPerRow: renderContext.bytesPerRow, + length: renderContext.length + )) + if let image = renderContext.generateImage() { + self.frameReceived(image) + } } } diff --git a/submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift b/submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift index 0b71087ed0..57ee73149c 100644 --- a/submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift +++ b/submodules/DirectMediaImageCache/Sources/DirectMediaImageCache.swift @@ -12,7 +12,9 @@ import ManagedFile private func generateBlurredThumbnail(image: UIImage) -> UIImage? { let thumbnailContextSize = CGSize(width: 32.0, height: 32.0) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else { + return nil + } let filledSize = image.size.aspectFilled(thumbnailContextSize) let imageRect = CGRect(origin: CGPoint(x: (thumbnailContextSize.width - filledSize.width) / 2.0, y: (thumbnailContextSize.height - filledSize.height) / 2.0), size: filledSize) @@ -129,7 +131,9 @@ private func loadImage(data: Data) -> UIImage? { source.rowBytes = Int(width * 2) source.data = UnsafeMutableRawPointer(mutating: sourceBytes.advanced(by: 4 + 2 + 2)) - let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: true, clear: false) + guard let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: true, clear: false) else { + return nil + } var target = vImage_Buffer() target.width = UInt(width) @@ -218,7 +222,11 @@ public final class DirectMediaImageCache { let data = dataSignal.start(next: { data in if let data = data, let image = UIImage(data: data) { let scaledSize = CGSize(width: CGFloat(width), height: CGFloat(width)) - let scaledContext = DrawingContext(size: scaledSize, scale: 1.0, opaque: true) + guard let scaledContext = DrawingContext(size: scaledSize, scale: 1.0, opaque: true) else { + subscriber.putNext(nil) + subscriber.putCompletion() + return + } scaledContext.withFlippedContext { context in let filledSize = image.size.aspectFilled(scaledSize) let imageRect = CGRect(origin: CGPoint(x: (scaledSize.width - filledSize.width) / 2.0, y: (scaledSize.height - filledSize.height) / 2.0), size: filledSize) diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift index 397b4f2a64..97cd118f4d 100644 --- a/submodules/Display/Source/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -21,7 +21,9 @@ private let grayscaleColorSpace = CGColorSpaceCreateDeviceGray() let deviceScale = UIScreen.main.scale public func generateImagePixel(_ size: CGSize, scale: CGFloat, pixelGenerator: (CGSize, UnsafeMutablePointer, Int) -> Void) -> UIImage? { - let context = DrawingContext(size: size, scale: scale, opaque: false, clear: false) + guard let context = DrawingContext(size: size, scale: scale, opaque: false, clear: false) else { + return nil + } pixelGenerator(CGSize(width: size.width * scale, height: size.height * scale), context.bytes.assumingMemoryBound(to: UInt8.self), context.bytesPerRow) return context.generateImage() } @@ -98,7 +100,9 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) if size.width.isZero || size.height.isZero { return nil } - let context = DrawingContext(size: size, scale: scale ?? 0.0, opaque: opaque, clear: false) + guard let context = DrawingContext(size: size, scale: scale ?? 0.0, opaque: opaque, clear: false) else { + return nil + } context.withFlippedContext { c in contextGenerator(context.size, c) } @@ -109,7 +113,9 @@ public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat? if size.width.isZero || size.height.isZero { return nil } - let context = DrawingContext(size: size, scale: scale ?? 0.0, opaque: opaque, clear: false) + guard let context = DrawingContext(size: size, scale: scale ?? 0.0, opaque: opaque, clear: false) else { + return nil + } context.withContext { c in rotatedContext(context.size, c) } @@ -560,7 +566,11 @@ public class DrawingContext { f(self.context) } - public init(size: CGSize, scale: CGFloat = 0.0, opaque: Bool = false, clear: Bool = false, bytesPerRow: Int? = nil) { + public init?(size: CGSize, scale: CGFloat = 0.0, opaque: Bool = false, clear: Bool = false, bytesPerRow: Int? = nil) { + if size.width <= 0.0 || size.height <= 0.0 { + return nil + } + assert(!size.width.isZero && !size.height.isZero) let size: CGSize = CGSize(width: max(1.0, size.width), height: max(1.0, size.height)) @@ -585,7 +595,7 @@ public class DrawingContext { self.bitmapInfo = DeviceGraphicsContextSettings.shared.transparentBitmapInfo } - self.context = CGContext( + guard let context = CGContext( data: self.imageBuffer.mutableBytes, width: Int(self.scaledSize.width), height: Int(self.scaledSize.height), @@ -595,7 +605,10 @@ public class DrawingContext { bitmapInfo: self.bitmapInfo.rawValue, releaseCallback: nil, releaseInfo: nil - )! + ) else { + return nil + } + self.context = context self.context.scaleBy(x: self.scale, y: self.scale) if clear { diff --git a/submodules/Display/Source/ImageCorners.swift b/submodules/Display/Source/ImageCorners.swift index 62dbc83251..d18ffc8cff 100644 --- a/submodules/Display/Source/ImageCorners.swift +++ b/submodules/Display/Source/ImageCorners.swift @@ -44,7 +44,7 @@ private func cornerContext(_ corner: Corner) -> DrawingContext { if let cached = cached { return cached } else { - let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true) + let context = DrawingContext(size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)), clear: true)! context.withContext { c in c.clear(CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(corner.radius), height: CGFloat(corner.radius)))) diff --git a/submodules/Display/Source/StatusBarProxyNode.swift b/submodules/Display/Source/StatusBarProxyNode.swift index cb29181ec1..f8104f9ce8 100644 --- a/submodules/Display/Source/StatusBarProxyNode.swift +++ b/submodules/Display/Source/StatusBarProxyNode.swift @@ -86,7 +86,9 @@ private class StatusBarItemNode: ASDisplayNode { func update() { let containingBounds = maxSubviewBounds(self.targetView) - let context = DrawingContext(size: containingBounds.size, clear: true) + guard let context = DrawingContext(size: containingBounds.size, clear: true) else { + return + } if let contents = self.targetView.layer.contents, (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents as CFTypeRef) == CGImage.typeID && false { let image = contents as! CGImage diff --git a/submodules/GalleryUI/Sources/GalleryVideoDecoration.swift b/submodules/GalleryUI/Sources/GalleryVideoDecoration.swift index 3a57b5d066..ac93529926 100644 --- a/submodules/GalleryUI/Sources/GalleryVideoDecoration.swift +++ b/submodules/GalleryUI/Sources/GalleryVideoDecoration.swift @@ -51,7 +51,9 @@ public final class GalleryVideoDecoration: UniversalVideoDecoration { let boundingSize: CGSize = CGSize(width: max(corners.topLeft.radius, corners.bottomLeft.radius) + max(corners.topRight.radius, corners.bottomRight.radius), height: max(corners.topLeft.radius, corners.topRight.radius) + max(corners.bottomLeft.radius, corners.bottomRight.radius)) let size: CGSize = CGSize(width: boundingSize.width + corners.extendedEdges.left + corners.extendedEdges.right, height: boundingSize.height + corners.extendedEdges.top + corners.extendedEdges.bottom) let arguments = TransformImageArguments(corners: corners, imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) - let context = DrawingContext(size: size, clear: true) + guard let context = DrawingContext(size: size, clear: true) else { + return + } context.withContext { ctx in ctx.setFillColor(UIColor.black.cgColor) ctx.fill(arguments.drawingRect) diff --git a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift index ae33e87612..59b5e3965f 100644 --- a/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift +++ b/submodules/GradientBackground/Sources/SoftwareGradientBackground.swift @@ -103,7 +103,7 @@ private func generateGradient(size: CGSize, colors inputColors: [UIColor], posit positionFloats.advanced(by: i * 2 + 1).pointee = Float(1.0 - positions[i].y) } - let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: true, clear: false) + let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: true, clear: false)! let imageBytes = context.bytes.assumingMemoryBound(to: UInt8.self) for y in 0 ..< height { diff --git a/submodules/ImageTransparency/Sources/ImageTransparency.swift b/submodules/ImageTransparency/Sources/ImageTransparency.swift index 8f73dcbd9e..54277c8e66 100644 --- a/submodules/ImageTransparency/Sources/ImageTransparency.swift +++ b/submodules/ImageTransparency/Sources/ImageTransparency.swift @@ -61,12 +61,14 @@ public func imageHasTransparency(_ cgImage: CGImage) -> Bool { return false } -private func scaledDrawingContext(_ cgImage: CGImage, maxSize: CGSize) -> DrawingContext { +private func scaledDrawingContext(_ cgImage: CGImage, maxSize: CGSize) -> DrawingContext? { var size = CGSize(width: cgImage.width, height: cgImage.height) if (size.width > maxSize.width && size.height > maxSize.height) { size = size.aspectFilled(maxSize) } - let context = DrawingContext(size: size, scale: 1.0, clear: true) + guard let context = DrawingContext(size: size, scale: 1.0, clear: true) else { + return nil + } context.withFlippedContext { context in context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size)) } @@ -81,7 +83,9 @@ public func imageRequiresInversion(_ cgImage: CGImage) -> Bool { return false } - let context = scaledDrawingContext(cgImage, maxSize: CGSize(width: 128.0, height: 128.0)) + guard let context = scaledDrawingContext(cgImage, maxSize: CGSize(width: 128.0, height: 128.0)) else { + return false + } if let cgImage = context.generateImage()?.cgImage, let (histogramBins, alphaBinIndex) = generateHistogram(cgImage: cgImage) { var hasAlpha = false for i in 0 ..< 255 { @@ -92,7 +96,9 @@ public func imageRequiresInversion(_ cgImage: CGImage) -> Bool { } if hasAlpha { - let probingContext = DrawingContext(size: CGSize(width: cgImage.width, height: cgImage.height)) + guard let probingContext = DrawingContext(size: CGSize(width: cgImage.width, height: cgImage.height)) else { + return false + } probingContext.withContext { c in c.draw(cgImage, in: CGRect(origin: CGPoint(), size: probingContext.size)) } diff --git a/submodules/LocationResources/Sources/MapResources.swift b/submodules/LocationResources/Sources/MapResources.swift index 259388cd80..08de2db8ef 100644 --- a/submodules/LocationResources/Sources/MapResources.swift +++ b/submodules/LocationResources/Sources/MapResources.swift @@ -113,7 +113,9 @@ public func chatMapSnapshotImage(engine: TelegramEngine, resource: MapSnapshotMe return signal |> map { fullSizeData in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } var fullSizeImage: CGImage? if let fullSizeData = fullSizeData { diff --git a/submodules/LocationResources/Sources/VenueIconResources.swift b/submodules/LocationResources/Sources/VenueIconResources.swift index 08ce086052..16d1ed09c2 100644 --- a/submodules/LocationResources/Sources/VenueIconResources.swift +++ b/submodules/LocationResources/Sources/VenueIconResources.swift @@ -139,7 +139,9 @@ public func venueIcon(engine: TelegramEngine, type: String, background: Bool) -> let data: Signal = isBuiltinIcon ? .single(nil) : venueIconData(engine: engine, resource: VenueIconResource(type: type)) return data |> map { data in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } var iconImage: UIImage? if let data = data, let image = UIImage(data: data) { diff --git a/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift b/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift index c68a5459f9..82517c8ce0 100644 --- a/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift +++ b/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift @@ -61,7 +61,9 @@ public final class ManagedAnimationState { } func draw() -> UIImage? { - let renderContext = DrawingContext(size: self.displaySize, scale: UIScreenScale, clear: true) + guard let renderContext = DrawingContext(size: self.displaySize, scale: UIScreenScale, clear: true) else { + return nil + } self.instance.renderFrame(with: Int32(self.frameIndex ?? 0), into: renderContext.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(renderContext.size.width * renderContext.scale), height: Int32(renderContext.size.height * renderContext.scale), bytesPerRow: Int32(renderContext.bytesPerRow)) return renderContext.generateImage() diff --git a/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift b/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift index ef28d57c61..9af464ac9d 100644 --- a/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift +++ b/submodules/OpenInExternalAppUI/Sources/OpenInAppIconResources.swift @@ -136,7 +136,9 @@ public func openInAppIcon(engine: TelegramEngine, appIcon: OpenInAppIcon) -> Sig case let .resource(resource): return openInAppIconData(engine: engine, appIcon: resource) |> map { data in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } var sourceImage: UIImage? if let data = data, let image = UIImage(data: data) { @@ -161,7 +163,9 @@ public func openInAppIcon(engine: TelegramEngine, appIcon: OpenInAppIcon) -> Sig } case let .image(image): return .single({ arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } context.withFlippedContext { c in c.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: arguments.drawingSize)) diff --git a/submodules/PasscodeUI/Sources/PasscodeBackground.swift b/submodules/PasscodeUI/Sources/PasscodeBackground.swift index aff1c5812a..c3901f496a 100644 --- a/submodules/PasscodeUI/Sources/PasscodeBackground.swift +++ b/submodules/PasscodeUI/Sources/PasscodeBackground.swift @@ -88,7 +88,7 @@ final class ImageBasedPasscodeBackground: PasscodeBackground { self.size = size let contextSize = size.aspectFilled(CGSize(width: 320.0, height: 320.0)) - let foregroundContext = DrawingContext(size: contextSize, scale: 1.0) + let foregroundContext = DrawingContext(size: contextSize, scale: 1.0)! let bounds = CGRect(origin: CGPoint(), size: contextSize) let filledImageSize = image.size.aspectFilled(contextSize) @@ -109,7 +109,7 @@ final class ImageBasedPasscodeBackground: PasscodeBackground { } self.foregroundImage = foregroundContext.generateImage()! - let backgroundContext = DrawingContext(size: contextSize, scale: 1.0) + let backgroundContext = DrawingContext(size: contextSize, scale: 1.0)! backgroundContext.withFlippedContext { c in c.interpolationQuality = .medium c.draw(image.cgImage!, in: filledImageRect) diff --git a/submodules/Pdf/Sources/PDF.swift b/submodules/Pdf/Sources/PDF.swift index 08cc6438f7..0a4aed234e 100644 --- a/submodules/Pdf/Sources/PDF.swift +++ b/submodules/Pdf/Sources/PDF.swift @@ -12,7 +12,9 @@ public func generatePdfPreviewImage(data: Data, size: CGSize) -> UIImage? { guard let document = CGPDFDocument(provider) else { return nil } guard let firstPage = document.page(at: 1) else { return nil } - let context = DrawingContext(size: size) + guard let context = DrawingContext(size: size) else { + return nil + } context.withContext { c in var pageRect = firstPage.getBoxRect(.mediaBox) let pdfScale = 320.0 / pageRect.size.width diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index c26cc9c06d..22d5fca5e4 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -663,36 +663,40 @@ public func chatMessagePhotoInternal(photoData: Signal thumbnailContextSize.width { - let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) - additionalBlurContext.withFlippedContext { c in - c.interpolationQuality = .default - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) - } + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + if thumbnailContextFittingSize.width > thumbnailContextSize.width { + let additionalContextSize = thumbnailContextFittingSize + if let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) { + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } + } + imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + blurredThumbnailImage = additionalBlurContext.generateImage() + } + } else { + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) - blurredThumbnailImage = additionalBlurContext.generateImage() - } else { - blurredThumbnailImage = thumbnailContext.generateImage() } } } if let blurredThumbnailImage = blurredThumbnailImage, fullSizeImage == nil, arguments.corners.isEmpty { - let context = DrawingContext(size: blurredThumbnailImage.size, scale: blurredThumbnailImage.scale, clear: true) + guard let context = DrawingContext(size: blurredThumbnailImage.size, scale: blurredThumbnailImage.scale, clear: true) else { + return nil + } context.withFlippedContext { c in c.setBlendMode(.copy) if let cgImage = blurredThumbnailImage.cgImage { @@ -704,7 +708,9 @@ public func chatMessagePhotoInternal(photoData: Signal thumbnailContextSize.width { let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) - additionalBlurContext.withFlippedContext { c in - c.interpolationQuality = .default - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + if let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) { + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } } + imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + sideBlurredImage = additionalBlurContext.generateImage() } - imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) - sideBlurredImage = additionalBlurContext.generateImage() } else { sideBlurredImage = thumbnailContext.generateImage() } @@ -845,7 +854,9 @@ public func chatMessagePhotoThumbnail(account: Account, photoReference: ImageMed let fullSizeData = value._1 let fullSizeComplete = value._2 return { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize @@ -890,14 +901,15 @@ public func chatMessagePhotoThumbnail(account: Account, photoReference: ImageMed if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in @@ -936,7 +948,9 @@ public func chatMessageVideoThumbnail(account: Account, fileReference: FileMedia let fullSizeData = value._1 let fullSizeComplete = value._2 return { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize @@ -989,14 +1003,15 @@ public func chatMessageVideoThumbnail(account: Account, fileReference: FileMedia } else { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() } } @@ -1035,7 +1050,9 @@ public func chatSecretPhoto(account: Account, photoReference: ImageMediaReferenc let fullSizeData = value._1 let fullSizeComplete = value._3 return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize @@ -1057,24 +1074,26 @@ public func chatSecretPhoto(account: Account, photoReference: ImageMediaReferenc if let imageSource = CGImageSourceCreateWithData(fullSizeData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) { let thumbnailSize = CGSize(width: image.width, height: image.height) let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 20.0, height: 20.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) - let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) - thumbnailContext2.withFlippedContext { c in - c.interpolationQuality = .none - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) + if let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) { + thumbnailContext2.withFlippedContext { c in + c.interpolationQuality = .none + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size)) + } + } + imageFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes) + + blurredImage = thumbnailContext2.generateImage() } } - imageFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes) - - blurredImage = thumbnailContext2.generateImage() } } } @@ -1083,24 +1102,26 @@ public func chatSecretPhoto(account: Account, photoReference: ImageMediaReferenc if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { let thumbnailSize = CGSize(width: image.width, height: image.height) let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 20.0, height: 20.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) - let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) - thumbnailContext2.withFlippedContext { c in - c.interpolationQuality = .none - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) + if let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) { + thumbnailContext2.withFlippedContext { c in + c.interpolationQuality = .none + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size)) + } + } + imageFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes) + + blurredImage = thumbnailContext2.generateImage() } } - imageFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes) - - blurredImage = thumbnailContext2.generateImage() } } @@ -1211,7 +1232,9 @@ public func avatarGalleryThumbnailPhoto(account: Account, representations: [Imag let fullSizeComplete = value._2 return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) @@ -1250,14 +1273,15 @@ public func avatarGalleryThumbnailPhoto(account: Account, representations: [Imag if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in @@ -1302,7 +1326,9 @@ public func mediaGridMessagePhoto(account: Account, photoReference: ImageMediaRe let fullSizeData = value._1 let fullSizeComplete = value._3 return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) @@ -1344,16 +1370,17 @@ public func mediaGridMessagePhoto(account: Account, photoReference: ImageMediaRe } else { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 90.0, height: 90.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + if !useMiniThumbnailIfAvailable { + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + } + + blurredThumbnailImage = thumbnailContext.generateImage() } - if !useMiniThumbnailIfAvailable { - telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - } - - blurredThumbnailImage = thumbnailContext.generateImage() } } @@ -1405,7 +1432,10 @@ public func gifPaneVideoThumbnail(account: Account, videoReference: FileMediaRef |> map { data in let thumbnailData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } + let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize) @@ -1419,14 +1449,15 @@ public func gifPaneVideoThumbnail(account: Account, videoReference: FileMediaRef if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in @@ -1500,7 +1531,9 @@ public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: File } } - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect var drawingSize: CGSize @@ -1553,30 +1586,32 @@ public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: File let initialThumbnailContextFittingSize = drawingSize.fitted(CGSize(width: 90.0, height: 90.0)) let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) - if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { - thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) - } - - if thumbnailContextFittingSize.width > thumbnailContextSize.width { - let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) - additionalBlurContext.withFlippedContext { c in - c.interpolationQuality = .default - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) - } + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + if thumbnailContextFittingSize.width > thumbnailContextSize.width { + let additionalContextSize = thumbnailContextFittingSize + if let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) { + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } + } + imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + blurredThumbnailImage = additionalBlurContext.generateImage() + } + } else { + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) - blurredThumbnailImage = additionalBlurContext.generateImage() - } else { - blurredThumbnailImage = thumbnailContext.generateImage() } } } @@ -1594,41 +1629,43 @@ public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: File let initialThumbnailContextFittingSize = drawingSize.fitted(CGSize(width: 100.0, height: 100.0)) let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) - if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { - thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) - } - - if thumbnailContextFittingSize.width > thumbnailContextSize.width { - let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) - additionalBlurContext.withFlippedContext { c in - c.interpolationQuality = .default - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) - } + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + if thumbnailContextFittingSize.width > thumbnailContextSize.width { + let additionalContextSize = thumbnailContextFittingSize + if let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) { + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } + } + imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + sideBlurredImage = additionalBlurContext.generateImage() + } + } else { + sideBlurredImage = thumbnailContext.generateImage() + } + + if let blurredImage = sideBlurredImage { + let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size) + c.interpolationQuality = .medium + c.draw(blurredImage.cgImage!, in: CGRect(origin: CGPoint(x: arguments.drawingRect.minX + (arguments.drawingRect.width - filledSize.width) / 2.0, y: arguments.drawingRect.minY + (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) + c.setBlendMode(.normal) + c.setFillColor((arguments.emptyColor ?? UIColor.white).withAlphaComponent(0.05).cgColor) + c.fill(arguments.drawingRect) + c.setBlendMode(.copy) } - imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) - sideBlurredImage = additionalBlurContext.generateImage() - } else { - sideBlurredImage = thumbnailContext.generateImage() - } - - if let blurredImage = sideBlurredImage { - let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size) - c.interpolationQuality = .medium - c.draw(blurredImage.cgImage!, in: CGRect(origin: CGPoint(x: arguments.drawingRect.minX + (arguments.drawingRect.width - filledSize.width) / 2.0, y: arguments.drawingRect.minY + (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize)) - c.setBlendMode(.normal) - c.setFillColor((arguments.emptyColor ?? UIColor.white).withAlphaComponent(0.05).cgColor) - c.fill(arguments.drawingRect) - c.setBlendMode(.copy) } } else { c.fill(arguments.drawingRect) @@ -1817,7 +1854,9 @@ public func chatWebpageSnippetFile(account: Account, mediaReference: AnyMediaRef } if let fullSizeImage = fullSizeImage { - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let fittedSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height).aspectFilled(arguments.boundingSize) let drawingRect = arguments.drawingRect @@ -1844,7 +1883,9 @@ public func chatWebpageSnippetFile(account: Account, mediaReference: AnyMediaRef return context } else { if let emptyColor = arguments.emptyColor { - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } context.withFlippedContext { c in c.setBlendMode(.copy) @@ -1879,7 +1920,9 @@ public func chatWebpageSnippetPhoto(account: Account, photoReference: ImageMedia } if let fullSizeImage = fullSizeImage { - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) else { + return nil + } let fittedSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height).aspectFilled(arguments.boundingSize) let drawingRect = arguments.drawingRect @@ -1942,9 +1985,12 @@ public func chatSecretMessageVideo(account: Account, videoReference: FileMediaRe return signal |> map { thumbnailData in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } + if arguments.drawingSize.width.isLessThanOrEqualTo(0.0) || arguments.drawingSize.height.isLessThanOrEqualTo(0.0) { - return context + return nil } let drawingRect = arguments.drawingRect @@ -1957,24 +2003,26 @@ public func chatSecretMessageVideo(account: Account, videoReference: FileMediaRe if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { let thumbnailSize = CGSize(width: image.width, height: image.height) let thumbnailContextSize = thumbnailSize.aspectFilled(CGSize(width: 20.0, height: 20.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) - let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) - thumbnailContext2.withFlippedContext { c in - c.interpolationQuality = .none - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + let thumbnailContext2Size = thumbnailSize.aspectFitted(CGSize(width: 100.0, height: 100.0)) + if let thumbnailContext2 = DrawingContext(size: thumbnailContext2Size, scale: 1.0) { + thumbnailContext2.withFlippedContext { c in + c.interpolationQuality = .none + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContext2Size)) + } + } + imageFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes) + + blurredImage = thumbnailContext2.generateImage() } } - imageFastBlur(Int32(thumbnailContext2Size.width), Int32(thumbnailContext2Size.height), Int32(thumbnailContext2.bytesPerRow), thumbnailContext2.bytes) - - blurredImage = thumbnailContext2.generateImage() } } @@ -2105,7 +2153,9 @@ public func chatMessageImageFile(account: Account, fileReference: FileMediaRefer let fullSizeComplete = value._2 return { arguments in assertNotOnMainThread() - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect var fittedSize: CGSize @@ -2164,31 +2214,33 @@ public func chatMessageImageFile(account: Account, fileReference: FileMediaRefer let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 100.0, height: 100.0)) let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: clearContext) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) - if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { - thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) - } - - if thumbnailContextFittingSize.width > thumbnailContextSize.width { - let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0, clear: clearContext) - additionalBlurContext.withFlippedContext { c in - c.interpolationQuality = .default - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) - } + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: clearContext) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + if thumbnailContextFittingSize.width > thumbnailContextSize.width { + let additionalContextSize = thumbnailContextFittingSize + if let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0, clear: clearContext) { + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } + } + imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + blurredThumbnailImage = additionalBlurContext.generateImage() + } + } else { + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) - blurredThumbnailImage = additionalBlurContext.generateImage() - } else { - blurredThumbnailImage = thumbnailContext.generateImage() } } } @@ -2231,7 +2283,9 @@ public func instantPageImageFile(account: Account, fileReference: FileMediaRefer let fullSizeComplete = value._2 return { arguments in assertNotOnMainThread() - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) @@ -2290,7 +2344,9 @@ public func svgIconImageFile(account: Account, fileReference: FileMediaReference let fullSizePath = value.path let fullSizeComplete = value.complete return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect var fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) @@ -2473,35 +2529,39 @@ public func chatAvatarGalleryPhoto(account: Account, representations: [ImageRepr let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 90.0, height: 90.0)) let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) - } - telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) - if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { - thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) - } - - if thumbnailContextFittingSize.width > thumbnailContextSize.width { - let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) - additionalBlurContext.withFlippedContext { c in - c.interpolationQuality = .default - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) - } + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + if thumbnailContextFittingSize.width > thumbnailContextSize.width { + let additionalContextSize = thumbnailContextFittingSize + if let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) { + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } + } + imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + blurredThumbnailImage = additionalBlurContext.generateImage() + } + } else { + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) - blurredThumbnailImage = additionalBlurContext.generateImage() - } else { - blurredThumbnailImage = thumbnailContext.generateImage() } } } - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } context.withFlippedContext { c in c.setBlendMode(.copy) @@ -2533,7 +2593,9 @@ public func chatWebFileImage(account: Account, file: TelegramMediaWebFile) -> Si return account.postbox.mediaBox.resourceData(file.resource) |> map { fullSizeData in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } var fullSizeImage: CGImage? var imageOrientation: UIImage.Orientation = .up @@ -2761,7 +2823,9 @@ public func playerAlbumArt(postbox: Postbox, engine: TelegramEngine, fileReferen let remoteFullSizeData = remoteArtworkData._1 let remoteFullSizeComplete = remoteArtworkData._2 return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } var sourceImage: UIImage? if let fileArtworkData = fileArtworkData, let image = UIImage(data: fileArtworkData) { @@ -2857,7 +2921,9 @@ public func securePhotoInternal(account: Account, resource: TelegramMediaResourc } if let fullSizeImage = fullSizeImage { - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let fittedSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height).aspectFilled(arguments.boundingSize) let drawingRect = arguments.drawingRect @@ -2886,7 +2952,9 @@ public func securePhotoInternal(account: Account, resource: TelegramMediaResourc public func callDefaultBackground() -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return .single({ arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } context.withFlippedContext { c in let colors = [UIColor(rgb: 0x466f92).cgColor, UIColor(rgb: 0x244f74).cgColor] var locations: [CGFloat] = [1.0, 0.0] diff --git a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift index a26da3191d..5ffa1162f2 100644 --- a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift +++ b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift @@ -568,7 +568,9 @@ private final class VideoDecoration: UniversalVideoDecoration { let boundingSize: CGSize = CGSize(width: max(corners.topLeft.radius, corners.bottomLeft.radius) + max(corners.topRight.radius, corners.bottomRight.radius), height: max(corners.topLeft.radius, corners.topRight.radius) + max(corners.bottomLeft.radius, corners.bottomRight.radius)) let size: CGSize = CGSize(width: boundingSize.width + corners.extendedEdges.left + corners.extendedEdges.right, height: boundingSize.height + corners.extendedEdges.top + corners.extendedEdges.bottom) let arguments = TransformImageArguments(corners: corners, imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) - let context = DrawingContext(size: size, clear: true) + guard let context = DrawingContext(size: size, clear: true) else { + return + } context.withContext { ctx in ctx.setFillColor(UIColor.black.cgColor) ctx.fill(arguments.drawingRect) diff --git a/submodules/QrCode/Sources/QrCode.swift b/submodules/QrCode/Sources/QrCode.swift index eb3cb3c082..ba0074e0e7 100644 --- a/submodules/QrCode/Sources/QrCode.swift +++ b/submodules/QrCode/Sources/QrCode.swift @@ -70,7 +70,9 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n } |> map { data, size, bytesPerRow in return (size, { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize) @@ -105,7 +107,9 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n } let squareSize = CGSize(width: side, height: side) - let tmpContext = DrawingContext(size: CGSize(width: squareSize.width * 4.0, height: squareSize.height), scale: arguments.scale ?? 0.0, clear: true) + guard let tmpContext = DrawingContext(size: CGSize(width: squareSize.width * 4.0, height: squareSize.height), scale: arguments.scale ?? 0.0, clear: true) else { + return nil + } tmpContext.withContext { c in if let backgroundColor = backgroundColor { c.setFillColor(backgroundColor.cgColor) diff --git a/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift b/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift index 168098f8c2..abec3b6999 100644 --- a/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift +++ b/submodules/SettingsUI/Sources/Themes/SettingsThemeWallpaperNode.swift @@ -13,7 +13,9 @@ import GradientBackground private func whiteColorImage(theme: PresentationTheme, color: UIColor) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return .single({ arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } context.withFlippedContext { c in c.setFillColor(color.cgColor) @@ -30,7 +32,9 @@ private func whiteColorImage(theme: PresentationTheme, color: UIColor) -> Signal } private let blackColorImage: UIImage? = { - let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, opaque: true, clear: false) + guard let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, opaque: true, clear: false) else { + return nil + } context.withContext { c in c.setFillColor(UIColor.black.cgColor) c.fill(CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0))) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift index a868b85e56..81c8810792 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsThemeItem.swift @@ -172,7 +172,9 @@ private func createThemeImage(theme: PresentationTheme) -> Signal<(TransformImag return .single(theme) |> map { theme -> (TransformImageArguments) -> DrawingContext? in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect context.withContext { c in diff --git a/submodules/StickerResources/Sources/StickerResources.swift b/submodules/StickerResources/Sources/StickerResources.swift index 3c3f88d1d6..4e89fa8e74 100644 --- a/submodules/StickerResources/Sources/StickerResources.swift +++ b/submodules/StickerResources/Sources/StickerResources.swift @@ -282,7 +282,9 @@ public func chatMessageLegacySticker(account: Account, file: TelegramMediaFile, let arguments = TransformImageArguments(corners: preArguments.corners, imageSize: contextSize, boundingSize: contextSize, intrinsicInsets: preArguments.intrinsicInsets) - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let thumbnailImage: CGImage? = nil @@ -290,14 +292,15 @@ public func chatMessageLegacySticker(account: Account, file: TelegramMediaFile, if let thumbnailImage = thumbnailImage { let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) - thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.interpolationQuality = .none + c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() } - imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in @@ -341,7 +344,9 @@ public func chatMessageStickerPackThumbnail(postbox: Postbox, resource: MediaRes } } - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) else { + return nil + } let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize @@ -403,7 +408,9 @@ public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small: return nil } - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) else { + return nil + } let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize @@ -432,20 +439,21 @@ public func chatMessageSticker(postbox: Postbox, file: TelegramMediaFile, small: let thumbnailDrawingSize = thumbnailContextSize thumbnailContextSize.width += thumbnailInset * 2.0 thumbnailContextSize.height += thumbnailInset * 2.0 - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) - thumbnailContext.withFlippedContext { c in - if let cgImage = thumbnailImage.0.cgImage, let cgImageAlpha = thumbnailImage.1.cgImage { - c.setBlendMode(.normal) - c.interpolationQuality = .medium - - let mask = CGImage(maskWidth: cgImageAlpha.width, height: cgImageAlpha.height, bitsPerComponent: cgImageAlpha.bitsPerComponent, bitsPerPixel: cgImageAlpha.bitsPerPixel, bytesPerRow: cgImageAlpha.bytesPerRow, provider: cgImageAlpha.dataProvider!, decode: nil, shouldInterpolate: true) - - c.draw(cgImage.masking(mask!)!, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) { + thumbnailContext.withFlippedContext { c in + if let cgImage = thumbnailImage.0.cgImage, let cgImageAlpha = thumbnailImage.1.cgImage { + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + let mask = CGImage(maskWidth: cgImageAlpha.width, height: cgImageAlpha.height, bitsPerComponent: cgImageAlpha.bitsPerComponent, bitsPerPixel: cgImageAlpha.bitsPerPixel, bytesPerRow: cgImageAlpha.bytesPerRow, provider: cgImageAlpha.dataProvider!, decode: nil, shouldInterpolate: true) + + c.draw(cgImage.masking(mask!)!, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + } } + stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() } - stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in @@ -498,7 +506,9 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile return nil } - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect let fittedSize = arguments.imageSize @@ -526,20 +536,21 @@ public func chatMessageAnimatedSticker(postbox: Postbox, file: TelegramMediaFile let thumbnailDrawingSize = thumbnailContextSize thumbnailContextSize.width += thumbnailInset * 2.0 thumbnailContextSize.height += thumbnailInset * 2.0 - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) - thumbnailContext.withFlippedContext { c in - if let cgImage = thumbnailImage.0.cgImage, let cgImageAlpha = thumbnailImage.1.cgImage { - c.setBlendMode(.normal) - c.interpolationQuality = .medium - - let mask = CGImage(maskWidth: cgImageAlpha.width, height: cgImageAlpha.height, bitsPerComponent: cgImageAlpha.bitsPerComponent, bitsPerPixel: cgImageAlpha.bitsPerPixel, bytesPerRow: cgImageAlpha.bytesPerRow, provider: cgImageAlpha.dataProvider!, decode: nil, shouldInterpolate: true) - - c.draw(cgImage.masking(mask!)!, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) { + thumbnailContext.withFlippedContext { c in + if let cgImage = thumbnailImage.0.cgImage, let cgImageAlpha = thumbnailImage.1.cgImage { + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + let mask = CGImage(maskWidth: cgImageAlpha.width, height: cgImageAlpha.height, bitsPerComponent: cgImageAlpha.bitsPerComponent, bitsPerPixel: cgImageAlpha.bitsPerPixel, bytesPerRow: cgImageAlpha.bytesPerRow, provider: cgImageAlpha.dataProvider!, decode: nil, shouldInterpolate: true) + + c.draw(cgImage.masking(mask!)!, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + } } + stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + blurredThumbnailImage = thumbnailContext.generateImage() } - stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) - - blurredThumbnailImage = thumbnailContext.generateImage() } context.withFlippedContext { c in diff --git a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift index 176236668a..33cc6c6bf2 100644 --- a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift +++ b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift @@ -35,7 +35,10 @@ public func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzM return } - let context = DrawingContext(size: size, scale: 1.0, clear: true) + guard let context = DrawingContext(size: size, scale: 1.0, clear: true) else { + subscriber.putCompletion() + return + } player.renderFrame(with: 0, into: context.bytes.assumingMemoryBound(to: UInt8.self), width: Int32(size.width), height: Int32(size.height), bytesPerRow: Int32(context.bytesPerRow)) let yuvaPixelsPerAlphaRow = (Int(size.width) + 1) & (~1) @@ -53,7 +56,9 @@ public func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzM decodeYUVAToRGBA(yuvaFrameData.assumingMemoryBound(to: UInt8.self), context.bytes.assumingMemoryBound(to: UInt8.self), Int32(size.width), Int32(size.height), Int32(context.bytesPerRow)) if let colorSourceImage = context.generateImage(), let alphaImage = generateGrayscaleAlphaMaskImage(image: colorSourceImage) { - let colorContext = DrawingContext(size: size, scale: 1.0, clear: false) + guard let colorContext = DrawingContext(size: size, scale: 1.0, clear: false) else { + return + } colorContext.withFlippedContext { c in c.setFillColor(UIColor.black.cgColor) c.fill(CGRect(origin: CGPoint(), size: size)) diff --git a/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift b/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift index 9ae6a5dd69..6da2641fe0 100644 --- a/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift +++ b/submodules/TelegramPresentationData/Sources/ChatMessageBubbleImages.swift @@ -32,7 +32,7 @@ func mediaBubbleCornerImage(incoming: Bool, radius: CGFloat, inset: CGFloat) -> let imageSize = CGSize(width: radius + 7.0, height: 8.0) let fixedMainDiameter: CGFloat = 33.0 - let formContext = DrawingContext(size: imageSize) + let formContext = DrawingContext(size: imageSize)! formContext.withFlippedContext { context in context.clear(CGRect(origin: CGPoint(), size: imageSize)) context.translateBy(x: imageSize.width / 2.0, y: imageSize.height / 2.0) @@ -205,7 +205,7 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa let bottomEllipse = CGRect(origin: CGPoint(x: 24.0, y: 16.0), size: CGSize(width: 27.0, height: 17.0)) let topEllipse = CGRect(origin: CGPoint(x: 33.0, y: 14.0), size: CGSize(width: 23.0, height: 21.0)) - let formContext = DrawingContext(size: imageSize) + let formContext = DrawingContext(size: imageSize)! formContext.withFlippedContext { context in context.clear(CGRect(origin: CGPoint(), size: rawSize)) context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset) @@ -240,7 +240,7 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa } let formImage = formContext.generateImage()! - let outlineContext = DrawingContext(size: imageSize) + let outlineContext = DrawingContext(size: imageSize)! outlineContext.withFlippedContext { context in context.clear(CGRect(origin: CGPoint(), size: rawSize)) context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset) @@ -298,7 +298,7 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa context.addLine(to: CGPoint(x: fixedMainDiameter + borderOffset, y: outlineBottomEllipse.midY)) context.strokePath() - let bubbleTailContext = DrawingContext(size: imageSize) + let bubbleTailContext = DrawingContext(size: imageSize)! bubbleTailContext.withFlippedContext { context in context.clear(CGRect(origin: CGPoint(), size: rawSize)) context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset) @@ -358,7 +358,7 @@ public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloa context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) })! - let drawingContext = DrawingContext(size: imageSize) + let drawingContext = DrawingContext(size: imageSize)! drawingContext.withFlippedContext { context in if onlyShadow { context.clear(CGRect(origin: CGPoint(), size: rawSize)) diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index 15b333c5c7..de5a3c55bb 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -444,7 +444,7 @@ private func serviceColor(for data: Signal) -> Signa } public func averageColor(from image: UIImage) -> UIColor { - let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false) + let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)! context.withFlippedContext({ context in if let cgImage = image.cgImage { context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0)) diff --git a/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift b/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift index 7762f7aaa4..96b56e41c0 100644 --- a/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift +++ b/submodules/TelegramUI/Components/LottieAnimationCache/Sources/LottieAnimationCache.swift @@ -50,7 +50,9 @@ public func cacheStillSticker(path: String, width: Int, height: Int, writer: Ani let work: () -> Void = { if let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let image = WebP.convert(fromWebP: data) { writer.add(with: { surface in - let context = DrawingContext(size: CGSize(width: CGFloat(surface.width), height: CGFloat(surface.height)), scale: 1.0, opaque: false, clear: true, bytesPerRow: surface.bytesPerRow) + guard let context = DrawingContext(size: CGSize(width: CGFloat(surface.width), height: CGFloat(surface.height)), scale: 1.0, opaque: false, clear: true, bytesPerRow: surface.bytesPerRow) else { + return 1.0 + } context.withFlippedContext { c in UIGraphicsPushContext(c) c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: context.size)) diff --git a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift index a9d3598093..6c2b72a6ed 100644 --- a/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift +++ b/submodules/TelegramUI/Components/MultiAnimationRenderer/Sources/MultiAnimationRenderer.swift @@ -106,7 +106,9 @@ private final class ItemAnimationContext { switch frame.format { case let .rgba(data, width, height, bytesPerRow): - let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: false, bytesPerRow: bytesPerRow) + guard let context = DrawingContext(size: CGSize(width: CGFloat(width), height: CGFloat(height)), scale: 1.0, opaque: false, bytesPerRow: bytesPerRow) else { + return nil + } data.withUnsafeBytes { bytes -> Void in memcpy(context.bytes, bytes.baseAddress!, height * bytesPerRow) @@ -133,7 +135,9 @@ private final class ItemAnimationContext { case let .rgba(data, width, height, bytesPerRow): let blurredWidth = 12 let blurredHeight = 12 - let context = DrawingContext(size: CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)), scale: 1.0, opaque: true, bytesPerRow: bytesPerRow) + guard let context = DrawingContext(size: CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)), scale: 1.0, opaque: true, bytesPerRow: bytesPerRow) else { + return nil + } let size = CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)) diff --git a/submodules/TelegramUI/Sources/ChatBotInfoItem.swift b/submodules/TelegramUI/Sources/ChatBotInfoItem.swift index a7850d9fce..fccd6bdaf1 100644 --- a/submodules/TelegramUI/Sources/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/Sources/ChatBotInfoItem.swift @@ -544,7 +544,9 @@ private final class VideoDecoration: UniversalVideoDecoration { let boundingSize: CGSize = CGSize(width: max(corners.topLeft.radius, corners.bottomLeft.radius) + max(corners.topRight.radius, corners.bottomRight.radius), height: max(corners.topLeft.radius, corners.topRight.radius) + max(corners.bottomLeft.radius, corners.bottomRight.radius)) let size: CGSize = CGSize(width: boundingSize.width + corners.extendedEdges.left + corners.extendedEdges.right, height: boundingSize.height + corners.extendedEdges.top + corners.extendedEdges.bottom) let arguments = TransformImageArguments(corners: corners, imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) - let context = DrawingContext(size: size, clear: true) + guard let context = DrawingContext(size: size, clear: true) else { + return + } context.withContext { ctx in ctx.setFillColor(UIColor.black.cgColor) ctx.fill(arguments.drawingRect) diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index 25fcb05613..a4a9f474b4 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -413,7 +413,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio let boundingSize: CGSize = CGSize(width: max(corners.topLeft.radius, corners.bottomLeft.radius) + max(corners.topRight.radius, corners.bottomRight.radius), height: max(corners.topLeft.radius, corners.topRight.radius) + max(corners.bottomLeft.radius, corners.bottomRight.radius)) let size: CGSize = CGSize(width: boundingSize.width + corners.extendedEdges.left + corners.extendedEdges.right, height: boundingSize.height + corners.extendedEdges.top + corners.extendedEdges.bottom) let arguments = TransformImageArguments(corners: corners, imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) - let context = DrawingContext(size: size, clear: true) + guard let context = DrawingContext(size: size, clear: true) else { + return + } context.withContext { ctx in ctx.setFillColor(UIColor.black.cgColor) ctx.fill(arguments.drawingRect) diff --git a/submodules/TelegramUI/Sources/EmojiResources.swift b/submodules/TelegramUI/Sources/EmojiResources.swift index 7b961272ca..5664627054 100644 --- a/submodules/TelegramUI/Sources/EmojiResources.swift +++ b/submodules/TelegramUI/Sources/EmojiResources.swift @@ -264,7 +264,9 @@ func largeEmoji(postbox: Postbox, emoji: String, outline: Bool = true) -> Signal return combineLatest(queue: nil, dataSignals) |> map { datas in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } var sourceImages: [UIImage] = [] for resourceData in datas { diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index e4a1f97b65..525c76dad6 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -533,7 +533,9 @@ private func fetchEmojiThumbnailRepresentation(account: Account, resource: Media textSize = CGSize(width: ceil(textSize.width) + 1.0, height: ceil(textSize.height) + 1.0) let emojiSize = CGSize(width: 52.0, height: 52.0) - let context = DrawingContext(size: emojiSize, clear: true) + guard let context = DrawingContext(size: emojiSize, clear: true) else { + return EmptyDisposable + } context.withFlippedContext { context in let size = textSize let bounds = CGRect(origin: CGPoint(), size: size) @@ -637,7 +639,9 @@ private func fetchEmojiRepresentation(account: Account, resource: MediaResource, let size = CGSize(width: 160.0, height: 160.0) let spacing: CGFloat = 16.0 - let context = DrawingContext(size: size, clear: true) + guard let context = DrawingContext(size: size, clear: true) else { + return EmptyDisposable + } context.withFlippedContext { context in let origin: CGPoint switch representation.tile { diff --git a/submodules/TelegramUI/Sources/SpotlightContacts.swift b/submodules/TelegramUI/Sources/SpotlightContacts.swift index 581686a4bb..54be6ceca6 100644 --- a/submodules/TelegramUI/Sources/SpotlightContacts.swift +++ b/submodules/TelegramUI/Sources/SpotlightContacts.swift @@ -134,16 +134,17 @@ private final class SpotlightIndexStorage { if let updatedAvatarSourcePathValue = updatedAvatarSourcePath, let avatarData = try? Data(contentsOf: URL(fileURLWithPath: self.appBasePath + "/" + updatedAvatarSourcePathValue)), let image = UIImage(data: avatarData) { let size = CGSize(width: 120.0, height: 120.0) - let context = DrawingContext(size: size, scale: 1.0, clear: true) - context.withFlippedContext { c in - c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) - c.setBlendMode(.destinationOut) - c.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: size)) - } - if let resultImage = context.generateImage(), let resultData = resultImage.pngData(), let _ = try? resultData.write(to: URL(fileURLWithPath: avatarPath)) { - resolvedAvatarPath = avatarPath - } else { - updatedAvatarSourcePath = nil + if let context = DrawingContext(size: size, scale: 1.0, clear: true) { + context.withFlippedContext { c in + c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + c.setBlendMode(.destinationOut) + c.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + } + if let resultImage = context.generateImage(), let resultData = resultImage.pngData(), let _ = try? resultData.write(to: URL(fileURLWithPath: avatarPath)) { + resolvedAvatarPath = avatarPath + } else { + updatedAvatarSourcePath = nil + } } } } diff --git a/submodules/TelegramUniversalVideoContent/Sources/ChatBubbleVideoDecoration.swift b/submodules/TelegramUniversalVideoContent/Sources/ChatBubbleVideoDecoration.swift index 89b5ae6718..46b641a0e2 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/ChatBubbleVideoDecoration.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/ChatBubbleVideoDecoration.swift @@ -47,7 +47,9 @@ public final class ChatBubbleVideoDecoration: UniversalVideoDecoration { let boundingSize: CGSize = CGSize(width: max(corners.topLeft.radius, corners.bottomLeft.radius) + max(corners.topRight.radius, corners.bottomRight.radius), height: max(corners.topLeft.radius, corners.topRight.radius) + max(corners.bottomLeft.radius, corners.bottomRight.radius)) let size: CGSize = CGSize(width: boundingSize.width + corners.extendedEdges.left + corners.extendedEdges.right, height: boundingSize.height + corners.extendedEdges.top + corners.extendedEdges.bottom) let arguments = TransformImageArguments(corners: corners, imageSize: size, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) - let context = DrawingContext(size: size, clear: true) + guard let context = DrawingContext(size: size, clear: true) else { + return + } context.withContext { ctx in ctx.setFillColor(UIColor.black.cgColor) ctx.fill(arguments.drawingRect) diff --git a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift index 6f7e3496e7..2bd15c231a 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift @@ -495,7 +495,9 @@ private func youtubeEmbedStoryboardImage(account: Account, resource: YoutubeEmbe return signal |> map { fullSizeData in let drawingSize = CGSize(width: CGFloat(size.width), height: CGFloat(size.height)) - let context = DrawingContext(size: drawingSize, clear: true) + guard let context = DrawingContext(size: drawingSize, clear: true) else { + return nil + } var fullSizeImage: CGImage? if let fullSizeData = fullSizeData { diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 0ca3837f05..b3ac7582b2 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -812,6 +812,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; RTCAudioSessionConfiguration *sharedConfiguration = [RTCAudioSessionConfiguration webRTCConfiguration]; sharedConfiguration.mode = AVAudioSessionModeVoiceChat; sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers; + sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; sharedConfiguration.outputNumberOfChannels = 1; [RTCAudioSessionConfiguration setWebRTCConfiguration:sharedConfiguration]; @@ -895,6 +896,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; sharedConfiguration.mode = AVAudioSessionModeVoiceChat; } sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers; + sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; sharedConfiguration.outputNumberOfChannels = 1; [RTCAudioSessionConfiguration setWebRTCConfiguration:sharedConfiguration]; @@ -1495,6 +1497,7 @@ private: RTCAudioSessionConfiguration *sharedConfiguration = [RTCAudioSessionConfiguration webRTCConfiguration]; sharedConfiguration.mode = AVAudioSessionModeVoiceChat; sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers; + sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; if (disableAudioInput) { sharedConfiguration.outputNumberOfChannels = 2; } else { diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 28861aec25..f9f4928b4b 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -20,7 +20,9 @@ private let motionAmount: CGFloat = 32.0 private func generateBlurredContents(image: UIImage) -> UIImage? { let size = image.size.aspectFitted(CGSize(width: 64.0, height: 64.0)) - let context = DrawingContext(size: size, scale: 1.0, opaque: true, clear: false) + guard let context = DrawingContext(size: size, scale: 1.0, opaque: true, clear: false) else { + return nil + } context.withFlippedContext { c in c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) } diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 0c459b1e73..90b2e83923 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -215,30 +215,33 @@ public func wallpaperImage(account: Account, accountManager: AccountManager thumbnailContextSize.width { - let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) - additionalBlurContext.withFlippedContext { c in - c.interpolationQuality = .default - if let image = thumbnailContext.generateImage()?.cgImage { - c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) - } + if let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) { + thumbnailContext.withFlippedContext { c in + c.draw(fullSizeImageValue, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + } + telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + + var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5)) + if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 { + thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0)) + } + + if false, thumbnailContextFittingSize.width > thumbnailContextSize.width { + let additionalContextSize = thumbnailContextFittingSize + guard let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) else { + return nil + } + additionalBlurContext.withFlippedContext { c in + c.interpolationQuality = .default + if let image = thumbnailContext.generateImage()?.cgImage { + c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize)) + } + } + imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) + fullSizeImage = additionalBlurContext.generateImage()?.cgImage + } else { + fullSizeImage = thumbnailContext.generateImage()?.cgImage } - imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes) - fullSizeImage = additionalBlurContext.generateImage()?.cgImage - } else { - fullSizeImage = thumbnailContext.generateImage()?.cgImage } } @@ -254,7 +257,9 @@ public func wallpaperImage(account: Account, accountManager: AccountManager thumbnailContextSize.width { let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) + guard let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) else { + return nil + } additionalBlurContext.withFlippedContext { c in c.interpolationQuality = .default if let image = thumbnailContext.generateImage()?.cgImage { @@ -282,7 +289,9 @@ public func wallpaperImage(account: Account, accountManager: AccountManager Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return .single({ arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } context.withFlippedContext { c in c.setFillColor(color.withAlphaComponent(1.0).cgColor) @@ -713,7 +728,9 @@ public func gradientImage(_ colors: [UIColor], rotation: Int32? = nil) -> Signal } } return .single({ arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: !arguments.corners.isEmpty) + guard let context = DrawingContext(size: arguments.drawingSize, clear: !arguments.corners.isEmpty) else { + return nil + } let drawingRect = arguments.drawingRect @@ -768,7 +785,9 @@ private func builtinWallpaperData() -> Signal { public func settingsBuiltinWallpaperImage(account: Account, thumbnail: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return builtinWallpaperData() |> map { fullSizeImage in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect var fittedSize = fullSizeImage.size.aspectFilled(drawingRect.size) @@ -811,7 +830,9 @@ public func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryM let isThumbnail = result?.1 ?? false return { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: 1.0, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, scale: 1.0, clear: true) else { + return nil + } let dimensions = sourceImage?.size @@ -829,7 +850,9 @@ public func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryM let initialThumbnailContextFittingSize = fittedSize.fitted(CGSize(width: 100.0, height: 100.0)) let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else { + return nil + } thumbnailContext.withFlippedContext { c in c.interpolationQuality = .none c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) @@ -843,7 +866,9 @@ public func photoWallpaper(postbox: Postbox, photoLibraryResource: PhotoLibraryM if thumbnailContextFittingSize.width > thumbnailContextSize.width { let additionalContextSize = thumbnailContextFittingSize - let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) + guard let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0) else { + return nil + } additionalBlurContext.withFlippedContext { c in c.interpolationQuality = .default if let image = thumbnailContext.generateImage()?.cgImage { @@ -1230,7 +1255,9 @@ public func themeImage(account: Account, accountManager: AccountManager map { colors in return { arguments in - let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) + guard let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil) else { + return nil + } let drawingRect = arguments.drawingRect context.withContext { c in @@ -1530,7 +1563,9 @@ public func themeIconImage(account: Account, accountManager: AccountManager map { image in if let image = image { return { arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) + guard let context = DrawingContext(size: arguments.drawingSize, clear: true) else { + return nil + } let drawingRect = arguments.drawingRect let imageSize = image.size let fittedSize = imageSize.aspectFilled(arguments.boundingSize).fitted(imageSize) From f2b0d699d901e2e066e9f476a82ab2c8b3dd3b0a Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 15 Nov 2022 13:41:09 +0400 Subject: [PATCH 02/12] Bump version --- versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versions.json b/versions.json index 48efc7a309..9ced858715 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "9.1.1", + "app": "9.2.0", "bazel": "5.3.1", "xcode": "14.0" } From 152fd01567e545a8893b9888bcdd520c8025d80d Mon Sep 17 00:00:00 2001 From: Ali <> Date: Wed, 16 Nov 2022 01:43:02 +0400 Subject: [PATCH 03/12] [WIP] Inline forums --- .../Sources/ChatListController.swift | 2 +- .../AvatarNode/Sources/AvatarNode.swift | 17 +- .../Sources/ChatListController.swift | 77 ++--- .../Sources/ChatListControllerNode.swift | 323 +++++++++++++++--- .../Sources/Node/ChatListItem.swift | 164 +++++++-- .../Sources/Node/ChatListNode.swift | 67 +++- .../Sources/Node/ChatListViewTransition.swift | 2 +- .../Sources/ContactsController.swift | 2 +- .../Sources/DebugController.swift | 14 +- submodules/Display/Source/ListView.swift | 22 +- .../Source/ListViewIntermediateState.swift | 1 + submodules/Postbox/Sources/ViewTracker.swift | 11 + submodules/TelegramApi/Sources/Api0.swift | 2 +- submodules/TelegramApi/Sources/Api11.swift | 20 +- submodules/TelegramApi/Sources/Api29.swift | 56 ++- .../ApiUtils/TelegramMediaAction.swift | 2 +- .../TelegramCore/Sources/ForumChannels.swift | 6 +- .../ContactMultiselectionControllerNode.swift | 4 +- .../PeerInfoGroupsInCommonPaneNode.swift | 2 +- .../PeerInfo/Panes/PeerInfoMembersPane.swift | 2 +- .../Sources/PeerSelectionControllerNode.swift | 4 +- .../Sources/ExperimentalUISettings.swift | 6 + 22 files changed, 631 insertions(+), 175 deletions(-) diff --git a/submodules/AccountContext/Sources/ChatListController.swift b/submodules/AccountContext/Sources/ChatListController.swift index 280fd0ad81..329f81d18e 100644 --- a/submodules/AccountContext/Sources/ChatListController.swift +++ b/submodules/AccountContext/Sources/ChatListController.swift @@ -4,7 +4,7 @@ import Postbox import Display import TelegramCore -public enum ChatListControllerLocation { +public enum ChatListControllerLocation: Equatable { case chatList(groupId: EngineChatList.Group) case forum(peerId: PeerId) } diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index d934d8afb5..0f75fc7316 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -233,15 +233,22 @@ public final class AvatarNode: ASDisplayNode { } set(value) { let updateImage = !value.size.equalTo(super.frame.size) super.frame = value - self.imageNode.frame = CGRect(origin: CGPoint(), size: value.size) - self.editOverlayNode?.frame = self.imageNode.frame - if updateImage && !self.displaySuspended { - self.setNeedsDisplay() - self.editOverlayNode?.setNeedsDisplay() + + if updateImage { + self.updateSize(size: value.size) } } } + public func updateSize(size: CGSize) { + self.imageNode.frame = CGRect(origin: CGPoint(), size: size) + self.editOverlayNode?.frame = self.imageNode.frame + if !self.displaySuspended { + self.setNeedsDisplay() + self.editOverlayNode?.setNeedsDisplay() + } + } + public func playArchiveAnimation() { guard let theme = self.theme else { return diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 459c4d79e4..cade9157be 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -52,7 +52,7 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa } else { offset = 0.0 } - let _ = listNode.scrollToOffsetFromTop(offset) + let _ = listNode.scrollToOffsetFromTop(offset, animated: true) return true } else if searchNode.expansionProgress == 1.0 { var sortItemNode: ListViewItemNode? @@ -341,6 +341,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private let moreBarButtonItem: UIBarButtonItem private var forumChannelTracker: ForumChannelTopics? + private var backNavigationItem: UIBarButtonItem? + private let selectAddMemberDisposable = MetaDisposable() private let addMemberDisposable = MetaDisposable() private let joinForumDisposable = MetaDisposable() @@ -392,6 +394,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) + self.backNavigationItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.navigationBackPressed)) + self.tabBarItemContextActionType = .always self.automaticallyControlPresentationContextLayout = false @@ -742,6 +746,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if strongSelf.navigationItem.leftBarButtonItem?.accessibilityLabel != leftBarButtonItem.accessibilityLabel { strongSelf.navigationItem.setLeftBarButton(leftBarButtonItem, animated: true) } + } else if strongSelf.chatListDisplayNode.inlineStackContainerNode != nil { } else { let editItem: UIBarButtonItem if stateAndFilterId.state.editing { @@ -1160,10 +1165,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if force { strongSelf.tabContainerNode.cancelAnimations() - strongSelf.chatListDisplayNode.inlineTabContainerNode.cancelAnimations() } strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: tabContainerData.2, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) - strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: tabContainerData.0, selectedFilter: filter, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: fraction, presentationData: strongSelf.presentationData, transition: transition) } self.reloadFilters() } @@ -1277,7 +1280,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController editItem = self.moreBarButtonItem } } - if case .chatList(.root) = self.location { + if self.chatListDisplayNode.inlineStackContainerNode != nil { + self.backNavigationItem?.title = self.presentationData.strings.Common_Back + } else if case .chatList(.root) = self.location { self.navigationItem.leftBarButtonItem = editItem let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose @@ -1296,7 +1301,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let layout = self.validLayout { self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate) - self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .immediate) } if self.isNodeLoaded { @@ -1383,11 +1387,21 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } if case let .channel(channel) = peer, channel.flags.contains(.isForum), threadId == nil { - strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) + strongSelf.chatListDisplayNode.clearHighlightAnimated(true) + + if strongSelf.context.sharedContext.immediateExperimentalUISettings.inlineForums { + strongSelf.chatListDisplayNode.setInlineChatList(location: .forum(peerId: channel.id)) + + if strongSelf.navigationItem.leftBarButtonItem !== strongSelf.backNavigationItem { + strongSelf.navigationItem.setLeftBarButton(strongSelf.backNavigationItem, animated: true) + } + } else { + strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) + } } else { if let threadId = threadId { let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start() - strongSelf.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) + strongSelf.chatListDisplayNode.clearHighlightAnimated(true) } else { var navigationAnimationOptions: NavigationAnimationOptions = [] var groupId: EngineChatList.Group = .root @@ -1581,7 +1595,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController navigationController.filterController(strongSelf, animated: true) } - self.chatListDisplayNode.containerNode.contentOffsetChanged = { [weak self] offset in + self.chatListDisplayNode.contentOffsetChanged = { [weak self] offset in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode, let validLayout = strongSelf.validLayout { var offset = offset if validLayout.inVoiceOver { @@ -1591,7 +1605,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - self.chatListDisplayNode.containerNode.contentScrollingEnded = { [weak self] listView in + self.chatListDisplayNode.contentScrollingEnded = { [weak self] listView in if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode { return fixListNodeScrolling(listView, searchNode: searchContentNode) } else { @@ -1885,20 +1899,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.selectTab(id: id) } } - self.chatListDisplayNode.inlineTabContainerNode.tabSelected = { [weak self] id in - self?.selectTab(id: id) - } self.tabContainerNode.tabRequestedDeletion = { [weak self] id in if case let .filter(id) = id { self?.askForFilterRemoval(id: id) } } - self.chatListDisplayNode.inlineTabContainerNode.tabRequestedDeletion = { [weak self] id in - if case let .filter(id) = id { - self?.askForFilterRemoval(id: id) - } - } self.tabContainerNode.presentPremiumTip = { [weak self] in if let strongSelf = self { strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: nil, text: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAll, customUndoText: strongSelf.presentationData.strings.ChatListFolderSettings_SubscribeToMoveAllAction), elevatedLayout: false, position: .top, animateInAsReplacement: false, action: { action in @@ -2108,9 +2114,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.tabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in tabContextGesture(id, sourceNode, gesture, false, isDisabled) } - self.chatListDisplayNode.inlineTabContainerNode.contextGesture = { id, sourceNode, gesture, isDisabled in - tabContextGesture(id, sourceNode, gesture, true, isDisabled) - } if case .chatList(.root) = self.location { self.ready.set(.never()) @@ -2499,12 +2502,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0))) self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) - if let tabContainerData = self.tabContainerData { - self.chatListDisplayNode.inlineTabContainerNode.isHidden = !tabContainerData.1 || tabContainerData.0.count <= 1 - } else { - self.chatListDisplayNode.inlineTabContainerNode.isHidden = true - } - self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, visualNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition) } @@ -2559,22 +2556,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } @objc private func reorderingDonePressed() { - guard let defaultFilters = self.tabContainerData else { - return - } - let defaultFilterIds = defaultFilters.0.compactMap { entry -> Int32? in - switch entry { - case .all: - return 0 - case let .filter(id, _, _): - return id - } - } - var reorderedFilterIdsValue: [Int32]? - if let reorderedFilterIds = self.chatListDisplayNode.inlineTabContainerNode.reorderedFilterIds, reorderedFilterIds != defaultFilterIds { - reorderedFilterIdsValue = reorderedFilterIds - } else if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds { + if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds { reorderedFilterIdsValue = reorderedFilterIds } @@ -2620,6 +2603,17 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) } + @objc private func navigationBackPressed() { + self.chatListDisplayNode.setInlineChatList(location: nil) + + if self.navigationItem.leftBarButtonItem === self.backNavigationItem { + let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) + editItem.accessibilityLabel = self.presentationData.strings.Common_Edit + + self.navigationItem.setLeftBarButton(editItem, animated: true) + } + } + public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) |> deliverOnMainQueue).start(next: { peer in @@ -2872,7 +2866,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController (strongSelf.parent as? TabBarController)?.updateLayout(transition: transition) } else { strongSelf.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: strongSelf.isPremium, filtersLimit: filtersLimit, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) - strongSelf.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: resolvedItems, selectedFilter: selectedEntryId, isReordering: strongSelf.chatListDisplayNode.isReorderingFilters || (strongSelf.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !strongSelf.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: strongSelf.chatListDisplayNode.containerNode.transitionFraction, presentationData: strongSelf.presentationData, transition: .animated(duration: 0.4, curve: .spring)) } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index e20cb03d29..c44ba07a34 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -173,7 +173,7 @@ private final class ChatListShimmerNode: ASDisplayNode { self.addSubnode(self.maskNode) } - func update(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, size: CGSize, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { + func update(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, size: CGSize, isInlineMode: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) { if self.currentParams?.size != size || self.currentParams?.presentationData !== presentationData { self.currentParams = (size, presentationData) @@ -186,6 +186,7 @@ private final class ChatListShimmerNode: ASDisplayNode { }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in }, openForumThread: { _, _ in }) + interaction.isInlineMode = isInlineMode let items = (0 ..< 2).map { _ -> ChatListItem in let message = EngineMessage( @@ -251,14 +252,25 @@ private final class ChatListShimmerNode: ASDisplayNode { context.setBlendMode(.copy) context.setFillColor(UIColor.clear.cgColor) - context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) + if !isInlineMode { + if itemNodes[sampleIndex].avatarNode.isHidden { + context.fillEllipse(in: itemNodes[sampleIndex].avatarNode.frame.offsetBy(dx: 0.0, dy: currentY)) + } + } + let titleFrame = itemNodes[sampleIndex].titleNode.frame.offsetBy(dx: 0.0, dy: currentY) fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: floor(titleFrame.midY - fakeLabelPlaceholderHeight / 2.0)), width: 60.0) - fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0) + let textFrame = itemNodes[sampleIndex].textNode.textNode.frame.offsetBy(dx: 0.0, dy: currentY) - fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0) - fillLabelPlaceholderRect(origin: CGPoint(x: titleFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0) + if isInlineMode { + context.fillEllipse(in: CGRect(origin: CGPoint(x: textFrame.minX, y: titleFrame.minY + 2.0), size: CGSize(width: 16.0, height: 16.0))) + } + + fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + itemHeight - floor(itemNodes[sampleIndex].titleNode.frame.midY - fakeLabelPlaceholderHeight / 2.0) - fakeLabelPlaceholderHeight), width: 60.0) + + fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 120.0) + fillLabelPlaceholderRect(origin: CGPoint(x: textFrame.minX + 120.0 + 10.0, y: currentY + floor((itemHeight - fakeLabelPlaceholderHeight) / 2.0)), width: 60.0) let dateFrame = itemNodes[sampleIndex].dateNode.frame.offsetBy(dx: 0.0, dy: currentY) fillLabelPlaceholderRect(origin: CGPoint(x: dateFrame.maxX - 30.0, y: dateFrame.minY), width: 30.0) @@ -288,6 +300,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { private let becameEmpty: (ChatListFilter?) -> Void private let emptyAction: (ChatListFilter?) -> Void private let secondaryEmptyAction: () -> Void + private let isInlineMode: Bool private var floatingHeaderOffset: CGFloat? @@ -296,9 +309,9 @@ private final class ChatListContainerItemNode: ASDisplayNode { private var shimmerNodeOffset: CGFloat = 0.0 let listNode: ChatListNode - private var validLayout: (CGSize, UIEdgeInsets, CGFloat)? + private var validLayout: (CGSize, UIEdgeInsets, CGFloat, ChatListControllerLocation?)? - init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { + init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { self.context = context self.animationCache = animationCache self.animationRenderer = animationRenderer @@ -306,8 +319,9 @@ private final class ChatListContainerItemNode: ASDisplayNode { self.becameEmpty = becameEmpty self.emptyAction = emptyAction self.secondaryEmptyAction = secondaryEmptyAction + self.isInlineMode = isInlineMode - self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true) + self.listNode = ChatListNode(context: context, location: location, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: animationCache, animationRenderer: animationRenderer, disableAnimations: true, isInlineMode: isInlineMode) super.init() @@ -361,7 +375,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { }) strongSelf.emptyNode = emptyNode strongSelf.addSubnode(emptyNode) - if let (size, insets, _) = strongSelf.validLayout { + if let (size, insets, _, _) = strongSelf.validLayout { let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) emptyNode.frame = emptyNodeFrame emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate) @@ -387,7 +401,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { let emptyShimmerEffectNode = ChatListShimmerNode() strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode) - if let (size, insets, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset { + if let (size, insets, _, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset { strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate) } } @@ -407,14 +421,14 @@ private final class ChatListContainerItemNode: ASDisplayNode { return } strongSelf.floatingHeaderOffset = offset - if let (size, insets, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { + if let (size, insets, _, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition) } } } private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) { - node.update(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, size: size, presentationData: self.presentationData, transition: .immediate) + node.update(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, size: size, isInlineMode: self.isInlineMode, presentationData: self.presentationData, transition: .immediate) transition.updateFrameAdditive(node: node, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: size)) } @@ -430,15 +444,14 @@ private final class ChatListContainerItemNode: ASDisplayNode { self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) } - func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.validLayout = (size, insets, visualNavigationHeight) + func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, insets, visualNavigationHeight, inlineNavigationLocation) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) - self.listNode.visualInsets = UIEdgeInsets(top: visualNavigationHeight, left: 0.0, bottom: 0.0, right: 0.0) - self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation) if let emptyNode = self.emptyNode { let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) @@ -450,8 +463,9 @@ private final class ChatListContainerItemNode: ASDisplayNode { final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private let context: AccountContext - private let location: ChatListControllerLocation + let location: ChatListControllerLocation private let previewing: Bool + private let isInlineMode: Bool private let controlsHistoryPreload: Bool private let filterBecameEmpty: (ChatListFilter?) -> Void private let filterEmptyAction: (ChatListFilter?) -> Void @@ -473,12 +487,14 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private(set) var transitionFraction: CGFloat = 0.0 private var transitionFractionOffset: CGFloat = 0.0 private var disableItemNodeOperationsWhileAnimating: Bool = false - private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool)? + private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?)? private var enableAdjacentFilterLoading: Bool = false private var panRecognizer: InteractiveTransitionGestureRecognizer? + let leftSeparatorLayer: SimpleLayer + private let _ready = Promise() var ready: Signal { return _ready.get() @@ -624,17 +640,18 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { var peerSelected: ((EnginePeer, Int64?, Bool, Bool, ChatListNodeEntryPromoInfo?) -> Void)? var groupSelected: ((EngineChatList.Group) -> Void)? var updatePeerGrouping: ((EnginePeer.Id, Bool) -> Void)? - var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? - var contentScrollingEnded: ((ListView) -> Bool)? + fileprivate var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? + fileprivate var contentScrollingEnded: ((ListView) -> Bool)? var activateChatPreview: ((ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)? var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)? var didBeginSelectingChats: (() -> Void)? var displayFilterLimit: (() -> Void)? - init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { + init(context: AccountContext, location: ChatListControllerLocation, previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { self.context = context self.location = location self.previewing = previewing + self.isInlineMode = isInlineMode self.filterBecameEmpty = filterBecameEmpty self.filterEmptyAction = filterEmptyAction self.secondaryEmptyAction = secondaryEmptyAction @@ -646,9 +663,15 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.selectedId = .all + self.leftSeparatorLayer = SimpleLayer() + self.leftSeparatorLayer.isHidden = true + self.leftSeparatorLayer.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor.cgColor + super.init() - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: nil, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + self.backgroundColor = presentationData.theme.chatList.backgroundColor + + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: nil, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -685,6 +708,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { panRecognizer.cancelsTouchesInView = true self.panRecognizer = panRecognizer self.view.addGestureRecognizer(panRecognizer) + + self.view.layer.addSublayer(self.leftSeparatorLayer) } deinit { @@ -714,7 +739,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.onFilterSwitch?() self.transitionFractionOffset = 0.0 - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] { + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] { for (id, itemNode) in self.itemNodes { if id != selectedId { itemNode.emptyNode?.restartAnimation() @@ -727,13 +752,13 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { for (_, itemNode) in self.itemNodes { itemNode.layer.removeAllAnimations() } - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true) } } } case .changed: - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { let translation = recognizer.translation(in: self.view) var transitionFraction = translation.x / layout.size.width @@ -770,11 +795,11 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } } - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, false) } case .cancelled, .ended: - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { let translation = recognizer.translation(in: self.view) let velocity = recognizer.velocity(in: self.view) var directionIsToRight: Bool? @@ -813,12 +838,12 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.transitionFraction = 0.0 let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) self.disableItemNodeOperationsWhileAnimating = true - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: transition) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) DispatchQueue.main.async { self.disableItemNodeOperationsWhileAnimating = false - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout { - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) } } } @@ -827,9 +852,17 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } + func fixContentOffset(offset: CGFloat) { + self.currentItemNode.fixContentOffset(offset: offset) + } + func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData + self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + + self.leftSeparatorLayer.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor.cgColor + for (_, itemNode) in self.itemNodes { itemNode.updatePresentationData(presentationData) } @@ -884,8 +917,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } strongSelf.availableFilters = availableFilters strongSelf.filtersLimit = limit - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = strongSelf.validLayout { - strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = strongSelf.validLayout { + strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) } } if !availableFilters.contains(where: { $0.id == self.selectedId }) { @@ -902,8 +935,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { if value != self.enableAdjacentFilterLoading { self.enableAdjacentFilterLoading = value - if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout { - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) } } } @@ -912,7 +945,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.onFilterSwitch?() if id != self.selectedId, let index = self.availableFilters.firstIndex(where: { $0.id == id }) { if let itemNode = self.itemNodes[id] { - guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = self.validLayout else { + guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout else { return } self.selectedId = id @@ -921,12 +954,12 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } self.applyItemNodeAsCurrent(id: id, itemNode: itemNode) let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring) - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: transition) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: transition) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) itemNode.emptyNode?.restartAnimation() completion?() } else if self.pendingItemNode == nil { - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[index].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[index].filter, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -951,7 +984,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { strongSelf.pendingItemNode = nil - guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) = strongSelf.validLayout else { + guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = strongSelf.validLayout else { strongSelf.itemNodes[id] = itemNode strongSelf.addSubnode(itemNode) @@ -1000,7 +1033,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: -offset, y: 0.0)) - itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: .immediate) + itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) strongSelf.selectedId = id if let currentItemNode = strongSelf.currentItemNodeValue { @@ -1008,7 +1041,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } strongSelf.applyItemNodeAsCurrent(id: id, itemNode: itemNode) - strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, transition: .immediate) + strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false) } @@ -1019,8 +1052,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } - func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, transition: ContainedViewLayoutTransition) { - self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing) + func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?, transition: ContainedViewLayoutTransition) { + self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) self._validLayoutReady.set(.single(true)) @@ -1029,6 +1062,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.panRecognizer?.isEnabled = !isEditing + transition.updateFrame(layer: self.leftSeparatorLayer, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) + if let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { var validNodeIds: [ChatListFilterTabEntryId] = [] for i in max(0, selectedIndex - 1) ... min(self.availableFilters.count - 1, selectedIndex + 1) { @@ -1036,7 +1071,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { validNodeIds.append(id) if self.itemNodes[id] == nil && self.enableAdjacentFilterLoading && !self.disableItemNodeOperationsWhileAnimating { - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[i].filter, previewing: self.previewing, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[i].filter, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -1073,7 +1108,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { nodeTransition.updateFrame(node: itemNode, frame: itemFrame, completion: { _ in }) - itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, transition: nodeTransition) + itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, transition: nodeTransition) if wasAdded, case .animated = transition { animateSlidingIds.append(id) @@ -1106,7 +1141,7 @@ final class ChatListControllerNode: ASDisplayNode { private let animationRenderer: MultiAnimationRenderer let containerNode: ChatListContainerNode - let inlineTabContainerNode: ChatListFilterTabInlineContainerNode + private(set) var inlineStackContainerNode: ChatListContainerNode? private var tapRecognizer: UITapGestureRecognizer? var navigationBar: NavigationBar? weak var controller: ChatListControllerImpl? @@ -1121,7 +1156,10 @@ final class ChatListControllerNode: ASDisplayNode { var didBeginSelectingChatsWhileEditing: Bool = false var isEditing: Bool = false - private var containerLayout: (ContainerViewLayout, CGFloat, CGFloat, CGFloat)? + private var containerLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat)? + + var contentOffsetChanged: ((ListViewVisibleContentOffset) -> Void)? + var contentScrollingEnded: ((ListView) -> Bool)? var requestDeactivateSearch: (() -> Void)? var requestOpenPeerFromSearch: ((EnginePeer, Int64?, Bool) -> Void)? @@ -1146,7 +1184,7 @@ final class ChatListControllerNode: ASDisplayNode { var filterBecameEmpty: ((ChatListFilter?) -> Void)? var filterEmptyAction: ((ChatListFilter?) -> Void)? var secondaryEmptyAction: (() -> Void)? - self.containerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in + self.containerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, isInlineMode: false, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in filterBecameEmpty?(filter) }, filterEmptyAction: { filter in filterEmptyAction?(filter) @@ -1154,8 +1192,6 @@ final class ChatListControllerNode: ASDisplayNode { secondaryEmptyAction?() }) - self.inlineTabContainerNode = ChatListFilterTabInlineContainerNode() - self.controller = controller super.init() @@ -1167,7 +1203,13 @@ final class ChatListControllerNode: ASDisplayNode { self.backgroundColor = presentationData.theme.chatList.backgroundColor self.addSubnode(self.containerNode) - self.addSubnode(self.inlineTabContainerNode) + + self.containerNode.contentOffsetChanged = { [weak self] offset in + self?.contentOffsetChanged(offset: offset, isPrimary: true) + } + self.containerNode.contentScrollingEnded = { [weak self] listView in + return self?.contentScrollingEnded(listView: listView, isPrimary: true) ?? false + } self.addSubnode(self.debugListView) @@ -1292,9 +1334,42 @@ final class ChatListControllerNode: ASDisplayNode { self.controller?.presentationContext.containerLayoutUpdated(childrenLayout, transition: transition) transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - self.containerNode.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, transition: transition) + var mainNavigationBarHeight = navigationBarHeight + var cleanMainNavigationBarHeight = cleanNavigationBarHeight + var mainInsets = insets + if self.inlineStackContainerNode != nil { + mainNavigationBarHeight = visualNavigationHeight + cleanMainNavigationBarHeight = visualNavigationHeight + mainInsets.top = visualNavigationHeight + } + self.containerNode.update(layout: layout, navigationBarHeight: mainNavigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanMainNavigationBarHeight, insets: mainInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: self.inlineStackContainerNode?.location, transition: transition) - transition.updateFrame(node: self.inlineTabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.intrinsicInsets.bottom - 8.0 - 40.0), size: CGSize(width: layout.size.width, height: 40.0))) + if let inlineStackContainerNode = self.inlineStackContainerNode { + var inlineStackContainerNodeTransition = transition + var animateIn = false + if inlineStackContainerNode.supernode == nil { + self.insertSubnode(inlineStackContainerNode, aboveSubnode: self.containerNode) + inlineStackContainerNodeTransition = .immediate + animateIn = true + } + + let inlineSideInset: CGFloat = layout.safeInsets.left + 72.0 + inlineStackContainerNodeTransition.updateFrame(node: inlineStackContainerNode, frame: CGRect(origin: CGPoint(x: inlineSideInset, y: 0.0), size: layout.size)) + var inlineLayout = layout + inlineLayout.size.width -= inlineSideInset + inlineLayout.safeInsets.left = 0.0 + inlineLayout.intrinsicInsets.left = 0.0 + inlineLayout.additionalInsets.left = 0.0 + + var inlineInsets = insets + inlineInsets.left = 0.0 + + inlineStackContainerNode.update(layout: inlineLayout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: inlineInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: nil, transition: inlineStackContainerNodeTransition) + + if animateIn { + transition.animatePosition(node: inlineStackContainerNode, from: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y)) + } + } self.tapRecognizer?.isEnabled = self.isReorderingFilters @@ -1372,6 +1447,150 @@ final class ChatListControllerNode: ASDisplayNode { } } + func clearHighlightAnimated(_ animated: Bool) { + self.containerNode.currentItemNode.clearHighlightAnimated(true) + self.inlineStackContainerNode?.currentItemNode.clearHighlightAnimated(true) + } + + private var contentOffsetSyncLockedIn: Bool = false + + private func contentOffsetChanged(offset: ListViewVisibleContentOffset, isPrimary: Bool) { + guard let inlineStackContainerNode = self.inlineStackContainerNode else { + self.contentOffsetChanged?(offset) + return + } + guard let containerLayout = self.containerLayout else { + return + } + + if !isPrimary { + self.contentOffsetChanged?(offset) + if "".isEmpty { + return + } + } else { + if "".isEmpty { + return + } + } + + let targetNode: ChatListContainerNode + if isPrimary { + targetNode = inlineStackContainerNode + } else { + targetNode = self.containerNode + } + + switch offset { + case let .known(value) where (value <= containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0 + UIScreenPixel || self.contentOffsetSyncLockedIn): + if case let .known(otherValue) = targetNode.currentItemNode.visibleContentOffset(), abs(otherValue - value) <= UIScreenPixel { + self.contentOffsetSyncLockedIn = true + } + default: + break + } + + switch offset { + case let .known(value) where self.contentOffsetSyncLockedIn: + var targetValue = value + if targetValue > containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0 { + targetValue = containerLayout.navigationBarHeight - 76.0 - 46.0 - 8.0 + } + + targetNode.fixContentOffset(offset: targetValue) + + self.contentOffsetChanged?(offset) + default: + if !isPrimary { + self.contentOffsetChanged?(offset) + } + } + } + + private func contentScrollingEnded(listView: ListView, isPrimary: Bool) -> Bool { + guard let inlineStackContainerNode = self.inlineStackContainerNode else { + return self.contentScrollingEnded?(listView) ?? false + } + + self.contentOffsetSyncLockedIn = false + + if isPrimary { + return false + } + + let _ = inlineStackContainerNode + + return self.contentScrollingEnded?(listView) ?? false + } + + func setInlineChatList(location: ChatListControllerLocation?) { + if let location = location { + if self.inlineStackContainerNode?.location != location { + let inlineStackContainerNode = ChatListContainerNode(context: self.context, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in }, secondaryEmptyAction: {}) + + inlineStackContainerNode.leftSeparatorLayer.isHidden = false + + inlineStackContainerNode.presentAlert = self.containerNode.presentAlert + inlineStackContainerNode.present = self.containerNode.present + inlineStackContainerNode.push = self.containerNode.push + inlineStackContainerNode.deletePeerChat = self.containerNode.deletePeerChat + inlineStackContainerNode.deletePeerThread = self.containerNode.deletePeerThread + inlineStackContainerNode.setPeerThreadStopped = self.containerNode.setPeerThreadStopped + inlineStackContainerNode.setPeerThreadPinned = self.containerNode.setPeerThreadPinned + inlineStackContainerNode.peerSelected = self.containerNode.peerSelected + inlineStackContainerNode.groupSelected = self.containerNode.groupSelected + inlineStackContainerNode.updatePeerGrouping = self.containerNode.updatePeerGrouping + + inlineStackContainerNode.contentOffsetChanged = { [weak self] offset in + self?.contentOffsetChanged(offset: offset, isPrimary: false) + } + inlineStackContainerNode.contentScrollingEnded = { [weak self] listView in + return self?.contentScrollingEnded(listView: listView, isPrimary: false) ?? false + } + + inlineStackContainerNode.activateChatPreview = self.containerNode.activateChatPreview + inlineStackContainerNode.addedVisibleChatsWithPeerIds = self.containerNode.addedVisibleChatsWithPeerIds + inlineStackContainerNode.didBeginSelectingChats = nil + inlineStackContainerNode.displayFilterLimit = nil + + let previousInlineStackContainerNode = self.inlineStackContainerNode + + self.inlineStackContainerNode = inlineStackContainerNode + + if let containerLayout = self.containerLayout { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + + if let previousInlineStackContainerNode { + transition.updatePosition(node: previousInlineStackContainerNode, position: CGPoint(x: previousInlineStackContainerNode.position.x + previousInlineStackContainerNode.bounds.width + UIScreenPixel, y: previousInlineStackContainerNode.position.y), completion: { [weak previousInlineStackContainerNode] _ in + previousInlineStackContainerNode?.removeFromSupernode() + }) + } + + self.containerLayoutUpdated(containerLayout.layout, navigationBarHeight: containerLayout.navigationBarHeight, visualNavigationHeight: containerLayout.visualNavigationHeight, cleanNavigationBarHeight: containerLayout.cleanNavigationBarHeight, transition: transition) + } else { + previousInlineStackContainerNode?.removeFromSupernode() + } + } + } else { + if let inlineStackContainerNode = self.inlineStackContainerNode { + self.inlineStackContainerNode = nil + + self.containerNode.contentScrollingEnded = self.contentScrollingEnded + + if let containerLayout = self.containerLayout { + let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) + self.containerLayoutUpdated(containerLayout.layout, navigationBarHeight: containerLayout.navigationBarHeight, visualNavigationHeight: containerLayout.visualNavigationHeight, cleanNavigationBarHeight: containerLayout.cleanNavigationBarHeight, transition: transition) + + transition.updatePosition(node: inlineStackContainerNode, position: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y), completion: { [weak inlineStackContainerNode] _ in + inlineStackContainerNode?.removeFromSupernode() + }) + } else { + inlineStackContainerNode.removeFromSupernode() + } + } + } + } + func playArchiveAnimation() { self.containerNode.playArchiveAnimation() } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index c8bf77ef63..69cd3d0b1d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -775,6 +775,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { private let highlightedBackgroundNode: ASDisplayNode let contextContainer: ContextControllerSourceNode + let mainContentContainerNode: ASDisplayNode let avatarNode: AvatarNode var avatarIconView: ComponentHostView? @@ -784,6 +785,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { private let playbackStartDisposable = MetaDisposable() private var videoLoopCount = 0 + private var inlineNavigationMarkLayer: SimpleLayer? + let titleNode: TextNode let authorNode: AuthorNode private var compoundHighlightingNode: LinkHighlightingNode? @@ -1026,6 +1029,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.contextContainer = ContextControllerSourceNode() + self.mainContentContainerNode = ASDisplayNode() + self.mainContentContainerNode.clipsToBounds = true + self.measureNode = TextNode() self.titleNode = TextNode() @@ -1073,19 +1079,20 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.addSubnode(self.separatorNode) self.addSubnode(self.contextContainer) + self.contextContainer.addSubnode(self.mainContentContainerNode) self.contextContainer.addSubnode(self.avatarNode) self.contextContainer.addSubnode(self.onlineNode) - self.contextContainer.addSubnode(self.titleNode) - self.contextContainer.addSubnode(self.authorNode) - self.contextContainer.addSubnode(self.textNode.textNode) - self.contextContainer.addSubnode(self.dateNode) - self.contextContainer.addSubnode(self.statusNode) - self.contextContainer.addSubnode(self.pinnedIconNode) - self.contextContainer.addSubnode(self.badgeNode) - self.contextContainer.addSubnode(self.mentionBadgeNode) - self.contextContainer.addSubnode(self.mutedIconNode) + self.mainContentContainerNode.addSubnode(self.titleNode) + self.mainContentContainerNode.addSubnode(self.authorNode) + self.mainContentContainerNode.addSubnode(self.textNode.textNode) + self.mainContentContainerNode.addSubnode(self.dateNode) + self.mainContentContainerNode.addSubnode(self.statusNode) + self.mainContentContainerNode.addSubnode(self.pinnedIconNode) + self.mainContentContainerNode.addSubnode(self.badgeNode) + self.mainContentContainerNode.addSubnode(self.mentionBadgeNode) + self.mainContentContainerNode.addSubnode(self.mutedIconNode) self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in if let strongSelf = self, let layoutParams = strongSelf.layoutParams { @@ -1094,6 +1101,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } }) + self.contextContainer.shouldBegin = { [weak self] location in + guard let strongSelf = self else { + return false + } + if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view { + strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.compoundHighlightingNode + } else { + strongSelf.contextContainer.targetNodeForActivationProgress = nil + } + + return true + } + self.contextContainer.activated = { [weak self] gesture, location in guard let strongSelf = self, let item = strongSelf.item else { return @@ -1505,7 +1525,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let avatarDiameter = min(60.0, floor(item.presentationData.fontSize.baseDisplaySize * 60.0 / 17.0)) let avatarLeftInset: CGFloat - if case .forum = item.index { + if item.interaction.isInlineMode { + avatarLeftInset = 12.0 + } else if case .forum = item.index { avatarLeftInset = 50.0 } else { avatarLeftInset = 18.0 + avatarDiameter @@ -2290,8 +2312,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.cachedChatListText = chatListText strongSelf.cachedChatListSearchResult = chatListSearchResult strongSelf.onlineIsVoiceChat = onlineIsVoiceChat - - strongSelf.contextContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize) if case .groupReference = item.content { strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, layout.contentSize.height - itemHeight, 0.0) @@ -2301,7 +2321,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.separatorNode.backgroundColor = item.presentationData.theme.chatList.itemSeparatorColor } - let revealOffset = strongSelf.revealOffset + let revealOffset = 0.0//strongSelf.revealOffset let transition: ContainedViewLayoutTransition if animated { @@ -2310,6 +2330,30 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition = .immediate } + let contextContainerFrame = CGRect(origin: CGPoint(), size: layout.contentSize) + strongSelf.contextContainer.position = contextContainerFrame.center + transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) + + if item.interaction.inlineNavigationLocation != nil { + let mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) + + transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentFrame.size.width, y: 0.0), size: mainContentFrame.size)) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 0.0) + } else if case .chatList = item.chatListLocation { + let mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) + + transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentFrame.origin.x, y: 0.0), size: mainContentFrame.size)) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + } else { + let mainContentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) + + transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mainContentFrame.size)) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + } + var crossfadeContent = false if let selectableControlSizeAndApply = selectableControlSizeAndApply { let selectableControlSize = CGSize(width: selectableControlSizeAndApply.0, height: layout.contentSize.height) @@ -2380,9 +2424,51 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + revealOffset, dy: 0.0) let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) - transition.updateFrame(node: strongSelf.avatarNode, frame: avatarFrame) + var avatarScaleOffset: CGFloat = 0.0 + var avatarScale: CGFloat = 1.0 + if item.interaction.inlineNavigationLocation != nil { + avatarScale = 54.0 / avatarFrame.width + avatarScaleOffset = -(avatarFrame.width - avatarFrame.width * avatarScale) * 0.5 + } + transition.updatePosition(node: strongSelf.avatarNode, position: avatarFrame.center.offsetBy(dx: avatarScaleOffset, dy: 0.0)) + transition.updateBounds(node: strongSelf.avatarNode, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size)) + transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale) + strongSelf.avatarNode.updateSize(size: avatarFrame.size) strongSelf.updateVideoVisibility() + var itemPeerId: EnginePeer.Id? + if case let .chatList(index) = item.index { + itemPeerId = index.messageIndex.id.peerId + } + + if let itemPeerId = itemPeerId, let inlineNavigationLocation = item.interaction.inlineNavigationLocation, inlineNavigationLocation.location.peerId == itemPeerId { + let inlineNavigationMarkLayer: SimpleLayer + var animateIn = false + if let current = strongSelf.inlineNavigationMarkLayer { + inlineNavigationMarkLayer = current + } else { + inlineNavigationMarkLayer = SimpleLayer() + strongSelf.inlineNavigationMarkLayer = inlineNavigationMarkLayer + inlineNavigationMarkLayer.cornerRadius = 4.0 + animateIn = true + strongSelf.layer.addSublayer(inlineNavigationMarkLayer) + } + inlineNavigationMarkLayer.backgroundColor = item.presentationData.theme.list.itemAccentColor.cgColor + let markHeight: CGFloat = 50.0 + let markFrame = CGRect(origin: CGPoint(x: -4.0, y: avatarFrame.midY - markHeight * 0.5), size: CGSize(width: 8.0, height: markHeight)) + if animateIn { + inlineNavigationMarkLayer.frame = markFrame + transition.animatePositionAdditive(layer: inlineNavigationMarkLayer, offset: CGPoint(x: -markFrame.width * 0.5, y: 0.0)) + } else { + transition.updateFrame(layer: inlineNavigationMarkLayer, frame: markFrame) + } + } else { + if let inlineNavigationMarkLayer = strongSelf.inlineNavigationMarkLayer { + strongSelf.inlineNavigationMarkLayer = nil + transition.updatePosition(layer: inlineNavigationMarkLayer, position: CGPoint(x: -inlineNavigationMarkLayer.bounds.width * 0.5, y: avatarFrame.midY)) + } + } + if let threadInfo = threadInfo { let avatarIconView: ComponentHostView if let current = strongSelf.avatarIconView { @@ -2390,7 +2476,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { avatarIconView = ComponentHostView() strongSelf.avatarIconView = avatarIconView - strongSelf.contextContainer.view.addSubview(avatarIconView) + strongSelf.mainContentContainerNode.view.addSubview(avatarIconView) } let avatarIconContent: EmojiStatusComponent.Content @@ -2414,9 +2500,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { transition: .immediate, component: AnyComponent(avatarIconComponent), environment: {}, - containerSize: CGSize(width: 32.0, height: 32.0) + containerSize: item.interaction.isInlineMode ? CGSize(width: 18.0, height: 18.0) : CGSize(width: 32.0, height: 32.0) ) - transition.updateFrame(view: avatarIconView, frame: CGRect(origin: CGPoint(x: editingOffset + params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0) + revealOffset, y: contentRect.origin.y + 2.0), size: iconSize)) + + let avatarIconFrame: CGRect + if item.interaction.isInlineMode { + avatarIconFrame = CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.origin.y + 1.0), size: iconSize) + } else { + avatarIconFrame = CGRect(origin: CGPoint(x: editingOffset + params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0) + revealOffset, y: contentRect.origin.y + 2.0), size: iconSize) + } + transition.updateFrame(view: avatarIconView, frame: avatarIconFrame) } else if let avatarIconView = strongSelf.avatarIconView { strongSelf.avatarIconView = nil avatarIconView.removeFromSuperview() @@ -2479,7 +2572,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { dateStatusIconNode = ASImageNode() strongSelf.dateStatusIconNode = dateStatusIconNode - strongSelf.contextContainer.addSubnode(dateStatusIconNode) + strongSelf.mainContentContainerNode.addSubnode(dateStatusIconNode) } dateStatusIconNode.image = dateIconImage @@ -2539,6 +2632,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } var titleOffset: CGFloat = 0.0 + if item.interaction.isInlineMode { + titleOffset += 22.0 + } if let currentSecretIconImage = currentSecretIconImage { let iconNode: ASImageNode if let current = strongSelf.secretIconNode { @@ -2548,7 +2644,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { iconNode.isLayerBacked = true iconNode.displaysAsynchronously = false iconNode.displayWithoutProcessing = true - strongSelf.contextContainer.addSubnode(iconNode) + strongSelf.mainContentContainerNode.addSubnode(iconNode) strongSelf.secretIconNode = iconNode } iconNode.image = currentSecretIconImage @@ -2576,7 +2672,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { compoundHighlightingNode.alpha = strongSelf.authorNode.alpha compoundHighlightingNode.useModernPathCalculation = true strongSelf.compoundHighlightingNode = compoundHighlightingNode - strongSelf.contextContainer.insertSubnode(compoundHighlightingNode, at: 0) + strongSelf.mainContentContainerNode.insertSubnode(compoundHighlightingNode, at: 0) } let compoundTextButtonNode: HighlightTrackingButtonNode @@ -2585,7 +2681,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { compoundTextButtonNode = HighlightTrackingButtonNode() strongSelf.compoundTextButtonNode = compoundTextButtonNode - strongSelf.contextContainer.addSubnode(compoundTextButtonNode) + strongSelf.mainContentContainerNode.addSubnode(compoundTextButtonNode) compoundTextButtonNode.addTarget(strongSelf, action: #selector(strongSelf.compoundTextButtonPressed), forControlEvents: .touchUpInside) compoundTextButtonNode.highligthedChanged = { highlighted in guard let strongSelf = self, let compoundHighlightingNode = strongSelf.compoundHighlightingNode else { @@ -2644,7 +2740,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { textArrowNode = ASImageNode() strongSelf.textArrowNode = textArrowNode - compoundTextButtonNode.addSubnode(textArrowNode) + compoundHighlightingNode.addSubnode(textArrowNode) } textArrowNode.image = textArrowImage let arrowScale: CGFloat = 0.75 @@ -2677,7 +2773,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { dustNode = InvisibleInkDustNode(textNode: nil) dustNode.isUserInteractionEnabled = false strongSelf.dustNode = dustNode - strongSelf.contextContainer.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode) + strongSelf.mainContentContainerNode.insertSubnode(dustNode, aboveSubnode: strongSelf.textNode.textNode) } dustNode.update(size: textNodeFrame.size, color: theme.messageTextColor, textColor: theme.messageTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 0.0, dy: 1.0) }) dustNode.frame = textNodeFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0) @@ -2699,7 +2795,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let inputActivities = inputActivities, !inputActivities.isEmpty { if strongSelf.inputActivitiesNode.supernode == nil { - strongSelf.contextContainer.addSubnode(strongSelf.inputActivitiesNode) + strongSelf.mainContentContainerNode.addSubnode(strongSelf.inputActivitiesNode) } else { animateInputActivitiesFrame = true } @@ -2768,7 +2864,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { previewNodeAlphaTransition = .immediate previewNode = ChatListMediaPreviewNode(context: item.context, message: message, media: media) strongSelf.mediaPreviewNodes[mediaId] = previewNode - strongSelf.contextContainer.addSubnode(previewNode) + strongSelf.mainContentContainerNode.addSubnode(previewNode) } previewNode.updateLayout(size: mediaSize, synchronousLoads: synchronousLoads) previewNodeAlphaTransition.updateAlpha(node: previewNode, alpha: strongSelf.inputActivitiesNode.alpha.isZero ? 1.0 : 0.0) @@ -2820,7 +2916,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { credibilityIconView = ComponentHostView() strongSelf.credibilityIconView = credibilityIconView - strongSelf.contextContainer.view.addSubview(credibilityIconView) + strongSelf.mainContentContainerNode.view.addSubview(credibilityIconView) } let credibilityIconComponent = EmojiStatusComponent( @@ -2866,11 +2962,15 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight))) + transition.updateAlpha(node: strongSelf.separatorNode, alpha: item.interaction.inlineNavigationLocation != nil ? 0.0 : 1.0) transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: itemHeight))) let backgroundColor: UIColor let highlightedBackgroundColor: UIColor - if item.selected { + if item.interaction.inlineNavigationLocation != nil { + backgroundColor = theme.pinnedItemBackgroundColor + highlightedBackgroundColor = theme.itemHighlightedBackgroundColor + } else if item.selected { backgroundColor = theme.itemSelectedBackgroundColor highlightedBackgroundColor = theme.itemHighlightedBackgroundColor } else if isPinned { @@ -3011,7 +3111,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.playbackStartDisposable.set(nil) videoNode.isHidden = false } - videoNode.layer.cornerRadius = self.avatarNode.frame.size.width / 2.0 + videoNode.layer.cornerRadius = self.avatarNode.bounds.size.width / 2.0 if #available(iOS 13.0, *) { videoNode.layer.cornerCurve = .circular } @@ -3019,7 +3119,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { videoNode.canAttachContent = true videoNode.play() -// self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode) +// self.mainContentContainerNode.insertSubnode(videoNode, aboveSubnode: self.avatarNode) self.avatarNode.addSubnode(videoNode) self.videoNode = videoNode } @@ -3037,7 +3137,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { super.updateRevealOffset(offset: offset, transition: transition) - if let item = self.item, let params = self.layoutParams?.5, let currentItemHeight = self.currentItemHeight, let countersSize = self.layoutParams?.6 { + transition.updateBounds(node: self.contextContainer, bounds: self.contextContainer.frame.offsetBy(dx: -offset, dy: 0.0)) + + /*if let item = self.item, let params = self.layoutParams?.5, let currentItemHeight = self.currentItemHeight, let countersSize = self.layoutParams?.6 { let editingOffset: CGFloat if let selectableControlNode = self.selectableControlNode { editingOffset = selectableControlNode.bounds.size.width @@ -3188,7 +3290,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let badgeBackgroundFrame = CGRect(x: badgeOffset, y: self.pinnedIconNode.frame.origin.y, width: badgeBackgroundWidth, height: pinnedIconSize.height) transition.updateFrame(node: self.pinnedIconNode, frame: badgeBackgroundFrame) } - } + }*/ } override func touchesToOtherItemsPrevented() { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index d7fce6e645..2737726f3f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -35,7 +35,7 @@ struct ChatListNodeListViewTransition { let animateCrossfade: Bool } -final class ChatListHighlightedLocation { +final class ChatListHighlightedLocation: Equatable { let location: ChatLocation let progress: CGFloat @@ -47,6 +47,16 @@ final class ChatListHighlightedLocation { func withUpdatedProgress(_ progress: CGFloat) -> ChatListHighlightedLocation { return ChatListHighlightedLocation(location: location, progress: progress) } + + static func ==(lhs: ChatListHighlightedLocation, rhs: ChatListHighlightedLocation) -> Bool { + if lhs.location != rhs.location { + return false + } + if lhs.progress != rhs.progress { + return false + } + return true + } } public final class ChatListNodeInteraction { @@ -84,6 +94,9 @@ public final class ChatListNodeInteraction { public var searchTextHighightState: String? var highlightedChatLocation: ChatListHighlightedLocation? + var isInlineMode: Bool = false + var inlineNavigationLocation: ChatListHighlightedLocation? + let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer @@ -871,7 +884,9 @@ public final class ChatListNode: ListView { public var selectionLimit: Int32 = 100 public var reachedSelectionLimit: ((Int32) -> Void)? - public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool) { + private var visibleTopInset: CGFloat? + + public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) { self.context = context self.location = location self.chatListFilter = chatListFilter @@ -1156,6 +1171,7 @@ public final class ChatListNode: ListView { self.peerSelected?(peer, threadId, true, true, nil) }) }) + nodeInteraction.isInlineMode = isInlineMode let viewProcessingQueue = self.viewProcessingQueue @@ -1929,6 +1945,9 @@ public final class ChatListNode: ListView { guard let strongSelf = self else { return } + if !strongSelf.dequeuedInitialTransitionOnLayout { + return + } let atTop: Bool var revealHiddenItems: Bool = false switch offset { @@ -2255,11 +2274,15 @@ public final class ChatListNode: ListView { var scrollToItem = transition.scrollToItem if transition.adjustScrollToFirstItem { var offset: CGFloat = 0.0 - switch self.visibleContentOffset() { - case let .known(value) where abs(value) < .ulpOfOne: - offset = 0.0 - default: - offset = -navigationBarSearchContentHeight + if let visibleTopInset = self.visibleTopInset { + offset = visibleTopInset - self.insets.top + } else { + switch self.visibleContentOffset() { + case let .known(value) where abs(value) < .ulpOfOne: + offset = 0.0 + default: + offset = -navigationBarSearchContentHeight + } } scrollToItem = ListViewScrollToItem(index: 0, position: .top(offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) } @@ -2316,8 +2339,34 @@ public final class ChatListNode: ListView { } } - public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets) { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + public func fixContentOffset(offset: CGFloat) { + let _ = self.scrollToOffsetFromTop(offset, animated: false) + + /*let scrollToItem: ListViewScrollToItem = ListViewScrollToItem(index: 0, position: .top(-offset), animated: false, curve: .Default(duration: 0.0), directionHint: .Up) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })*/ + } + + public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, visibleTopInset: CGFloat, inlineNavigationLocation: ChatListControllerLocation?) { + self.visibleTopInset = visibleTopInset + + self.visualInsets = UIEdgeInsets(top: visibleTopInset, left: 0.0, bottom: 0.0, right: 0.0) + + var highlightedLocation: ChatListHighlightedLocation? + if case let .forum(peerId) = inlineNavigationLocation { + highlightedLocation = ChatListHighlightedLocation(location: .peer(id: peerId), progress: 1.0) + } + var navigationLocationUpdated = false + if self.interaction?.inlineNavigationLocation != highlightedLocation { + self.interaction?.inlineNavigationLocation = highlightedLocation + navigationLocationUpdated = true + } + + var options: ListViewDeleteAndInsertOptions = [.Synchronous, .LowLatency] + if navigationLocationUpdated { + options.insert(.ForceUpdate) + options.insert(.AnimateInsertion) + } + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: options, scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if !self.dequeuedInitialTransitionOnLayout { self.dequeuedInitialTransitionOnLayout = true diff --git a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift index e50289ef00..77a086921f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListViewTransition.swift @@ -210,7 +210,7 @@ func preparedChatListNodeViewTransition(from fromView: ChatListNodeView?, to toV } var adjustScrollToFirstItem = false - if !previewing && !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 2 { + if !previewing && !searchMode && fromEmptyView && scrollToItem == nil && toView.filteredEntries.count >= 1 { adjustScrollToFirstItem = true } diff --git a/submodules/ContactListUI/Sources/ContactsController.swift b/submodules/ContactListUI/Sources/ContactsController.swift index 55c20a8002..f9c170d984 100644 --- a/submodules/ContactListUI/Sources/ContactsController.swift +++ b/submodules/ContactListUI/Sources/ContactsController.swift @@ -105,7 +105,7 @@ private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBa } else { offset = 0.0 } - let _ = listNode.scrollToOffsetFromTop(offset) + let _ = listNode.scrollToOffsetFromTop(offset, animated: true) return true } else if searchNode.expansionProgress == 1.0 { var sortItemNode: ListViewItemNode? diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 88251c7749..ed13290c23 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -86,7 +86,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case enableDebugDataDisplay(Bool) case acceleratedStickers(Bool) case experimentalBackground(Bool) - case inlineStickers(Bool) + case inlineForums(Bool) case localTranscription(Bool) case enableReactionOverrides(Bool) case playerEmbedding(Bool) @@ -111,7 +111,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineStickers, .localTranscription, . enableReactionOverrides, .restorePurchases: + case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .resetWebViewCache, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay, .acceleratedStickers, .experimentalBackground, .inlineForums, .localTranscription, . enableReactionOverrides, .restorePurchases: return DebugControllerSection.experiments.rawValue case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue @@ -188,7 +188,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 30 case .experimentalBackground: return 31 - case .inlineStickers: + case .inlineForums: return 32 case .localTranscription: return 33 @@ -1053,12 +1053,12 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) - case let .inlineStickers(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Inline Stickers", value: value, sectionId: self.section, style: .blocks, updated: { value in + case let .inlineForums(value): + return ItemListSwitchItem(presentationData: presentationData, title: "Inline Forums", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - settings.inlineStickers = value + settings.inlineForums = value return PreferencesEntry(settings) }) }).start() @@ -1219,7 +1219,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay)) entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers)) entries.append(.experimentalBackground(experimentalSettings.experimentalBackground)) - entries.append(.inlineStickers(experimentalSettings.inlineStickers)) + entries.append(.inlineForums(experimentalSettings.inlineForums)) entries.append(.localTranscription(experimentalSettings.localTranscription)) if case .internal = sharedContext.applicationBindings.appBuildType { entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides)) diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index d768d2b57a..591ea3c2ba 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -1144,6 +1144,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if bottomItemFound { bottomItemEdge = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY + } else { + bottomItemEdge = self.visibleSize.height } if topItemFound && bottomItemFound { @@ -1734,7 +1736,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { - if let updateSizeAndInsets = updateSizeAndInsets, (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { + if let updateSizeAndInsets = updateSizeAndInsets, (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets && !options.contains(.ForceUpdate))) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets @@ -1769,7 +1771,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let widthUpdated: Bool if let updateSizeAndInsets = updateSizeAndInsets { - widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat.ulpOfOne + widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat.ulpOfOne || options.contains(.ForceUpdate) state.visibleSize = updateSizeAndInsets.size state.insets = updateSizeAndInsets.insets @@ -2820,7 +2822,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture case let .bottom(additionalOffset): offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + additionalOffset case let .top(additionalOffset): - offset = insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + additionalOffset + offset = (insets.top + additionalOffset + itemNode.scrollPositioningInsets.top) - itemNode.apparentFrame.minY case let .center(overflow): let contentAreaHeight = self.visibleSize.height - insets.bottom - insets.top if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { @@ -2912,7 +2914,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } else if self.snapToBottomInsetUntilFirstInteraction { offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom } else { - offsetFix = updateSizeAndInsets.insets.top - self.insets.top + if let visualInsets = self.visualInsets, animated, (visualInsets.top == updateSizeAndInsets.insets.top || visualInsets.top == self.insets.top) { + offsetFix = 0.0 + } else { + offsetFix = updateSizeAndInsets.insets.top - self.insets.top + } } offsetFix += additionalScrollDistance @@ -4783,10 +4789,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - public func scrollToOffsetFromTop(_ offset: CGFloat) -> Bool { + public func scrollToOffsetFromTop(_ offset: CGFloat, animated: Bool) -> Bool { for itemNode in self.itemNodes { if itemNode.index == 0 { - self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset), animated: true) + if animated { + self.scroller.setContentOffset(CGPoint(x: 0.0, y: offset), animated: animated) + } else { + self.scroller.contentOffset = CGPoint(x: 0.0, y: offset) + } return true } } diff --git a/submodules/Display/Source/ListViewIntermediateState.swift b/submodules/Display/Source/ListViewIntermediateState.swift index fcc91cc9a6..354018da4e 100644 --- a/submodules/Display/Source/ListViewIntermediateState.swift +++ b/submodules/Display/Source/ListViewIntermediateState.swift @@ -104,6 +104,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public static let PreferSynchronousDrawing = ListViewDeleteAndInsertOptions(rawValue: 64) public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128) public static let AnimateCrossfade = ListViewDeleteAndInsertOptions(rawValue: 256) + public static let ForceUpdate = ListViewDeleteAndInsertOptions(rawValue: 512) } public struct ListViewUpdateSizeAndInsets { diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index e544bb82fe..739f228028 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -206,6 +206,17 @@ final class ViewTracker { let record = (view, ValuePipe()) let index = self.combinedViews.add(record) + if view.views.keys.contains(where: { key in + switch key { + case .messageHistoryThreadIndex: + return true + default: + return false + } + }) { + self.updateTrackedForumTopicListHoles() + } + return (index, record.1.signal()) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 9c5b18ec29..539a285929 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -456,7 +456,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } dict[-1441072131] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } - dict[-1316338916] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } + dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) } dict[-1262252875] = { return Api.MessageAction.parse_messageActionWebViewDataSent($0) } dict[1205698681] = { return Api.MessageAction.parse_messageActionWebViewDataSentMe($0) } dict[546203849] = { return Api.MessageEntity.parse_inputMessageEntityMentionName($0) } diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 83decd957e..407ec13c3d 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -1020,7 +1020,7 @@ public extension Api { case messageActionSetChatTheme(emoticon: String) case messageActionSetMessagesTTL(period: Int32) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) - case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?) + case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) case messageActionWebViewDataSent(text: String) case messageActionWebViewDataSentMe(text: String, data: String) @@ -1265,14 +1265,15 @@ public extension Api { serializeInt32(iconColor, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} break - case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed): + case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): if boxed { - buffer.appendInt32(-1316338916) + buffer.appendInt32(-1064024032) } serializeInt32(flags, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)} break case .messageActionWebViewDataSent(let text): if boxed { @@ -1356,8 +1357,8 @@ public extension Api { return ("messageActionSetMessagesTTL", [("period", String(describing: period))]) case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId): return ("messageActionTopicCreate", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconColor", String(describing: iconColor)), ("iconEmojiId", String(describing: iconEmojiId))]) - case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed): - return ("messageActionTopicEdit", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed))]) + case .messageActionTopicEdit(let flags, let title, let iconEmojiId, let closed, let hidden): + return ("messageActionTopicEdit", [("flags", String(describing: flags)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed)), ("hidden", String(describing: hidden))]) case .messageActionWebViewDataSent(let text): return ("messageActionWebViewDataSent", [("text", String(describing: text))]) case .messageActionWebViewDataSentMe(let text, let data): @@ -1783,12 +1784,17 @@ public extension Api { if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { _4 = Api.parse(reader, signature: signature) as? Api.Bool } } + var _5: Api.Bool? + if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.Bool + } } let _c1 = _1 != nil let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4) + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.MessageAction.messageActionTopicEdit(flags: _1!, title: _2, iconEmojiId: _3, closed: _4, hidden: _5) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 1ba4add6d3..3499b5f17a 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1524,6 +1524,23 @@ public extension Api.functions.auth { }) } } +public extension Api.functions.auth { + static func importWebTokenAuthorization(apiId: Int32, apiHash: String, webAuthToken: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(767062953) + serializeInt32(apiId, buffer: buffer, boxed: false) + serializeString(apiHash, buffer: buffer, boxed: false) + serializeString(webAuthToken, buffer: buffer, boxed: false) + return (FunctionDescription(name: "auth.importWebTokenAuthorization", parameters: [("apiId", String(describing: apiId)), ("apiHash", String(describing: apiHash)), ("webAuthToken", String(describing: webAuthToken))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.Authorization? in + let reader = BufferReader(buffer) + var result: Api.auth.Authorization? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.auth.Authorization + } + return result + }) + } +} public extension Api.functions.auth { static func logOut() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2026,16 +2043,17 @@ public extension Api.functions.channels { } } public extension Api.functions.channels { - static func editForumTopic(flags: Int32, channel: Api.InputChannel, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func editForumTopic(flags: Int32, channel: Api.InputChannel, topicId: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1820868141) + buffer.appendInt32(-186670715) serializeInt32(flags, buffer: buffer, boxed: false) channel.serialize(buffer, true) serializeInt32(topicId, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeString(title!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 1) != 0 {serializeInt64(iconEmojiId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {closed!.serialize(buffer, true)} - return (FunctionDescription(name: "channels.editForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 3) != 0 {hidden!.serialize(buffer, true)} + return (FunctionDescription(name: "channels.editForumTopic", parameters: [("flags", String(describing: flags)), ("channel", String(describing: channel)), ("topicId", String(describing: topicId)), ("title", String(describing: title)), ("iconEmojiId", String(describing: iconEmojiId)), ("closed", String(describing: closed)), ("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -2484,6 +2502,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func reportAntiSpamFalsePositive(channel: Api.InputChannel, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1471109485) + channel.serialize(buffer, true) + serializeInt32(msgId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "channels.reportAntiSpamFalsePositive", parameters: [("channel", String(describing: channel)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.channels { static func reportSpam(channel: Api.InputChannel, participant: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -2537,6 +2571,22 @@ public extension Api.functions.channels { }) } } +public extension Api.functions.channels { + static func toggleAntiSpam(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1760814315) + channel.serialize(buffer, true) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "channels.toggleAntiSpam", parameters: [("channel", String(describing: channel)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.channels { static func toggleForum(channel: Api.InputChannel, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 9e1a92a6a8..b5da27157e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -89,7 +89,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .giftPremium(currency: currency, amount: amount, months: months)) case let .messageActionTopicCreate(_, title, iconColor, iconEmojiId): return TelegramMediaAction(action: .topicCreated(title: title, iconColor: iconColor, iconFileId: iconEmojiId)) - case let .messageActionTopicEdit(flags, title, iconEmojiId, closed): + case let .messageActionTopicEdit(flags, title, iconEmojiId, closed, _): var components: [TelegramMediaActionType.ForumTopicEditComponent] = [] if let title = title { components.append(.title(title)) diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 18b7ad8d16..a007a94b92 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -293,7 +293,8 @@ func _internal_editForumChannelTopic(account: Account, peerId: PeerId, threadId: topicId: Int32(clamping: threadId), title: title, iconEmojiId: iconFileId ?? 0, - closed: nil + closed: nil, + hidden: nil )) |> mapError { _ -> EditForumChannelTopicError in return .generic @@ -338,7 +339,8 @@ func _internal_setForumChannelTopicClosed(account: Account, id: EnginePeer.Id, t topicId: Int32(clamping: threadId), title: nil, iconEmojiId: nil, - closed: isClosed ? .boolTrue : .boolFalse + closed: isClosed ? .boolTrue : .boolFalse, + hidden: nil )) |> mapError { _ -> EditForumChannelTopicError in return .generic diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index cdd96329e7..e38ac68f4d 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -96,7 +96,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode { placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle - let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) + let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false) if let limit = limit { chatListNode.selectionLimit = limit chatListNode.reachedSelectionLimit = reachedSelectionLimit @@ -279,7 +279,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { combinedInsets.right += layout.safeInsets.right let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: combinedInsets, headerInsets: headerInsets, duration: duration, curve: curve) - chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil) } self.contentNode.node.frame = CGRect(origin: CGPoint(), size: layout.size) diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift index e2def93ae8..e7e6f19ff1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoGroupsInCommonPaneNode.swift @@ -144,7 +144,7 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode { } func scrollToTop() -> Bool { - if !self.listNode.scrollToOffsetFromTop(0.0) { + if !self.listNode.scrollToOffsetFromTop(0.0, animated: true) { self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) return true } else { diff --git a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift index 36d0fe8fb9..e3e3769a6c 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/Panes/PeerInfoMembersPane.swift @@ -224,7 +224,7 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode { } func scrollToTop() -> Bool { - if !self.listNode.scrollToOffsetFromTop(0.0) { + if !self.listNode.scrollToOffsetFromTop(0.0, animated: true) { self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) return true } else { diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index d83fb93dcf..958645739a 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -137,7 +137,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { chatListLocation = .chatList(groupId: .root) } - self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true) + self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false) super.init() @@ -576,7 +576,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve) - self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets) + self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil) if let contactListNode = self.contactListNode { contactListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index 5db9e36288..5bda3a0f4a 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -45,6 +45,7 @@ public struct ExperimentalUISettings: Codable, Equatable { public var inlineStickers: Bool public var localTranscription: Bool public var enableReactionOverrides: Bool + public var inlineForums: Bool public var accountReactionEffectOverrides: [AccountReactionOverrides] public var accountStickerEffectOverrides: [AccountReactionOverrides] @@ -69,6 +70,7 @@ public struct ExperimentalUISettings: Codable, Equatable { inlineStickers: false, localTranscription: false, enableReactionOverrides: false, + inlineForums: false, accountReactionEffectOverrides: [], accountStickerEffectOverrides: [] ) @@ -94,6 +96,7 @@ public struct ExperimentalUISettings: Codable, Equatable { inlineStickers: Bool, localTranscription: Bool, enableReactionOverrides: Bool, + inlineForums: Bool, accountReactionEffectOverrides: [AccountReactionOverrides], accountStickerEffectOverrides: [AccountReactionOverrides] ) { @@ -116,6 +119,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.inlineStickers = inlineStickers self.localTranscription = localTranscription self.enableReactionOverrides = enableReactionOverrides + self.inlineForums = inlineForums self.accountReactionEffectOverrides = accountReactionEffectOverrides self.accountStickerEffectOverrides = accountStickerEffectOverrides } @@ -142,6 +146,7 @@ public struct ExperimentalUISettings: Codable, Equatable { self.inlineStickers = (try container.decodeIfPresent(Int32.self, forKey: "inlineStickers") ?? 0) != 0 self.localTranscription = (try container.decodeIfPresent(Int32.self, forKey: "localTranscription") ?? 0) != 0 self.enableReactionOverrides = try container.decodeIfPresent(Bool.self, forKey: "enableReactionOverrides") ?? false + self.inlineForums = try container.decodeIfPresent(Bool.self, forKey: "inlineForums") ?? false self.accountReactionEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountReactionEffectOverrides")) ?? [] self.accountStickerEffectOverrides = (try? container.decodeIfPresent([AccountReactionOverrides].self, forKey: "accountStickerEffectOverrides")) ?? [] } @@ -168,6 +173,7 @@ public struct ExperimentalUISettings: Codable, Equatable { try container.encode((self.inlineStickers ? 1 : 0) as Int32, forKey: "inlineStickers") try container.encode((self.localTranscription ? 1 : 0) as Int32, forKey: "localTranscription") try container.encode(self.enableReactionOverrides, forKey: "enableReactionOverrides") + try container.encode(self.inlineForums, forKey: "inlineForums") try container.encode(self.accountReactionEffectOverrides, forKey: "accountReactionEffectOverrides") try container.encode(self.accountStickerEffectOverrides, forKey: "accountStickerEffectOverrides") } From c36e1585194695bcb0638853926ae27fba7c9cb6 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 17 Nov 2022 01:21:06 +0400 Subject: [PATCH 04/12] [WIP] Inline forum improvements --- .../Sources/ChatListController.swift | 14 +- .../Sources/ChatListControllerNode.swift | 173 ++++++++++++++---- .../Sources/Node/ChatListItem.swift | 65 ++++--- .../Sources/Node/ChatListNode.swift | 44 ++++- submodules/Display/Source/ListView.swift | 6 +- submodules/Display/Source/NavigationBar.swift | 9 +- .../ContactMultiselectionControllerNode.swift | 2 +- .../Sources/PeerSelectionControllerNode.swift | 2 +- 8 files changed, 233 insertions(+), 82 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 2849fe73ad..9ccbc00ca3 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -746,7 +746,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if strongSelf.navigationItem.leftBarButtonItem?.accessibilityLabel != leftBarButtonItem.accessibilityLabel { strongSelf.navigationItem.setLeftBarButton(leftBarButtonItem, animated: true) } - } else if strongSelf.chatListDisplayNode.inlineStackContainerNode != nil { + } else if strongSelf.chatListDisplayNode.inlineStackContainerTransitionFraction != 0.0 { } else { let editItem: UIBarButtonItem if stateAndFilterId.state.editing { @@ -1280,7 +1280,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController editItem = self.moreBarButtonItem } } - if self.chatListDisplayNode.inlineStackContainerNode != nil { + if self.chatListDisplayNode.inlineStackContainerTransitionFraction != 0.0 { self.backNavigationItem?.title = self.presentationData.strings.Common_Back } else if case .chatList(.root) = self.location { self.navigationItem.leftBarButtonItem = editItem @@ -2483,6 +2483,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.navigationBar?.secondaryContentNodeDisplayFraction = 1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction + + if let inlineStackContainerNode = self.chatListDisplayNode.inlineStackContainerNode { + let _ = inlineStackContainerNode + } else { + } + super.containerLayoutUpdated(layout, transition: transition) let wasInVoiceOver = self.validLayout?.inVoiceOver ?? false @@ -2503,9 +2510,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController tabContainerOffset += layout.statusBarHeight ?? 0.0 tabContainerOffset += 44.0 + 20.0 } + tabContainerOffset += self.chatListDisplayNode.inlineStackContainerTransitionFraction * NavigationBar.defaultSecondaryContentHeight let navigationBarHeight = self.navigationBar?.frame.maxY ?? 0.0 + transition.updateAlpha(node: self.tabContainerNode, alpha: 1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction) + transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0))) self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index dd77507bd0..3df33b9ec7 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -309,7 +309,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { private var shimmerNodeOffset: CGFloat = 0.0 let listNode: ChatListNode - private var validLayout: (CGSize, UIEdgeInsets, CGFloat, ChatListControllerLocation?)? + private var validLayout: (CGSize, UIEdgeInsets, CGFloat, CGFloat, ChatListControllerLocation?, CGFloat)? init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { self.context = context @@ -375,7 +375,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { }) strongSelf.emptyNode = emptyNode strongSelf.addSubnode(emptyNode) - if let (size, insets, _, _) = strongSelf.validLayout { + if let (size, insets, _, _, _, _) = strongSelf.validLayout { let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) emptyNode.frame = emptyNodeFrame emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate) @@ -401,7 +401,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { let emptyShimmerEffectNode = ChatListShimmerNode() strongSelf.emptyShimmerEffectNode = emptyShimmerEffectNode strongSelf.insertSubnode(emptyShimmerEffectNode, belowSubnode: strongSelf.listNode) - if let (size, insets, _, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset { + if let (size, insets, _, _, _, _) = strongSelf.validLayout, let offset = strongSelf.floatingHeaderOffset { strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: .immediate) } } @@ -421,7 +421,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { return } strongSelf.floatingHeaderOffset = offset - if let (size, insets, _, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { + if let (size, insets, _, _, _, _) = strongSelf.validLayout, let emptyShimmerEffectNode = strongSelf.emptyShimmerEffectNode { strongSelf.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: size, insets: insets, verticalOffset: offset + strongSelf.shimmerNodeOffset, transition: transition) } } @@ -444,14 +444,14 @@ private final class ChatListContainerItemNode: ASDisplayNode { self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings) } - func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, transition: ContainedViewLayoutTransition) { - self.validLayout = (size, insets, visualNavigationHeight, inlineNavigationLocation) + func updateLayout(size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve) transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size)) - self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation) + self.listNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: visualNavigationHeight, originalTopInset: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction) if let emptyNode = self.emptyNode { let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) @@ -487,7 +487,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private(set) var transitionFraction: CGFloat = 0.0 private var transitionFractionOffset: CGFloat = 0.0 private var disableItemNodeOperationsWhileAnimating: Bool = false - private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?)? + private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat)? private var enableAdjacentFilterLoading: Bool = false @@ -744,7 +744,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.onFilterSwitch?() self.transitionFractionOffset = 0.0 - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] { + if let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) = self.validLayout, let itemNode = self.itemNodes[self.selectedId] { for (id, itemNode) in self.itemNodes { if id != selectedId { itemNode.emptyNode?.restartAnimation() @@ -757,13 +757,13 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { for (_, itemNode) in self.itemNodes { itemNode.layer.removeAllAnimations() } - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .immediate) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, true) } } } case .changed: - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + if let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { let translation = recognizer.translation(in: self.view) var transitionFraction = translation.x / layout.size.width @@ -800,11 +800,11 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } } - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .immediate) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, .immediate, false) } case .cancelled, .ended: - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { + if let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) = self.validLayout, let selectedIndex = self.availableFilters.firstIndex(where: { $0.id == self.selectedId }) { let translation = recognizer.translation(in: self.view) let velocity = recognizer.velocity(in: self.view) var directionIsToRight: Bool? @@ -843,12 +843,12 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.transitionFraction = 0.0 let transition: ContainedViewLayoutTransition = .animated(duration: 0.45, curve: .spring) self.disableItemNodeOperationsWhileAnimating = true - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: transition) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: transition) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) DispatchQueue.main.async { self.disableItemNodeOperationsWhileAnimating = false - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout { - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) + if let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .immediate) } } } @@ -864,7 +864,13 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData - self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + if let validLayout = self.validLayout { + if let _ = validLayout.inlineNavigationLocation { + self.backgroundColor = self.presentationData.theme.chatList.backgroundColor.mixedWith(self.presentationData.theme.chatList.pinnedItemBackgroundColor, alpha: validLayout.inlineNavigationTransitionFraction) + } else { + self.backgroundColor = self.presentationData.theme.chatList.backgroundColor + } + } self.leftSeparatorLayer.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor.cgColor @@ -922,8 +928,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } strongSelf.availableFilters = availableFilters strongSelf.filtersLimit = limit - if let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = strongSelf.validLayout { - strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) + if let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) = strongSelf.validLayout { + strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .immediate) } } if !availableFilters.contains(where: { $0.id == self.selectedId }) { @@ -940,8 +946,8 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { if value != self.enableAdjacentFilterLoading { self.enableAdjacentFilterLoading = value - if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout { - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) + if self.enableAdjacentFilterLoading, let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) = self.validLayout { + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .immediate) } } } @@ -950,7 +956,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.onFilterSwitch?() if id != self.selectedId, let index = self.availableFilters.firstIndex(where: { $0.id == id }) { if let itemNode = self.itemNodes[id] { - guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = self.validLayout else { + guard let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) = self.validLayout else { return } self.selectedId = id @@ -959,7 +965,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } self.applyItemNodeAsCurrent(id: id, itemNode: itemNode) let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring) - self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: transition) + self.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: transition) self.currentItemFilterUpdated?(self.currentItemFilter, self.transitionFraction, transition, false) itemNode.emptyNode?.restartAnimation() completion?() @@ -989,7 +995,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { strongSelf.pendingItemNode = nil - guard let (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) = strongSelf.validLayout else { + guard let (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) = strongSelf.validLayout else { strongSelf.itemNodes[id] = itemNode strongSelf.addSubnode(itemNode) @@ -1038,7 +1044,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { transition.animatePositionAdditive(node: itemNode, offset: CGPoint(x: -offset, y: 0.0)) - itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) + itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .immediate) strongSelf.selectedId = id if let currentItemNode = strongSelf.currentItemNodeValue { @@ -1046,7 +1052,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } strongSelf.applyItemNodeAsCurrent(id: id, itemNode: itemNode) - strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, transition: .immediate) + strongSelf.update(layout: layout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: insets, isReorderingFilters: isReorderingFilters, isEditing: isEditing, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .immediate) strongSelf.currentItemFilterUpdated?(strongSelf.currentItemFilter, strongSelf.transitionFraction, transition, false) } @@ -1057,14 +1063,20 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } - func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?, transition: ContainedViewLayoutTransition) { - self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation) + func update(layout: ContainerViewLayout, navigationBarHeight: CGFloat, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, cleanNavigationBarHeight: CGFloat, insets: UIEdgeInsets, isReorderingFilters: Bool, isEditing: Bool, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (layout, navigationBarHeight, visualNavigationHeight, originalNavigationHeight, cleanNavigationBarHeight, insets, isReorderingFilters, isEditing, inlineNavigationLocation, inlineNavigationTransitionFraction) self._validLayoutReady.set(.single(true)) transition.updateAlpha(node: self, alpha: isReorderingFilters ? 0.5 : 1.0) self.isUserInteractionEnabled = !isReorderingFilters + if let _ = inlineNavigationLocation { + transition.updateBackgroundColor(node: self, color: self.presentationData.theme.chatList.backgroundColor.mixedWith(self.presentationData.theme.chatList.pinnedItemBackgroundColor, alpha: inlineNavigationTransitionFraction)) + } else { + transition.updateBackgroundColor(node: self, color: self.presentationData.theme.chatList.backgroundColor) + } + self.panRecognizer?.isEnabled = !isEditing transition.updateFrame(layer: self.leftSeparatorLayer, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) @@ -1113,7 +1125,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { nodeTransition.updateFrame(node: itemNode, frame: itemFrame, completion: { _ in }) - itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, transition: nodeTransition) + itemNode.updateLayout(size: layout.size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: nodeTransition) if wasAdded, case .animated = transition { animateSlidingIds.append(id) @@ -1138,7 +1150,7 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { } } -final class ChatListControllerNode: ASDisplayNode { +final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { private let context: AccountContext private let location: ChatListControllerLocation private var presentationData: PresentationData @@ -1146,7 +1158,11 @@ final class ChatListControllerNode: ASDisplayNode { private let animationRenderer: MultiAnimationRenderer let containerNode: ChatListContainerNode + + private(set) var inlineStackContainerTransitionFraction: CGFloat = 0.0 private(set) var inlineStackContainerNode: ChatListContainerNode? + private var inlineContentPanRecognizer: InteractiveTransitionGestureRecognizer? + private var tapRecognizer: UITapGestureRecognizer? var navigationBar: NavigationBar? weak var controller: ChatListControllerImpl? @@ -1247,6 +1263,19 @@ final class ChatListControllerNode: ASDisplayNode { strongSelf.controller?.dismissAllUndoControllers() } } + + let inlineContentPanRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.inlineContentPanGesture(_:)), allowedDirections: { [weak self] _ in + guard let strongSelf = self, strongSelf.inlineStackContainerNode != nil else { + return [] + } + let directions: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter] + return directions + }, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0)) + inlineContentPanRecognizer.delegate = self + inlineContentPanRecognizer.delaysTouchesBegan = false + inlineContentPanRecognizer.cancelsTouchesInView = true + self.inlineContentPanRecognizer = inlineContentPanRecognizer + self.view.addGestureRecognizer(inlineContentPanRecognizer) } override func didLoad() { @@ -1264,6 +1293,69 @@ final class ChatListControllerNode: ASDisplayNode { } } + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer { + return false + } + if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { + return true + } + return false + } + + @objc private func inlineContentPanGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + break + case .changed: + if let inlineStackContainerNode = self.inlineStackContainerNode { + let translation = recognizer.translation(in: self.view) + var transitionFraction = translation.x / inlineStackContainerNode.bounds.width + transitionFraction = 1.0 - max(0.0, min(1.0, transitionFraction)) + self.inlineStackContainerTransitionFraction = transitionFraction + self.controller?.requestLayout(transition: .immediate) + } + case .cancelled, .ended: + if let inlineStackContainerNode = self.inlineStackContainerNode { + let translation = recognizer.translation(in: self.view) + let velocity = recognizer.velocity(in: self.view) + var directionIsToRight: Bool? + if abs(velocity.x) > 10.0 { + if translation.x > 0.0 { + if velocity.x <= 0.0 { + directionIsToRight = nil + } else { + directionIsToRight = true + } + } else { + if velocity.x >= 0.0 { + directionIsToRight = nil + } else { + directionIsToRight = false + } + } + } else { + if abs(translation.x) > inlineStackContainerNode.bounds.width / 2.0 { + directionIsToRight = translation.x > inlineStackContainerNode.bounds.width / 2.0 + } + } + + if let directionIsToRight = directionIsToRight, directionIsToRight { + self.setInlineChatList(location: nil) + } else { + self.inlineStackContainerTransitionFraction = 1.0 + self.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) + } + } + default: + break + } + } + func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData @@ -1342,12 +1434,12 @@ final class ChatListControllerNode: ASDisplayNode { var mainNavigationBarHeight = navigationBarHeight var cleanMainNavigationBarHeight = cleanNavigationBarHeight var mainInsets = insets - if self.inlineStackContainerNode != nil { + if self.inlineStackContainerNode != nil && "".isEmpty { mainNavigationBarHeight = visualNavigationHeight cleanMainNavigationBarHeight = visualNavigationHeight mainInsets.top = visualNavigationHeight } - self.containerNode.update(layout: layout, navigationBarHeight: mainNavigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanMainNavigationBarHeight, insets: mainInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: self.inlineStackContainerNode?.location, transition: transition) + self.containerNode.update(layout: layout, navigationBarHeight: mainNavigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: cleanMainNavigationBarHeight, insets: mainInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: self.inlineStackContainerNode?.location, inlineNavigationTransitionFraction: self.inlineStackContainerTransitionFraction, transition: transition) if let inlineStackContainerNode = self.inlineStackContainerNode { var inlineStackContainerNodeTransition = transition @@ -1359,7 +1451,9 @@ final class ChatListControllerNode: ASDisplayNode { } let inlineSideInset: CGFloat = layout.safeInsets.left + 72.0 - inlineStackContainerNodeTransition.updateFrame(node: inlineStackContainerNode, frame: CGRect(origin: CGPoint(x: inlineSideInset, y: 0.0), size: layout.size)) + var inlineStackFrame = CGRect(origin: CGPoint(x: inlineSideInset, y: 0.0), size: CGSize(width: layout.size.width - inlineSideInset, height: layout.size.height)) + inlineStackFrame.origin.x += (1.0 - self.inlineStackContainerTransitionFraction) * inlineStackFrame.width + inlineStackContainerNodeTransition.updateFrame(node: inlineStackContainerNode, frame: inlineStackFrame) var inlineLayout = layout inlineLayout.size.width -= inlineSideInset inlineLayout.safeInsets.left = 0.0 @@ -1369,7 +1463,7 @@ final class ChatListControllerNode: ASDisplayNode { var inlineInsets = insets inlineInsets.left = 0.0 - inlineStackContainerNode.update(layout: inlineLayout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: inlineInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: nil, transition: inlineStackContainerNodeTransition) + inlineStackContainerNode.update(layout: inlineLayout, navigationBarHeight: navigationBarHeight, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: cleanNavigationBarHeight, insets: inlineInsets, isReorderingFilters: self.isReorderingFilters, isEditing: self.isEditing, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0, transition: inlineStackContainerNodeTransition) if animateIn { transition.animatePosition(node: inlineStackContainerNode, from: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y)) @@ -1561,8 +1655,9 @@ final class ChatListControllerNode: ASDisplayNode { let previousInlineStackContainerNode = self.inlineStackContainerNode self.inlineStackContainerNode = inlineStackContainerNode + self.inlineStackContainerTransitionFraction = 1.0 - if let containerLayout = self.containerLayout { + if let _ = self.containerLayout { let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) if let previousInlineStackContainerNode { @@ -1571,7 +1666,7 @@ final class ChatListControllerNode: ASDisplayNode { }) } - self.containerLayoutUpdated(containerLayout.layout, navigationBarHeight: containerLayout.navigationBarHeight, visualNavigationHeight: containerLayout.visualNavigationHeight, cleanNavigationBarHeight: containerLayout.cleanNavigationBarHeight, transition: transition) + self.controller?.requestLayout(transition: transition) } else { previousInlineStackContainerNode?.removeFromSupernode() } @@ -1579,16 +1674,18 @@ final class ChatListControllerNode: ASDisplayNode { } else { if let inlineStackContainerNode = self.inlineStackContainerNode { self.inlineStackContainerNode = nil + self.inlineStackContainerTransitionFraction = 0.0 self.containerNode.contentScrollingEnded = self.contentScrollingEnded - if let containerLayout = self.containerLayout { + if let _ = self.containerLayout { let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring) - self.containerLayoutUpdated(containerLayout.layout, navigationBarHeight: containerLayout.navigationBarHeight, visualNavigationHeight: containerLayout.visualNavigationHeight, cleanNavigationBarHeight: containerLayout.cleanNavigationBarHeight, transition: transition) transition.updatePosition(node: inlineStackContainerNode, position: CGPoint(x: inlineStackContainerNode.position.x + inlineStackContainerNode.bounds.width + UIScreenPixel, y: inlineStackContainerNode.position.y), completion: { [weak inlineStackContainerNode] _ in inlineStackContainerNode?.removeFromSupernode() }) + + self.controller?.requestLayout(transition: transition) } else { inlineStackContainerNode.removeFromSupernode() } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index bf2f42a507..55ab0e5eb0 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2349,26 +2349,28 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.contextContainer.position = contextContainerFrame.center transition.updateBounds(node: strongSelf.contextContainer, bounds: contextContainerFrame.offsetBy(dx: -strongSelf.revealOffset, dy: 0.0)) - if item.interaction.inlineNavigationLocation != nil { - let mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) - transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) + var mainContentFrame: CGRect + var mainContentBoundsOffset: CGFloat + var mainContentAlpha: CGFloat = 1.0 + + if case .chatList = item.chatListLocation { + mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + mainContentBoundsOffset = mainContentFrame.origin.x - transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentFrame.size.width, y: 0.0), size: mainContentFrame.size)) - transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 0.0) - } else if case .chatList = item.chatListLocation { - let mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) - transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) - - transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentFrame.origin.x, y: 0.0), size: mainContentFrame.size)) - transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { + mainContentAlpha = 1.0 - inlineNavigationLocation.progress + mainContentBoundsOffset += (mainContentFrame.width - mainContentFrame.minX) * inlineNavigationLocation.progress + } } else { - let mainContentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) - transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) - - transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: mainContentFrame.size)) - transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: 1.0) + mainContentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height)) + mainContentBoundsOffset = 0.0 } + transition.updatePosition(node: strongSelf.mainContentContainerNode, position: mainContentFrame.center) + + transition.updateBounds(node: strongSelf.mainContentContainerNode, bounds: CGRect(origin: CGPoint(x: mainContentBoundsOffset, y: 0.0), size: mainContentFrame.size)) + transition.updateAlpha(node: strongSelf.mainContentContainerNode, alpha: mainContentAlpha) + var crossfadeContent = false if let selectableControlSizeAndApply = selectableControlSizeAndApply { let selectableControlSize = CGSize(width: selectableControlSizeAndApply.0, height: layout.contentSize.height) @@ -2441,9 +2443,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let avatarFrame = CGRect(origin: CGPoint(x: leftInset - avatarLeftInset + editingOffset + 10.0 + revealOffset, y: floor((itemHeight - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter)) var avatarScaleOffset: CGFloat = 0.0 var avatarScale: CGFloat = 1.0 - if item.interaction.inlineNavigationLocation != nil { - avatarScale = 54.0 / avatarFrame.width - avatarScaleOffset = -(avatarFrame.width - avatarFrame.width * avatarScale) * 0.5 + if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { + let targetAvatarScale: CGFloat = 54.0 / avatarFrame.width + avatarScale = targetAvatarScale * inlineNavigationLocation.progress + 1.0 * (1.0 - inlineNavigationLocation.progress) + + let targetAvatarScaleOffset: CGFloat = -(avatarFrame.width - avatarFrame.width * avatarScale) * 0.5 + avatarScaleOffset = targetAvatarScaleOffset * inlineNavigationLocation.progress } transition.updatePosition(node: strongSelf.avatarNode, position: avatarFrame.center.offsetBy(dx: avatarScaleOffset, dy: 0.0)) transition.updateBounds(node: strongSelf.avatarNode, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size)) @@ -2470,7 +2475,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } inlineNavigationMarkLayer.backgroundColor = item.presentationData.theme.list.itemAccentColor.cgColor let markHeight: CGFloat = 50.0 - let markFrame = CGRect(origin: CGPoint(x: -4.0, y: avatarFrame.midY - markHeight * 0.5), size: CGSize(width: 8.0, height: markHeight)) + var markFrame = CGRect(origin: CGPoint(x: -4.0, y: avatarFrame.midY - markHeight * 0.5), size: CGSize(width: 8.0, height: markHeight)) + markFrame.origin.x -= (1.0 - inlineNavigationLocation.progress) * markFrame.width * 0.5 if animateIn { inlineNavigationMarkLayer.frame = markFrame transition.animatePositionAdditive(layer: inlineNavigationMarkLayer, offset: CGPoint(x: -markFrame.width * 0.5, y: 0.0)) @@ -2979,15 +2985,16 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: separatorInset, y: layoutOffset + itemHeight - separatorHeight), size: CGSize(width: params.width - separatorInset, height: separatorHeight))) - transition.updateAlpha(node: strongSelf.separatorNode, alpha: item.interaction.inlineNavigationLocation != nil ? 0.0 : 1.0) + if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { + transition.updateAlpha(node: strongSelf.separatorNode, alpha: 1.0 - inlineNavigationLocation.progress) + } else { + transition.updateAlpha(node: strongSelf.separatorNode, alpha: 1.0) + } transition.updateFrame(node: strongSelf.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: itemHeight))) let backgroundColor: UIColor let highlightedBackgroundColor: UIColor - if item.interaction.inlineNavigationLocation != nil { - backgroundColor = theme.pinnedItemBackgroundColor - highlightedBackgroundColor = theme.itemHighlightedBackgroundColor - } else if item.selected { + if item.selected { backgroundColor = theme.itemSelectedBackgroundColor highlightedBackgroundColor = theme.itemHighlightedBackgroundColor } else if isPinned { @@ -3002,11 +3009,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { backgroundColor = theme.itemBackgroundColor highlightedBackgroundColor = theme.itemHighlightedBackgroundColor } + if animated { transition.updateBackgroundColor(node: strongSelf.backgroundNode, color: backgroundColor) } else { strongSelf.backgroundNode.backgroundColor = backgroundColor } + + if let inlineNavigationLocation = item.interaction.inlineNavigationLocation { + transition.updateAlpha(node: strongSelf.backgroundNode, alpha: 1.0 - inlineNavigationLocation.progress) + } else { + transition.updateAlpha(node: strongSelf.backgroundNode, alpha: 1.0) + } + strongSelf.highlightedBackgroundNode.backgroundColor = highlightedBackgroundColor let topNegativeInset: CGFloat = 0.0 strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layoutOffset - separatorHeight - topNegativeInset), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height + separatorHeight + topNegativeInset)) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 5b361a601d..9907331c0e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -889,6 +889,7 @@ public final class ChatListNode: ListView { public var reachedSelectionLimit: ((Int32) -> Void)? private var visibleTopInset: CGFloat? + private var originalTopInset: CGFloat? public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) { self.context = context @@ -1965,7 +1966,7 @@ public final class ChatListNode: ListView { } }) - self.visibleContentOffsetChanged = { [weak self] offset in + self.visibleContentOffsetChanged = { [weak self] offset, transition in guard let strongSelf = self else { return } @@ -2375,27 +2376,54 @@ public final class ChatListNode: ListView { self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: scrollToItem, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })*/ } - public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, visibleTopInset: CGFloat, inlineNavigationLocation: ChatListControllerLocation?) { - self.visibleTopInset = visibleTopInset - - self.visualInsets = UIEdgeInsets(top: visibleTopInset, left: 0.0, bottom: 0.0, right: 0.0) + public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, visibleTopInset: CGFloat, originalTopInset: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat) { var highlightedLocation: ChatListHighlightedLocation? if case let .forum(peerId) = inlineNavigationLocation { - highlightedLocation = ChatListHighlightedLocation(location: .peer(id: peerId), progress: 1.0) + highlightedLocation = ChatListHighlightedLocation(location: .peer(id: peerId), progress: inlineNavigationTransitionFraction) } + var navigationLocationPresenceUpdated = false + if (self.interaction?.inlineNavigationLocation == nil) != (highlightedLocation == nil) { + navigationLocationPresenceUpdated = true + } + var navigationLocationUpdated = false if self.interaction?.inlineNavigationLocation != highlightedLocation { self.interaction?.inlineNavigationLocation = highlightedLocation navigationLocationUpdated = true } + let insetDelta: CGFloat = 0.0 + if navigationLocationPresenceUpdated { + let targetTopInset: CGFloat + if highlightedLocation != nil { + targetTopInset = self.visibleTopInset ?? self.insets.top + } else { + targetTopInset = self.originalTopInset ?? self.insets.top + } + let immediateInsetDelta = self.insets.top - targetTopInset + + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, additionalScrollDistance: immediateInsetDelta, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: self.visibleSize, insets: UIEdgeInsets(top: targetTopInset, left: self.insets.left, bottom: self.insets.bottom, right: self.insets.right), duration: 0.0, curve: .Default(duration: 0.0)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + + self.visualInsets = UIEdgeInsets(top: visibleTopInset, left: 0.0, bottom: 0.0, right: 0.0) + + self.visibleTopInset = visibleTopInset + self.originalTopInset = originalTopInset + + var additionalScrollDistance: CGFloat = 0.0 + var options: ListViewDeleteAndInsertOptions = [.Synchronous, .LowLatency] if navigationLocationUpdated { options.insert(.ForceUpdate) - options.insert(.AnimateInsertion) + + if transition.isAnimated { + options.insert(.AnimateInsertion) + } + + additionalScrollDistance += insetDelta } - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: options, scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: options, scrollToItem: nil, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) if !self.dequeuedInitialTransitionOnLayout { self.dequeuedInitialTransitionOnLayout = true diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 591ea3c2ba..db7c427d8d 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -2914,11 +2914,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } else if self.snapToBottomInsetUntilFirstInteraction { offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom } else { - if let visualInsets = self.visualInsets, animated, (visualInsets.top == updateSizeAndInsets.insets.top || visualInsets.top == self.insets.top) { + /*if let visualInsets = self.visualInsets, animated, (visualInsets.top == updateSizeAndInsets.insets.top || visualInsets.top == self.insets.top) { offsetFix = 0.0 - } else { + } else {*/ offsetFix = updateSizeAndInsets.insets.top - self.insets.top - } + //} } offsetFix += additionalScrollDistance diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 7462c6a7ab..6931a979a5 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -489,6 +489,7 @@ open class NavigationBar: ASDisplayNode { public private(set) var contentNode: NavigationBarContentNode? public private(set) var secondaryContentNode: ASDisplayNode? + public var secondaryContentNodeDisplayFraction: CGFloat = 1.0 private var itemTitleListenerKey: Int? private var itemTitleViewListenerKey: Int? @@ -1200,7 +1201,7 @@ open class NavigationBar: ASDisplayNode { self.backgroundNode.update(size: backgroundFrame.size, transition: transition) } - let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? NavigationBar.defaultSecondaryContentHeight : 0.0 + let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? (NavigationBar.defaultSecondaryContentHeight * self.secondaryContentNodeDisplayFraction) : 0.0 let leftButtonInset: CGFloat = leftInset + 16.0 let backButtonInset: CGFloat = leftInset + 27.0 @@ -1218,11 +1219,11 @@ open class NavigationBar: ASDisplayNode { case .expansion: expansionHeight = contentNode.height - let additionalExpansionHeight: CGFloat = self.secondaryContentNode != nil && appearsHidden ? NavigationBar.defaultSecondaryContentHeight : 0.0 + let additionalExpansionHeight: CGFloat = self.secondaryContentNode != nil && appearsHidden ? (NavigationBar.defaultSecondaryContentHeight * self.secondaryContentNodeDisplayFraction) : 0.0 contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - (appearsHidden ? 0.0 : additionalContentHeight) - expansionHeight - apparentAdditionalHeight - additionalExpansionHeight), size: CGSize(width: size.width, height: expansionHeight)) if appearsHidden { if self.secondaryContentNode != nil { - contentNodeFrame.origin.y += NavigationBar.defaultSecondaryContentHeight + contentNodeFrame.origin.y += NavigationBar.defaultSecondaryContentHeight * self.secondaryContentNodeDisplayFraction } } } @@ -1545,7 +1546,7 @@ open class NavigationBar: ASDisplayNode { } if let _ = self.secondaryContentNode { - result += NavigationBar.defaultSecondaryContentHeight + result += NavigationBar.defaultSecondaryContentHeight * self.secondaryContentNodeDisplayFraction } return result diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift index e38ac68f4d..50adb454d9 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionControllerNode.swift @@ -279,7 +279,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode { combinedInsets.right += layout.safeInsets.right let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: combinedInsets, headerInsets: headerInsets, duration: duration, curve: curve) - chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil) + chatsNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, originalTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0) } self.contentNode.node.frame = CGRect(origin: CGPoint(), size: layout.size) diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 958645739a..ad695a6d86 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -576,7 +576,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: layout.size, insets: insets, headerInsets: headerInsets, duration: duration, curve: curve) - self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil) + self.chatListNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets, visibleTopInset: updateSizeAndInsets.insets.top, originalTopInset: updateSizeAndInsets.insets.top, inlineNavigationLocation: nil, inlineNavigationTransitionFraction: 0.0) if let contactListNode = self.contactListNode { contactListNode.bounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) From f9f194f04c987c053063f88e07976a2c5d88cdd7 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 17 Nov 2022 01:38:41 +0400 Subject: [PATCH 05/12] Fix build --- submodules/ChatListUI/BUILD | 1 + .../Sources/Node/ChatListNode.swift | 2 +- submodules/Display/Source/NavigationBar.swift | 22 +++++++++++++++++++ .../Components/ChatListHeaderComponent/BUILD | 20 +++++++++++++++++ .../Sources/ChatListHeaderComponent.swift | 15 +++++++++++++ 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD create mode 100644 submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 2853d4113a..13a6fd7410 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -84,6 +84,7 @@ swift_library( "//submodules/TelegramUI/Components/NotificationPeerExceptionController", "//submodules/AnimationUI:AnimationUI", "//submodules/PeerInfoUI", + "//submodules/TelegramUI/Components/ChatListHeaderComponent:ChatListHeaderComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 9907331c0e..52348dde50 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1966,7 +1966,7 @@ public final class ChatListNode: ListView { } }) - self.visibleContentOffsetChanged = { [weak self] offset, transition in + self.visibleContentOffsetChanged = { [weak self] offset in guard let strongSelf = self else { return } diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 6931a979a5..1721ee5f84 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -438,6 +438,10 @@ open class BlurredBackgroundView: UIView { } } +public protocol NavigationBarHeaderView: UIView { + func update(size: CGSize, transition: ContainedViewLayoutTransition) +} + open class NavigationBar: ASDisplayNode { public static var defaultSecondaryContentHeight: CGFloat { return 38.0 @@ -647,6 +651,18 @@ open class NavigationBar: ASDisplayNode { } } + public var customHeaderContentView: NavigationBarHeaderView? { + didSet { + if self.customHeaderContentView !== oldValue { + self.customHeaderContentView?.removeFromSuperview() + + if let customHeaderContentView = self.customHeaderContentView { + self.view.addSubview(customHeaderContentView) + } + } + } + } + public var layoutSuspended: Bool = false private let titleNode: ImmediateTextNode @@ -1355,6 +1371,12 @@ open class NavigationBar: ASDisplayNode { leftTitleInset -= 1.0 } + if let customHeaderContentView = self.customHeaderContentView { + let headerSize = CGSize(width: size.width, height: nominalHeight) + customHeaderContentView.update(size: headerSize, transition: transition) + transition.updateFrame(view: customHeaderContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentVerticalOrigin), size: headerSize)) + } + if self.titleNode.supernode != nil { let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD new file mode 100644 index 0000000000..7467c13e9f --- /dev/null +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListHeaderComponent", + module_name = "ChatListHeaderComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Display:Display", + "//submodules/ComponentFlow:ComponentFlow", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift new file mode 100644 index 0000000000..561e32fd15 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -0,0 +1,15 @@ +import Foundation +import UIKit +import Display +import ComponentFlow + +/*public final class ChatListHeaderComponent: Component { + public final class View: UIView, NavigationBarHeaderView { + public func update(size: CGSize, transition: ContainedViewLayoutTransition) { + + } + + + } +} +*/ From 6a708ac1c28c2b5190a8f338ae4b370fef2ed1b4 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 18 Nov 2022 02:16:33 +0400 Subject: [PATCH 06/12] [WIP] Inline forums --- submodules/ChatListUI/BUILD | 3 +- .../ChatListUI/Sources/ChatContextMenus.swift | 14 +- .../Sources/ChatListController.swift | 1336 ++++++++++------- .../Sources/ChatListControllerNode.swift | 10 +- .../Sources/ChatListSearchListPaneNode.swift | 2 +- .../Sources/Node/ChatListBadgeNode.swift | 52 +- .../Sources/Node/ChatListItem.swift | 73 +- submodules/Display/Source/NavigationBar.swift | 40 +- .../Resources/PresentationResourceKey.swift | 1 + .../PresentationResourcesChatList.swift | 6 + submodules/TelegramUI/BUILD | 1 + .../Components/ChatListHeaderComponent/BUILD | 12 +- .../Sources/ChatListHeaderComponent.swift | 930 +++++++++++- .../Sources/MoreHeaderButton.swift | 168 +++ .../Components/ChatListTitleView/BUILD | 30 + .../Sources/ChatListTitleLockView.swift | 0 .../Sources/ChatListTitleProxyNode.swift | 10 +- .../Sources/ChatListTitleView.swift | 80 +- .../TelegramUI/Components/ChatTitleView/BUILD | 1 + .../ChatTitleView/Sources/ChatTitleView.swift | 176 ++- .../TelegramUI/Sources/ChatController.swift | 1 + 21 files changed, 2268 insertions(+), 678 deletions(-) create mode 100644 submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/MoreHeaderButton.swift create mode 100644 submodules/TelegramUI/Components/ChatListTitleView/BUILD rename submodules/{ChatListUI => TelegramUI/Components/ChatListTitleView}/Sources/ChatListTitleLockView.swift (100%) rename submodules/{ChatListUI => TelegramUI/Components/ChatListTitleView}/Sources/ChatListTitleProxyNode.swift (94%) rename submodules/{ChatListUI => TelegramUI/Components/ChatListTitleView}/Sources/ChatListTitleView.swift (89%) diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 13a6fd7410..89931e38b5 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -84,7 +84,8 @@ swift_library( "//submodules/TelegramUI/Components/NotificationPeerExceptionController", "//submodules/AnimationUI:AnimationUI", "//submodules/PeerInfoUI", - "//submodules/TelegramUI/Components/ChatListHeaderComponent:ChatListHeaderComponent", + "//submodules/TelegramUI/Components/ChatListHeaderComponent", + "//submodules/TelegramUI/Components/ChatListTitleView", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 85133accc4..9cb7a38ad3 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -490,7 +490,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } } -func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: Int64, isPinned: Bool?, isClosed: Bool?, chatListController: ChatListControllerImpl?, joined: Bool) -> Signal<[ContextMenuItem], NoError> { +func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: Int64, isPinned: Bool?, isClosed: Bool?, chatListController: ChatListControllerImpl?, joined: Bool, canSelect: Bool) -> Signal<[ContextMenuItem], NoError> { let presentationData = context.sharedContext.currentPresentationData.with({ $0 }) let strings = presentationData.strings @@ -763,11 +763,13 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: } } - items.append(.separator) - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Select, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in - f(.default) - chatListController?.selectPeerThread(peerId: peerId, threadId: threadId) - }))) + if canSelect { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Select, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor) }, action: { _, f in + f(.default) + chatListController?.selectPeerThread(peerId: peerId, threadId: threadId) + }))) + } return .single(items) } diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 9ccbc00ca3..cf8fd86738 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -40,6 +40,9 @@ import ForumCreateTopicScreen import AnimationUI import ChatTitleView import PeerInfoUI +import ComponentDisplayAdapters +import ChatListHeaderComponent +import ChatListTitleView private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool { if listNode.scroller.isDragging { @@ -112,161 +115,6 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent } } -public final class MoreHeaderButton: HighlightableButtonNode { - public enum Content { - case image(UIImage?) - case more(UIImage?) - } - - public let referenceNode: ContextReferenceContentNode - public let containerNode: ContextControllerSourceNode - private let iconNode: ASImageNode - private var animationNode: AnimationNode? - - public var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? - - private var color: UIColor - - public init(color: UIColor) { - self.color = color - - self.referenceNode = ContextReferenceContentNode() - self.containerNode = ContextControllerSourceNode() - self.containerNode.animateScale = false - self.iconNode = ASImageNode() - self.iconNode.displaysAsynchronously = false - self.iconNode.displayWithoutProcessing = true - self.iconNode.contentMode = .scaleToFill - - super.init() - - self.containerNode.addSubnode(self.referenceNode) - self.referenceNode.addSubnode(self.iconNode) - self.addSubnode(self.containerNode) - - self.containerNode.shouldBegin = { [weak self] location in - guard let strongSelf = self, let _ = strongSelf.contextAction else { - return false - } - return true - } - self.containerNode.activated = { [weak self] gesture, _ in - guard let strongSelf = self else { - return - } - strongSelf.contextAction?(strongSelf.containerNode, gesture) - } - - self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0)) - self.referenceNode.frame = self.containerNode.bounds - - self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color) - if let image = self.iconNode.image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) - } - - self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0) - } - - private var content: Content? - public func setContent(_ content: Content, animated: Bool = false) { - if case .more = content, self.animationNode == nil { - let iconColor = self.color - let animationNode = AnimationNode(animation: "anim_profilemore", colors: ["Point 2.Group 1.Fill 1": iconColor, - "Point 3.Group 1.Fill 1": iconColor, - "Point 1.Group 1.Fill 1": iconColor], scale: 1.0) - let animationSize = CGSize(width: 22.0, height: 22.0) - animationNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - animationSize.width) / 2.0), y: floor((self.containerNode.bounds.height - animationSize.height) / 2.0)), size: animationSize) - self.addSubnode(animationNode) - self.animationNode = animationNode - } - if animated { - if let snapshotView = self.referenceNode.view.snapshotContentTree() { - snapshotView.frame = self.referenceNode.frame - self.view.addSubview(snapshotView) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false) - - self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3) - - self.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - self.animationNode?.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3) - } - - switch content { - case let .image(image): - if let image = image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) - } - - self.iconNode.image = image - self.iconNode.isHidden = false - self.animationNode?.isHidden = true - case let .more(image): - if let image = image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) - } - - self.iconNode.image = image - self.iconNode.isHidden = false - self.animationNode?.isHidden = false - } - } else { - self.content = content - switch content { - case let .image(image): - if let image = image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) - } - - self.iconNode.image = image - self.iconNode.isHidden = false - self.animationNode?.isHidden = true - case let .more(image): - if let image = image { - self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) - } - - self.iconNode.image = image - self.iconNode.isHidden = false - self.animationNode?.isHidden = false - } - } - } - - override public func didLoad() { - super.didLoad() - self.view.isOpaque = false - } - - override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - return CGSize(width: 22.0, height: 44.0) - } - - public func onLayout() { - } - - public func play() { - self.animationNode?.playOnce() - } - - public static func optionsCircleImage(color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setStrokeColor(color.cgColor) - let lineWidth: CGFloat = 1.3 - context.setLineWidth(lineWidth) - - context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth, dy: lineWidth)) - }) - } -} - public class ChatListControllerImpl: TelegramBaseController, ChatListController { private var validLayout: ContainerViewLayout? @@ -286,15 +134,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return super.displayNode as! ChatListControllerNode } - private var titleView: ChatListTitleView? - private var chatTitleView: ChatTitleView? - private let infoReady = Promise() + private let headerContentView = ComponentView() - private var proxyUnavailableTooltipController: TooltipController? - private var didShowProxyUnavailableTooltipController = false + private var primaryContext: ChatListLocationContext? + private let primaryInfoReady = Promise() + + private var pendingSecondaryContext: ChatListLocationContext? + private var secondaryContext: ChatListLocationContext? - private var titleDisposable: Disposable? - private var chatTitleDisposable: Disposable? private var badgeDisposable: Disposable? private var badgeIconDisposable: Disposable? @@ -337,17 +184,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController private weak var emojiStatusSelectionController: ViewController? - private let moreBarButton: MoreHeaderButton - private let moreBarButtonItem: UIBarButtonItem private var forumChannelTracker: ForumChannelTopics? - private var backNavigationItem: UIBarButtonItem? - private let selectAddMemberDisposable = MetaDisposable() private let addMemberDisposable = MetaDisposable() private let joinForumDisposable = MetaDisposable() private let actionDisposables = DisposableSet() + private var plainTitle: String = "" + public override func updateNavigationCustomData(_ data: Any?, progress: CGFloat, transition: ContainedViewLayoutTransition) { if self.isNodeLoaded { self.chatListDisplayNode.containerNode.updateSelectedChatLocation(data: data as? ChatLocation, progress: progress, transition: transition) @@ -371,31 +216,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let groupCallPanelSource: GroupCallPanelSource switch self.location { case .chatList: - self.titleView = ChatListTitleView( - context: context, - theme: self.presentationData.theme, - strings: self.presentationData.strings, - animationCache: self.animationCache, - animationRenderer: self.animationRenderer - ) groupCallPanelSource = .all case let .forum(peerId): - self.chatTitleView = ChatTitleView(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.context.animationCache, animationRenderer: self.context.animationRenderer) groupCallPanelSource = .peer(peerId) } - self.moreBarButton = MoreHeaderButton(color: self.presentationData.theme.rootController.navigationBar.buttonColor) - self.moreBarButton.isUserInteractionEnabled = true - self.moreBarButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: self.presentationData.theme.rootController.navigationBar.buttonColor))) - - self.moreBarButtonItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)! - self.tabContainerNode = ChatListFilterTabContainerNode() super.init(context: context, navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData), mediaAccessoryPanelVisibility: .always, locationBroadcastPanelSource: .summary, groupCallPanelSource: groupCallPanelSource) - self.backNavigationItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.navigationBackPressed)) - self.tabBarItemContextActionType = .always self.automaticallyControlPresentationContextLayout = false @@ -409,21 +238,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } else { title = self.presentationData.strings.ChatList_ArchivedChatsTitle } + self.plainTitle = title case let .forum(peerId): title = "" self.forumChannelTracker = ForumChannelTopics(account: self.context.account, peerId: peerId) - self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in - guard let self = self else { - return - } - guard case let .forum(peerId) = self.location else { - return - } - ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: self, isViewingAsTopics: true, sourceView: sourceNode.view, gesture: gesture) - } - self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside) - self.navigationBar?.userInfo = PeerInfoNavigationSourceTag(peerId: peerId) self.navigationBar?.allowsCustomTransition = { [weak self] in guard let strongSelf = self else { @@ -432,109 +251,38 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if strongSelf.navigationBar?.userInfo == nil { return false } + + //TODO:loc + if "".isEmpty { + return false + } return true } } switch self.location { case .chatList: - if let titleView = self.titleView { + /*if let titleView = self.titleView { titleView.title = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil) - self.navigationItem.titleView = titleView - - titleView.openStatusSetup = { [weak self] sourceView in - self?.openStatusSetup(sourceView: sourceView) - } - } - self.infoReady.set(.single(true)) - case let .forum(peerId): - if let chatTitleView = self.chatTitleView { - self.navigationItem.titleView = chatTitleView - - chatTitleView.pressed = { [weak self] in - guard let self = self else { - return - } - let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) - ) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self = self, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { - return - } - (self.navigationController as? NavigationController)?.pushViewController(controller) - }) - } - self.chatTitleView?.longPressed = { [weak self] in - guard let self else { - return - } - self.activateSearch() - } - - let peerView = Promise() - peerView.set(context.account.viewTracker.peerView(peerId)) - - var onlineMemberCount: Signal = .single(nil) - - let recentOnlineSignal: Signal = peerView.get() - |> map { view -> Bool? in - if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { - if case .broadcast = peer.info { - return nil - } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { - return true - } else { - return false - } - } else { - return false - } - } - |> distinctUntilChanged - |> mapToSignal { isLarge -> Signal in - if let isLarge = isLarge { - if isLarge { - return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) - |> map(Optional.init) - } else { - return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) - |> map(Optional.init) - } - } else { - return .single(nil) - } - } - onlineMemberCount = recentOnlineSignal - - self.chatTitleDisposable = (combineLatest(queue: Queue.mainQueue(), - peerView.get(), - onlineMemberCount, - self.chatListDisplayNode.containerNode.currentItemState - ) - |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, stateAndFilterId in - guard let strongSelf = self, let chatTitleView = strongSelf.chatTitleView else { - return - } - - if stateAndFilterId.state.editing && stateAndFilterId.state.selectedThreadIds.count > 0 { - chatTitleView.titleContent = .custom(strongSelf.presentationData.strings.ChatList_SelectedTopics(Int32(stateAndFilterId.state.selectedThreadIds.count)), nil, false) - } else { - chatTitleView.titleContent = .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil) - } - - strongSelf.infoReady.set(.single(true)) - - if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, !channel.flags.contains(.isForum) { - if let navigationController = strongSelf.navigationController as? NavigationController { - let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) - navigationController.replaceController(strongSelf, with: chatController, animated: true) - } - } - }) - } + //self.navigationItem.titleView = titleView + }*/ + //self.primaryInfoReady.set(.single(true)) + break + case .forum: + break } + let primaryContext = ChatListLocationContext( + context: context, + location: self.location, + parentController: self, + hideNetworkActivityStatus: self.hideNetworkActivityStatus, + chatListDisplayNode: self.chatListDisplayNode, + isReorderingTabs: self.isReorderingTabsValue.get() + ) + self.primaryContext = primaryContext + self.primaryInfoReady.set(primaryContext.ready.get()) + if !previewing { switch self.location { case let .chatList(groupId): @@ -555,24 +303,34 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.tabBarItem.animationOffset = CGPoint(x: 0.0, y: UIScreenPixel) } - let leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) - leftBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit - self.navigationItem.leftBarButtonItem = leftBarButtonItem + self.primaryContext?.leftButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), + pressed: { [weak self] _ in + self?.editPressed() + } + ))) - let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) - rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose - self.navigationItem.rightBarButtonItem = rightBarButtonItem - let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil) - backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back - self.navigationItem.backBarButtonItem = backBarButtonItem + self.primaryContext?.rightButton = AnyComponentWithIdentity(id: "compose", component: AnyComponent(NavigationButtonComponent( + content: .icon(imageName: "Chat List/ComposeIcon"), + pressed: { [weak self] _ in + self?.composePressed() + } + ))) + + //let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.DialogList_Title, style: .plain, target: nil, action: nil) + //backBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Back + //self.navigationItem.backBarButtonItem = backBarButtonItem } else { switch self.location { case .chatList: - let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) - rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Common_Edit - self.navigationItem.rightBarButtonItem = rightBarButtonItem + self.primaryContext?.rightButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), + pressed: { [weak self] _ in + self?.editPressed() + } + ))) case .forum: - self.navigationItem.rightBarButtonItem = self.moreBarButtonItem + break } let backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) @@ -628,200 +386,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - let hasProxy = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings]) - |> map { sharedData -> (Bool, Bool) in - if let settings = sharedData.entries[SharedDataKeys.proxySettings]?.get(ProxySettings.self) { - return (!settings.servers.isEmpty, settings.enabled) - } else { - return (false, false) - } - } - |> distinctUntilChanged(isEqual: { lhs, rhs in - return lhs == rhs - }) - - let passcode = context.sharedContext.accountManager.accessChallengeData() - |> map { view -> (Bool, Bool) in - let data = view.data - return (data.isLockable, false) - } - - let peerStatus: Signal - switch self.location { - case .chatList(.root): - peerStatus = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) - |> map { peer -> NetworkStatusTitle.Status? in - guard case let .user(user) = peer else { - return nil - } - if let emojiStatus = user.emojiStatus { - return .emoji(emojiStatus) - } else if user.isPremium { - return .premium - } else { - return nil - } - } - |> distinctUntilChanged - default: - peerStatus = .single(nil) - } - - let previousEditingAndNetworkStateValue = Atomic<(Bool, AccountNetworkState)?>(value: nil) - if !self.hideNetworkActivityStatus { - self.titleDisposable = combineLatest(queue: .mainQueue(), - context.account.networkState, - hasProxy, - passcode, - self.chatListDisplayNode.containerNode.currentItemState, - self.isReorderingTabsValue.get(), - peerStatus - ).start(next: { [weak self] networkState, proxy, passcode, stateAndFilterId, isReorderingTabs, peerStatus in - if let strongSelf = self { - let defaultTitle: String - switch strongSelf.location { - case let .chatList(groupId): - if groupId == .root { - defaultTitle = strongSelf.presentationData.strings.DialogList_Title - } else { - defaultTitle = strongSelf.presentationData.strings.ChatList_ArchivedChatsTitle - } - case .forum: - defaultTitle = "" - } - let previousEditingAndNetworkState = previousEditingAndNetworkStateValue.swap((stateAndFilterId.state.editing, networkState)) - if stateAndFilterId.state.editing { - if case .chatList(.root) = strongSelf.location { - strongSelf.navigationItem.setRightBarButton(nil, animated: true) - } - let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? strongSelf.presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle - - var animated = false - if let (previousEditing, previousNetworkState) = previousEditingAndNetworkState { - if previousEditing != stateAndFilterId.state.editing, previousNetworkState == networkState, case .online = networkState { - animated = true - } - } - strongSelf.titleView?.setTitle(NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus), animated: animated) - } else if isReorderingTabs { - if case .chatList(.root) = strongSelf.location { - strongSelf.navigationItem.setRightBarButton(nil, animated: true) - } - let leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed)) - strongSelf.navigationItem.setLeftBarButton(leftBarButtonItem, animated: true) - - let (_, connectsViaProxy) = proxy - switch networkState { - case .waitingForNetwork: - strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) - case let .connecting(proxy): - var text = strongSelf.presentationData.strings.State_Connecting - if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 { - text = strongSelf.presentationData.strings.State_ConnectingToProxy - } - strongSelf.titleView?.title = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) - case .updating: - strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) - case .online: - strongSelf.titleView?.title = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) - } - } else { - var isRoot = false - if case .chatList(.root) = strongSelf.location { - isRoot = true - - if isReorderingTabs { - strongSelf.navigationItem.setRightBarButton(nil, animated: true) - } else { - let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.composePressed)) - rightBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.VoiceOver_Navigation_Compose - if strongSelf.navigationItem.rightBarButtonItem?.accessibilityLabel != rightBarButtonItem.accessibilityLabel { - strongSelf.navigationItem.setRightBarButton(rightBarButtonItem, animated: true) - } - } - - if isReorderingTabs { - let leftBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.reorderingDonePressed)) - leftBarButtonItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Done - if strongSelf.navigationItem.leftBarButtonItem?.accessibilityLabel != leftBarButtonItem.accessibilityLabel { - strongSelf.navigationItem.setLeftBarButton(leftBarButtonItem, animated: true) - } - } else if strongSelf.chatListDisplayNode.inlineStackContainerTransitionFraction != 0.0 { - } else { - let editItem: UIBarButtonItem - if stateAndFilterId.state.editing { - editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(strongSelf.donePressed)) - editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Done - } else { - editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(strongSelf.editPressed)) - editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Edit - } - if strongSelf.navigationItem.leftBarButtonItem?.accessibilityLabel != editItem.accessibilityLabel { - strongSelf.navigationItem.setLeftBarButton(editItem, animated: true) - } - } - } else { - switch strongSelf.location { - case .chatList: - let editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(strongSelf.editPressed)) - editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Edit - strongSelf.navigationItem.setRightBarButton(editItem, animated: true) - case .forum: - if strongSelf.navigationItem.rightBarButtonItem !== strongSelf.moreBarButtonItem { - strongSelf.navigationItem.setRightBarButton(strongSelf.moreBarButtonItem, animated: true) - } - } - } - - let (hasProxy, connectsViaProxy) = proxy - let (isPasscodeSet, isManuallyLocked) = passcode - var checkProxy = false - switch networkState { - case .waitingForNetwork: - strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) - case let .connecting(proxy): - var text = strongSelf.presentationData.strings.State_Connecting - if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 { - text = strongSelf.presentationData.strings.State_ConnectingToProxy - } - if let proxy = proxy, proxy.hasConnectionIssues { - checkProxy = true - } - strongSelf.titleView?.title = NetworkStatusTitle(text: text, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) - case .updating: - strongSelf.titleView?.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) - case .online: - strongSelf.titleView?.setTitle(NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus), animated: (previousEditingAndNetworkState?.0 ?? false) != stateAndFilterId.state.editing) - } - if case .chatList(.root) = location, checkProxy { - if strongSelf.proxyUnavailableTooltipController == nil && !strongSelf.didShowProxyUnavailableTooltipController && strongSelf.isNodeLoaded && strongSelf.displayNode.view.window != nil && strongSelf.navigationController?.topViewController === self { - strongSelf.didShowProxyUnavailableTooltipController = true - let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true) - strongSelf.proxyUnavailableTooltipController = tooltipController - tooltipController.dismissed = { [weak tooltipController] _ in - if let strongSelf = self, let tooltipController = tooltipController, strongSelf.proxyUnavailableTooltipController === tooltipController { - strongSelf.proxyUnavailableTooltipController = nil - } - } - strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { - if let strongSelf = self, let titleView = strongSelf.titleView, let rect = titleView.proxyButtonFrame { - return (titleView, rect.insetBy(dx: 0.0, dy: -4.0)) - } - return nil - })) - } - } else { - strongSelf.didShowProxyUnavailableTooltipController = false - if let proxyUnavailableTooltipController = strongSelf.proxyUnavailableTooltipController { - strongSelf.proxyUnavailableTooltipController = nil - proxyUnavailableTooltipController.dismiss() - } - } - } - } - }) - } - self.badgeDisposable = (combineLatest(renderedTotalUnreadCount(accountManager: context.sharedContext.accountManager, engine: context.engine), self.presentationDataValue.get()) |> deliverOnMainQueue).start(next: { [weak self] count, presentationData in if let strongSelf = self { if count.0 == 0 { @@ -832,18 +396,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } }) - self.titleView?.toggleIsLocked = { [weak self] in - if let strongSelf = self { - strongSelf.context.sharedContext.appLockContext.lock() - } - } - - self.titleView?.openProxySettings = { [weak self] in - if let strongSelf = self { - (strongSelf.navigationController as? NavigationController)?.pushViewController(context.sharedContext.makeProxySettingsController(context: context)) - } - } - self.presentationDataDisposable = (context.sharedContext.presentationData |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { @@ -1178,8 +730,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController deinit { self.openMessageFromSearchDisposable.dispose() - self.titleDisposable?.dispose() - self.chatTitleDisposable?.dispose() self.badgeDisposable?.dispose() self.badgeIconDisposable?.dispose() self.passcodeLockTooltipDisposable.dispose() @@ -1197,12 +747,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.actionDisposables.dispose() } + func findTitleView() -> ChatListTitleView? { + guard let componentView = self.headerContentView.view as? ChatListHeaderComponent.View else { + return nil + } + return componentView.findTitleView() + } + private func openStatusSetup(sourceView: UIView) { self.emojiStatusSelectionController?.dismiss() var selectedItems = Set() var topStatusTitle = self.presentationData.strings.PeerStatusSetup_NoTimerTitle var currentSelection: Int64? - if let peerStatus = self.titleView?.title.peerStatus, case let .emoji(emojiStatus) = peerStatus { + if let peerStatus = self.findTitleView()?.title.peerStatus, case let .emoji(emojiStatus) = peerStatus { selectedItems.insert(MediaId(namespace: Namespaces.Media.CloudFile, id: emojiStatus.fileId)) currentSelection = emojiStatus.fileId @@ -1266,35 +823,30 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } self.searchContentNode?.updateThemeAndPlaceholder(theme: self.presentationData.theme, placeholder: placeholder, compactPlaceholder: compactPlaceholder) - let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing - let editItem: UIBarButtonItem - if editing { - editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) - editItem.accessibilityLabel = self.presentationData.strings.Common_Done + /*let editing = self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing + if case .chatList(.root) = self.location { + self.primaryContext?.leftButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), + pressed: { [weak self] in + self?.editPressed() + } + ))) + self.primaryContext?.rightButton = AnyComponentWithIdentity(id: "compose", component: AnyComponent(NavigationButtonComponent( + content: .icon(imageName: "Chat List/Compose Icon"), + pressed: { [weak self] in + self?.composePressed() + } + ))) } else { - switch self.location { - case .chatList: - editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) - editItem.accessibilityLabel = self.presentationData.strings.Common_Edit - case .forum: - editItem = self.moreBarButtonItem - } - } - if self.chatListDisplayNode.inlineStackContainerTransitionFraction != 0.0 { - self.backNavigationItem?.title = self.presentationData.strings.Common_Back - } else if case .chatList(.root) = self.location { - self.navigationItem.leftBarButtonItem = editItem - let rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationComposeIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.composePressed)) - rightBarButtonItem.accessibilityLabel = self.presentationData.strings.VoiceOver_Navigation_Compose - self.navigationItem.rightBarButtonItem = rightBarButtonItem - } else { - self.navigationItem.rightBarButtonItem = editItem - } + self.primaryContext?.rightButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), + pressed: { [weak self] in + self?.editPressed() + } + ))) + }*/ - self.titleView?.theme = self.presentationData.theme - self.titleView?.strings = self.presentationData.strings - - self.chatTitleView?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, hasEmbeddedTitleContent: false) + self.primaryContext?.updatePresentationData(presentationData: self.presentationData) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) @@ -1306,6 +858,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if self.isNodeLoaded { self.chatListDisplayNode.updatePresentationData(self.presentationData) } + + self.requestUpdateHeaderContent(transition: .immediate) } override public func loadDisplayNode() { @@ -1395,15 +949,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if case let .channel(channel) = peer, channel.flags.contains(.isForum), threadId == nil { strongSelf.chatListDisplayNode.clearHighlightAnimated(true) - if strongSelf.context.sharedContext.immediateExperimentalUISettings.inlineForums { - strongSelf.chatListDisplayNode.setInlineChatList(location: .forum(peerId: channel.id)) - - if strongSelf.navigationItem.leftBarButtonItem !== strongSelf.backNavigationItem { - strongSelf.navigationItem.setLeftBarButton(strongSelf.backNavigationItem, animated: true) - } - } else { - strongSelf.context.sharedContext.navigateToForumChannel(context: strongSelf.context, peerId: channel.id, navigationController: navigationController) - } + strongSelf.setInlineChatList(location: .forum(peerId: channel.id)) } else { if let threadId = threadId { let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start() @@ -1692,7 +1238,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: strongSelf.chatListDisplayNode.inlineStackContainerNode == nil) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } else { let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false) @@ -1728,7 +1274,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController chatController.canReadHistory.set(false) source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)) - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, isClosed: threadInfo?.isClosed, chatListController: strongSelf, joined: joined) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: isPinned, isClosed: threadInfo?.isClosed, chatListController: strongSelf, joined: joined, canSelect: strongSelf.chatListDisplayNode.inlineStackContainerNode == nil) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) } } @@ -2126,7 +1672,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } else { self.ready.set(combineLatest([ self.chatListDisplayNode.containerNode.ready, - self.infoReady.get() + self.primaryInfoReady.get() ]) |> map { values -> Bool in return !values.contains(where: { !$0 }) @@ -2204,7 +1750,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController }) #endif - if let lockViewFrame = self.titleView?.lockViewFrame, !self.didShowPasscodeLockTooltipController { + if let lockViewFrame = self.findTitleView()?.lockViewFrame, !self.didShowPasscodeLockTooltipController { self.passcodeLockTooltipDisposable.set(combineLatest(queue: .mainQueue(), ApplicationSpecificNotice.getPasscodeLockTips(accountManager: self.context.sharedContext.accountManager), self.context.sharedContext.accountManager.accessChallengeData() |> take(1)).start(next: { [weak self] tooltipValue, passcodeView in if let strongSelf = self { if !tooltipValue { @@ -2214,7 +1760,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.DialogList_PasscodeLockHelp), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, dismissByTapOutside: true) strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { [weak self] in - if let strongSelf = self, let titleView = strongSelf.titleView { + if let strongSelf = self, let titleView = strongSelf.findTitleView() { return (titleView, lockViewFrame.offsetBy(dx: 4.0, dy: 14.0)) } return nil @@ -2482,14 +2028,109 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.chatListDisplayNode.containerNode.currentItemNode.clearHighlightAnimated(true) } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.navigationBar?.secondaryContentNodeDisplayFraction = 1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction + func requestUpdateHeaderContent(transition: ContainedViewLayoutTransition) { + if let validLayout = self.validLayout { + self.updateHeaderContent(layout: validLayout, transition: transition) + } + } + + private func updateHeaderContent(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var primaryContent: ChatListHeaderComponent.Content? + if let primaryContext = self.primaryContext { + var backTitle: String? + if let previousItem = self.navigationBar?.previousItem { + switch previousItem { + case let .item(item): + backTitle = item.title ?? self.presentationData.strings.Common_Back + case .close: + backTitle = self.presentationData.strings.Common_Close + } + } + primaryContent = ChatListHeaderComponent.Content( + title: self.plainTitle, + titleComponent: primaryContext.chatTitleComponent.flatMap { AnyComponent($0) }, + chatListTitle: primaryContext.chatListTitle, + leftButton: primaryContext.leftButton, + rightButtons: primaryContext.rightButtons, + backTitle: backTitle, + backPressed: backTitle != nil ? { [weak self] in + guard let self else { + return + } + self.navigationBackPressed() + } : nil + ) + } + var secondaryContent: ChatListHeaderComponent.Content? + if let secondaryContext = self.secondaryContext { + secondaryContent = ChatListHeaderComponent.Content( + title: self.plainTitle, + titleComponent: secondaryContext.chatTitleComponent.flatMap { AnyComponent($0) }, + chatListTitle: secondaryContext.chatListTitle, + leftButton: secondaryContext.leftButton, + rightButtons: secondaryContext.rightButtons, + backTitle: nil, + backPressed: { [weak self] in + guard let self else { + return + } + self.setInlineChatList(location: nil) + } + ) + } + + let _ = self.headerContentView.update( + transition: Transition(transition), + component: AnyComponent(ChatListHeaderComponent( + sideInset: layout.safeInsets.left + 16.0, + primaryContent: primaryContent, + secondaryContent: secondaryContent, + secondaryTransition: self.chatListDisplayNode.inlineStackContainerTransitionFraction, + networkStatus: nil, + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + openStatusSetup: { [weak self] sourceView in + guard let self else { + return + } + self.openStatusSetup(sourceView: sourceView) + }, + toggleIsLocked: { [weak self] in + guard let self else { + return + } + self.context.sharedContext.appLockContext.lock() + } + )), + environment: {}, + containerSize: CGSize(width: layout.size.width, height: 44.0) + ) + if let componentView = self.headerContentView.view as? NavigationBarHeaderView { + if self.navigationBar?.customHeaderContentView !== componentView { + self.navigationBar?.customHeaderContentView = componentView + } + } + } + + override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + if self.chatListDisplayNode.searchDisplayController?.contentNode != nil { + self.navigationBar?.secondaryContentNodeDisplayFraction = 1.0 + } else { + self.navigationBar?.secondaryContentNodeDisplayFraction = 1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction + } + + self.updateHeaderContent(layout: layout, transition: transition) + + super.updateNavigationBarLayout(layout, transition: transition) if let inlineStackContainerNode = self.chatListDisplayNode.inlineStackContainerNode { let _ = inlineStackContainerNode } else { } - + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) let wasInVoiceOver = self.validLayout?.inVoiceOver ?? false @@ -2526,16 +2167,27 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController super.navigationStackConfigurationUpdated(next: next) } - @objc private func editPressed() { - let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) - editItem.accessibilityLabel = self.presentationData.strings.Common_Done + @objc fileprivate func editPressed() { if case .chatList(.root) = self.location { - self.navigationItem.setLeftBarButton(editItem, animated: true) + self.primaryContext?.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + self?.donePressed() + } + ))) (self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(.details, transition: .animated(duration: 0.5, curve: .spring)) } else { - self.navigationItem.setRightBarButton(editItem, animated: true) + self.primaryContext?.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + self?.donePressed() + } + ))) (self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(.master, transition: .animated(duration: 0.5, curve: .spring)) } + + self.requestUpdateHeaderContent(transition: .animated(duration: 0.3, curve: .spring)) + self.searchContentNode?.setIsEnabled(false, animated: true) self.chatListDisplayNode.didBeginSelectingChatsWhileEditing = false @@ -2551,7 +2203,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - @objc private func donePressed() { + @objc fileprivate func donePressed() { self.reorderingDonePressed() (self.navigationController as? NavigationController)?.updateMasterDetailsBlackout(nil, transition: .animated(duration: 0.4, curve: .spring)) @@ -2571,7 +2223,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - @objc private func reorderingDonePressed() { + @objc fileprivate func reorderingDonePressed() { + if !self.chatListDisplayNode.isReorderingFilters { + return + } + var reorderedFilterIdsValue: [Int32]? if let reorderedFilterIds = self.tabContainerNode.reorderedFilterIds { reorderedFilterIdsValue = reorderedFilterIds @@ -2614,20 +2270,35 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } - @objc private func moreButtonPressed() { - self.moreBarButton.play() - self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil) + func setInlineChatList(location: ChatListControllerLocation?) { + if let location { + let pendingSecondaryContext = ChatListLocationContext( + context: self.context, + location: location, + parentController: self, + hideNetworkActivityStatus: false, + chatListDisplayNode: self.chatListDisplayNode, + isReorderingTabs: .single(false) + ) + self.pendingSecondaryContext = pendingSecondaryContext + let _ = (pendingSecondaryContext.ready.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self, weak pendingSecondaryContext] _ in + guard let self, let pendingSecondaryContext = pendingSecondaryContext, self.pendingSecondaryContext === pendingSecondaryContext else { + return + } + self.secondaryContext = pendingSecondaryContext + self.chatListDisplayNode.setInlineChatList(location: location) + }) + } else { + self.secondaryContext = nil + self.chatListDisplayNode.setInlineChatList(location: nil) + } } - @objc private func navigationBackPressed() { - self.chatListDisplayNode.setInlineChatList(location: nil) - - if self.navigationItem.leftBarButtonItem === self.backNavigationItem { - let editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) - editItem.accessibilityLabel = self.presentationData.strings.Common_Edit - - self.navigationItem.setLeftBarButton(editItem, animated: true) - } + private func navigationBackPressed() { + self.dismiss() } public static func openMoreMenu(context: AccountContext, peerId: EnginePeer.Id, sourceController: ViewController, isViewingAsTopics: Bool, sourceView: UIView, gesture: ContextGesture?) { @@ -2653,8 +2324,27 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return } - let chatController = context.sharedContext.makeChatListController(context: context, location: .forum(peerId: peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) - navigationController.replaceController(sourceController, with: chatController, animated: false) + if let targetController = navigationController.viewControllers.first(where: { controller in + var checkController = controller + if let tabBarController = checkController as? TabBarController { + if let currentController = tabBarController.currentController { + checkController = currentController + } else { + return false + } + } + if let controller = checkController as? ChatListControllerImpl { + if controller.chatListDisplayNode.inlineStackContainerNode?.location == .forum(peerId: peerId) { + return true + } + } + return false + }) { + let _ = navigationController.popToViewController(targetController, animated: true) + } else { + let chatController = context.sharedContext.makeChatListController(context: context, location: .forum(peerId: peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) + navigationController.replaceController(sourceController, with: chatController, animated: false) + } }))) items.append(.action(ContextMenuActionItem(text: strings.Chat_ContextViewAsMessages, icon: { theme in if isViewingAsTopics { @@ -2669,7 +2359,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) - navigationController.replaceController(sourceController, with: chatController, animated: false) + + if let sourceController = sourceController as? ChatListControllerImpl, case .forum(peerId) = sourceController.location { + navigationController.replaceController(sourceController, with: chatController, animated: false) + } else { + navigationController.pushViewController(chatController) + } }))) items.append(.separator) @@ -3147,7 +2842,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.composePressed() } - @objc private func composePressed() { + @objc fileprivate func composePressed() { guard let navigationController = self.navigationController as? NavigationController else { return } @@ -4230,7 +3925,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } public var lockViewFrame: CGRect? { - if let titleView = self.titleView, let lockViewFrame = titleView.lockViewFrame { + if let titleView = self.findTitleView(), let lockViewFrame = titleView.lockViewFrame { return titleView.convert(lockViewFrame, to: self.view) } else { return nil @@ -4457,3 +4152,490 @@ private final class HeaderContextReferenceContentSource: ContextReferenceContent return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds) } } + +private final class ChatListLocationContext { + let context: AccountContext + let location: ChatListControllerLocation + weak var parentController: ChatListControllerImpl? + var presentationData: PresentationData + + private var proxyUnavailableTooltipController: TooltipController? + private var didShowProxyUnavailableTooltipController = false + + private var titleDisposable: Disposable? + + private(set) var title: String = "" + private(set) var chatTitleComponent: ChatTitleComponent? + private(set) var chatListTitle: NetworkStatusTitle? + + var leftButton: AnyComponentWithIdentity? + var rightButton: AnyComponentWithIdentity? + var proxyButton: AnyComponentWithIdentity? + + var rightButtons: [AnyComponentWithIdentity] { + var result: [AnyComponentWithIdentity] = [] + if let rightButton = self.rightButton { + result.append(rightButton) + } + if let proxyButton = self.proxyButton { + result.append(proxyButton) + } + return result + } + + private let previousEditingAndNetworkStateValue = Atomic<(Bool, AccountNetworkState)?>(value: nil) + + private var didSetReady: Bool = false + let ready = Promise() + + init( + context: AccountContext, + location: ChatListControllerLocation, + parentController: ChatListControllerImpl, + hideNetworkActivityStatus: Bool, + chatListDisplayNode: ChatListControllerNode, + isReorderingTabs: Signal + ) { + self.context = context + self.location = location + self.parentController = parentController + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let hasProxy = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings]) + |> map { sharedData -> (Bool, Bool) in + if let settings = sharedData.entries[SharedDataKeys.proxySettings]?.get(ProxySettings.self) { + return (!settings.servers.isEmpty, settings.enabled) + } else { + return (false, false) + } + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs == rhs + }) + + let passcode = context.sharedContext.accountManager.accessChallengeData() + |> map { view -> (Bool, Bool) in + let data = view.data + return (data.isLockable, false) + } + + let peerStatus: Signal + switch self.location { + case .chatList(.root): + peerStatus = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + |> map { peer -> NetworkStatusTitle.Status? in + guard case let .user(user) = peer else { + return nil + } + if let emojiStatus = user.emojiStatus { + return .emoji(emojiStatus) + } else if user.isPremium { + return .premium + } else { + return nil + } + } + |> distinctUntilChanged + default: + peerStatus = .single(nil) + } + + switch location { + case .chatList: + if !hideNetworkActivityStatus { + self.titleDisposable = combineLatest(queue: .mainQueue(), + context.account.networkState, + hasProxy, + passcode, + chatListDisplayNode.containerNode.currentItemState, + isReorderingTabs, + peerStatus + ).start(next: { [weak self] networkState, proxy, passcode, stateAndFilterId, isReorderingTabs, peerStatus in + guard let self else { + return + } + self.updateChatList( + networkState: networkState, + proxy: proxy, + passcode: passcode, + stateAndFilterId: stateAndFilterId, + isReorderingTabs: isReorderingTabs, + peerStatus: peerStatus + ) + }) + } else { + self.didSetReady = true + self.ready.set(.single(true)) + } + case let .forum(peerId): + //self.navigationItem.titleView = chatTitleView + + /*chatTitleView.pressed = { [weak self] in + guard let self = self else { + return + } + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self = self, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + return + } + (self.navigationController as? NavigationController)?.pushViewController(controller) + }) + } + self.chatTitleView?.longPressed = { [weak self] in + guard let self else { + return + } + self.activateSearch() + }*/ + + let peerView = Promise() + peerView.set(context.account.viewTracker.peerView(peerId)) + + var onlineMemberCount: Signal = .single(nil) + + let recentOnlineSignal: Signal = peerView.get() + |> map { view -> Bool? in + if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel { + if case .broadcast = peer.info { + return nil + } else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 { + return true + } else { + return false + } + } else { + return false + } + } + |> distinctUntilChanged + |> mapToSignal { isLarge -> Signal in + if let isLarge = isLarge { + if isLarge { + return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } else { + return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId) + |> map(Optional.init) + } + } else { + return .single(nil) + } + } + onlineMemberCount = recentOnlineSignal + + self.titleDisposable = (combineLatest(queue: Queue.mainQueue(), + peerView.get(), + onlineMemberCount, + chatListDisplayNode.containerNode.currentItemState + ) + |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, stateAndFilterId in + guard let self else { + return + } + self.updateForum( + peerId: peerId, + peerView: peerView, + onlineMemberCount: onlineMemberCount, + stateAndFilterId: stateAndFilterId + ) + }) + } + } + + private func updateChatList( + networkState: AccountNetworkState, + proxy: (Bool, Bool), + passcode: (Bool, Bool), + stateAndFilterId: (state: ChatListNodeState, filterId: Int32?), + isReorderingTabs: Bool, + peerStatus: NetworkStatusTitle.Status? + ) { + let defaultTitle: String + switch location { + case let .chatList(groupId): + if groupId == .root { + defaultTitle = self.presentationData.strings.DialogList_Title + } else { + defaultTitle = self.presentationData.strings.ChatList_ArchivedChatsTitle + } + case .forum: + defaultTitle = "" + } + let previousEditingAndNetworkState = self.previousEditingAndNetworkStateValue.swap((stateAndFilterId.state.editing, networkState)) + + var titleContent: NetworkStatusTitle + + if stateAndFilterId.state.editing { + if case .chatList(.root) = self.location { + self.rightButton = nil + } + let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? self.presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle + + var animated = false + if let (previousEditing, previousNetworkState) = previousEditingAndNetworkState { + if previousEditing != stateAndFilterId.state.editing, previousNetworkState == networkState, case .online = networkState { + animated = true + } + } + titleContent = NetworkStatusTitle(text: title, activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + let _ = animated + } else if isReorderingTabs { + if case .chatList(.root) = self.location { + self.rightButton = nil + } + self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + self?.parentController?.reorderingDonePressed() + } + ))) + + let (_, connectsViaProxy) = proxy + + switch networkState { + case .waitingForNetwork: + titleContent = NetworkStatusTitle(text: self.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + case let .connecting(proxy): + let text = self.presentationData.strings.State_Connecting + let _ = proxy + /*if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 { + text = self.presentationData.strings.State_ConnectingToProxy + }*/ + titleContent = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + case .updating: + titleContent = NetworkStatusTitle(text: self.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + case .online: + titleContent = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + } + } else { + var isRoot = false + if case .chatList(.root) = self.location { + isRoot = true + + if isReorderingTabs { + self.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + self?.parentController?.editPressed() + } + ))) + } else { + self.rightButton = AnyComponentWithIdentity(id: "compose", component: AnyComponent(NavigationButtonComponent( + content: .icon(imageName: "Chat List/ComposeIcon"), + pressed: { [weak self] _ in + self?.parentController?.composePressed() + } + ))) + } + + if isReorderingTabs { + self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + self?.parentController?.reorderingDonePressed() + } + ))) + } else { + if stateAndFilterId.state.editing { + self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + self?.parentController?.donePressed() + } + ))) + } else { + self.leftButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), + pressed: { [weak self] _ in + self?.parentController?.editPressed() + } + ))) + } + } + } else { + self.rightButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), + pressed: { [weak self] _ in + self?.parentController?.editPressed() + } + ))) + } + + let (hasProxy, connectsViaProxy) = proxy + let (isPasscodeSet, isManuallyLocked) = passcode + var checkProxy = false + switch networkState { + case .waitingForNetwork: + titleContent = NetworkStatusTitle(text: self.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + case let .connecting(proxy): + let text = self.presentationData.strings.State_Connecting + /*if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 {*/ + //text = self.presentationData.strings.State_ConnectingToProxy + //} + if let proxy = proxy, proxy.hasConnectionIssues { + checkProxy = true + } + titleContent = NetworkStatusTitle(text: text, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + case .updating: + titleContent = NetworkStatusTitle(text: self.presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + case .online: + titleContent = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + } + + if titleContent.hasProxy { + let proxyStatus: ChatTitleProxyStatus + if titleContent.connectsViaProxy { + proxyStatus = titleContent.activity ? .connecting : .connected + } else { + proxyStatus = .available + } + + self.proxyButton = AnyComponentWithIdentity(id: "proxy", component: AnyComponent(NavigationButtonComponent( + content: .proxy(status: proxyStatus), + pressed: { [weak self] _ in + guard let self, let parentController = self.parentController else { + return + } + (parentController.navigationController as? NavigationController)?.pushViewController(self.context.sharedContext.makeProxySettingsController(context: self.context)) + } + ))) + + titleContent.hasProxy = false + titleContent.connectsViaProxy = false + } else { + self.proxyButton = nil + } + + self.chatListTitle = titleContent + + if case .chatList(.root) = self.location, checkProxy { + if self.proxyUnavailableTooltipController == nil, !self.didShowProxyUnavailableTooltipController, let parentController = self.parentController, parentController.isNodeLoaded, parentController.displayNode.view.window != nil, parentController.navigationController?.topViewController == nil { + self.didShowProxyUnavailableTooltipController = true + let tooltipController = TooltipController(content: .text(self.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true) + self.proxyUnavailableTooltipController = tooltipController + tooltipController.dismissed = { [weak self, weak tooltipController] _ in + if let strongSelf = self, let tooltipController = tooltipController, strongSelf.proxyUnavailableTooltipController === tooltipController { + strongSelf.proxyUnavailableTooltipController = nil + } + } + self.parentController?.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceViewAndRect: { [weak self] in + if let strongSelf = self, let titleView = strongSelf.parentController?.self.findTitleView(), let rect = titleView.proxyButtonFrame { + return (titleView, rect.insetBy(dx: 0.0, dy: -4.0)) + } + return nil + })) + } + } else { + self.didShowProxyUnavailableTooltipController = false + if let proxyUnavailableTooltipController = self.proxyUnavailableTooltipController { + self.proxyUnavailableTooltipController = nil + proxyUnavailableTooltipController.dismiss() + } + } + } + + if !self.didSetReady { + self.didSetReady = true + self.ready.set(.single(true)) + } + + self.parentController?.requestUpdateHeaderContent(transition: .immediate) + } + + private func updateForum( + peerId: EnginePeer.Id, + peerView: PeerView, + onlineMemberCount: Int32?, + stateAndFilterId: (state: ChatListNodeState, filterId: Int32?) + ) { + if stateAndFilterId.state.editing && stateAndFilterId.state.selectedThreadIds.count > 0 { + self.chatTitleComponent = ChatTitleComponent( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + nameDisplayOrder: self.presentationData.nameDisplayOrder, + content: .custom(self.presentationData.strings.ChatList_SelectedTopics(Int32(stateAndFilterId.state.selectedThreadIds.count)), nil, false), + tapped: { + }, + longTapped: { + } + ) + self.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( + content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + pressed: { [weak self] _ in + self?.parentController?.donePressed() + } + ))) + } else { + self.chatTitleComponent = ChatTitleComponent( + context: self.context, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + nameDisplayOrder: self.presentationData.nameDisplayOrder, + content: .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil), + tapped: { [weak self] in + guard let self else { + return + } + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer = peer, let controller = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { + return + } + (self.parentController?.navigationController as? NavigationController)?.pushViewController(controller) + }) + }, + longTapped: { [weak self] in + guard let self else { + return + } + self.parentController?.activateSearch() + } + ) + + self.rightButton = AnyComponentWithIdentity(id: "more", component: AnyComponent(NavigationButtonComponent( + content: .more, + pressed: { [weak self] sourceView in + guard let self, let parentController = self.parentController else { + return + } + ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: parentController, isViewingAsTopics: true, sourceView: sourceView, gesture: nil) + }, + contextAction: { [weak self] sourceView, gesture in + guard let self, let parentController = self.parentController else { + return + } + ChatListControllerImpl.openMoreMenu(context: self.context, peerId: peerId, sourceController: parentController, isViewingAsTopics: true, sourceView: sourceView, gesture: gesture) + } + ))) + } + + if !self.didSetReady { + self.didSetReady = true + self.ready.set(.single(true)) + } + + if let channel = peerView.peers[peerView.peerId] as? TelegramChannel, !channel.flags.contains(.isForum) { + if let parentController = self.parentController, let navigationController = parentController.navigationController as? NavigationController { + let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .peer(id: peerId), subject: nil, botStart: nil, mode: .standard(previewing: false)) + navigationController.replaceController(parentController, with: chatController, animated: true) + } + } else { + self.parentController?.requestUpdateHeaderContent(transition: .immediate) + } + } + + func updatePresentationData(presentationData: PresentationData) { + } + + deinit { + self.titleDisposable?.dispose() + } +} diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 3df33b9ec7..33931cb0ef 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1268,7 +1268,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { guard let strongSelf = self, strongSelf.inlineStackContainerNode != nil else { return [] } - let directions: InteractiveTransitionGestureRecognizerDirections = [.leftCenter, .rightCenter] + let directions: InteractiveTransitionGestureRecognizerDirections = [.rightCenter] return directions }, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0)) inlineContentPanRecognizer.delegate = self @@ -1345,7 +1345,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { } if let directionIsToRight = directionIsToRight, directionIsToRight { - self.setInlineChatList(location: nil) + self.controller?.setInlineChatList(location: nil) } else { self.inlineStackContainerTransitionFraction = 1.0 self.controller?.requestLayout(transition: .animated(duration: 0.4, curve: .spring)) @@ -1482,12 +1482,14 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { return nil } + let effectiveLocation = self.inlineStackContainerNode?.location ?? self.location + var filter: ChatListNodePeersFilter = [] - if case .forum = self.location { + if case .forum = effectiveLocation { filter.insert(.excludeRecent) } - let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, location: location, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in + let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, location: effectiveLocation, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in self?.requestOpenPeerFromSearch?(peer, threadId, dismissSearch) }, openDisabledPeer: { _, _ in }, openRecentPeerOptions: { [weak self] peer in diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 9699c23d7c..2c0e41c32e 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -745,7 +745,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable { index = .chatList( EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: message.index)) } } - return ChatListItem(presentationData: presentationData, context: context, chatListLocation: .chatList(groupId: .root), filterData: nil, index: index, content: .peer(messages: [message], peer: peer, threadInfo: chatThreadInfo, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: []), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) + return ChatListItem(presentationData: presentationData, context: context, chatListLocation: location, filterData: nil, index: index, content: .peer(messages: [message], peer: peer, threadInfo: chatThreadInfo, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, hasUnseenMentions: false, hasUnseenReactions: false, draftState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false, forumTopicData: nil, topForumTopicItems: []), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction) } case let .addContact(phoneNumber, theme, strings): return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: { diff --git a/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift b/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift index 0c12549640..c5246a2db1 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift @@ -34,8 +34,8 @@ private func measureString(_ string: String) -> String { } final class ChatListBadgeNode: ASDisplayNode { - private let backgroundNode: ASImageNode - private let textNode: TextNode + let backgroundNode: ASImageNode + let textNode: TextNode private let measureTextNode: TextNode private var text: String? @@ -43,6 +43,8 @@ final class ChatListBadgeNode: ASDisplayNode { private var isHiddenInternal = false + var disableBounce: Bool = false + override init() { self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true @@ -97,7 +99,7 @@ final class ChatListBadgeNode: ASDisplayNode { } let badgeWidth = max(imageWidth, badgeWidth) - let previousBadgeWidth = !strongSelf.backgroundNode.frame.width.isZero ? strongSelf.backgroundNode.frame.width : badgeWidth + let previousBadgeWidth = !strongSelf.backgroundNode.bounds.width.isZero ? strongSelf.backgroundNode.bounds.width : badgeWidth var animateTextNode = false if animated { @@ -116,14 +118,16 @@ final class ChatListBadgeNode: ASDisplayNode { if currentIsEmpty && !nextIsEmpty { strongSelf.isHiddenInternal = false - if bounce { - strongSelf.layer.animateScale(from: 0.0001, to: 1.2, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in - if let strongSelf = self { - strongSelf.layer.animateScale(from: 1.15, to: 1.0, duration: 0.12, removeOnCompletion: false) - } - }) - } else { - strongSelf.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.2, removeOnCompletion: false) + if !strongSelf.disableBounce { + if bounce { + strongSelf.layer.animateScale(from: 0.0001, to: 1.2, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in + if let strongSelf = self { + strongSelf.layer.animateScale(from: 1.15, to: 1.0, duration: 0.12, removeOnCompletion: false) + } + }) + } else { + strongSelf.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.2, removeOnCompletion: false) + } } } else if !currentIsEmpty && !nextIsEmpty && currentContent?.text != content.text { var animateScale = bounce @@ -134,7 +138,7 @@ final class ChatListBadgeNode: ASDisplayNode { } } - if animateScale { + if animateScale && !strongSelf.disableBounce { strongSelf.layer.animateScale(from: 1.0, to: 1.2, duration: 0.12, removeOnCompletion: false, completion: { [weak self] finished in if let strongSelf = self { strongSelf.layer.animateScale(from: 1.2, to: 1.0, duration: 0.12, removeOnCompletion: false) @@ -157,12 +161,16 @@ final class ChatListBadgeNode: ASDisplayNode { animateTextNode = true } else if !currentIsEmpty && nextIsEmpty && !strongSelf.isHiddenInternal { strongSelf.isHiddenInternal = true - strongSelf.layer.animateScale(from: 1.0, to: 0.0001, duration: 0.12, removeOnCompletion: false, completion: { [weak self] finished in - if let strongSelf = self { - strongSelf.isHidden = true - strongSelf.layer.removeAnimation(forKey: "transform.scale") - } - }) + if !strongSelf.disableBounce { + strongSelf.layer.animateScale(from: 1.0, to: 0.0001, duration: 0.12, removeOnCompletion: false, completion: { [weak self] finished in + if let strongSelf = self { + strongSelf.isHidden = true + strongSelf.layer.removeAnimation(forKey: "transform.scale") + } + }) + } else { + strongSelf.isHidden = true + } } } else { if case .none = content { @@ -183,14 +191,16 @@ final class ChatListBadgeNode: ASDisplayNode { let backgroundFrame = CGRect(x: 0.0, y: 0.0, width: badgeWidth, height: strongSelf.backgroundNode.image?.size.height ?? 0.0) if let (textLayout, _) = textLayoutAndApply { - let badgeTextFrame = CGRect(origin: CGPoint(x: backgroundFrame.midX - textLayout.size.width / 2.0, y: backgroundFrame.minY + 2.0), size: textLayout.size) - strongSelf.textNode.frame = badgeTextFrame + let badgeTextFrame = CGRect(origin: CGPoint(x: backgroundFrame.midX - textLayout.size.width / 2.0, y: backgroundFrame.minY + floorToScreenPixels((backgroundFrame.height - textLayout.size.height) / 2.0)), size: textLayout.size) + strongSelf.textNode.position = badgeTextFrame.center + strongSelf.textNode.bounds = CGRect(origin: CGPoint(), size: badgeTextFrame.size) if animateTextNode { strongSelf.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) strongSelf.textNode.layer.animatePosition(from: CGPoint(x: (previousBadgeWidth - badgeWidth) / 2.0, y: 8.0), to: CGPoint(), duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true) } } - strongSelf.backgroundNode.frame = backgroundFrame + strongSelf.backgroundNode.position = backgroundFrame.center + strongSelf.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size) if animated && badgeWidth != previousBadgeWidth { let previousBackgroundFrame = CGRect(x: 0.0, y: 0.0, width: previousBadgeWidth, height: backgroundFrame.height) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 55ab0e5eb0..dc2282f50d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -816,6 +816,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let statusNode: ChatListStatusNode let badgeNode: ChatListBadgeNode let mentionBadgeNode: ChatListBadgeNode + var avatarBadgeNode: ChatListBadgeNode? + var avatarBadgeBackground: ASImageNode? let onlineNode: PeerOnlineMarkerNode let pinnedIconNode: ASImageNode var secretIconNode: ASImageNode? @@ -1115,9 +1117,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { }) self.contextContainer.shouldBegin = { [weak self] location in - guard let strongSelf = self else { + guard let strongSelf = self, let item = strongSelf.item else { return false } + + if item.interaction.inlineNavigationLocation != nil { + return false + } + if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view { strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.compoundHighlightingNode } else { @@ -1373,6 +1380,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0)) let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) let badgeFont = Font.with(size: floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers]) + let avatarBadgeFont = Font.with(size: 16.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]) let account = item.context.account var messages: [EngineMessage] @@ -1497,6 +1505,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var statusState = ChatListStatusNodeState.none var currentBadgeBackgroundImage: UIImage? + var currentAvatarBadgeBackgroundImage: UIImage? var currentMentionBadgeImage: UIImage? var currentPinnedIconImage: UIImage? var currentMutedIconImage: UIImage? @@ -1547,6 +1556,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } let badgeDiameter = floor(item.presentationData.fontSize.baseDisplaySize * 20.0 / 17.0) + let avatarBadgeDiameter: CGFloat = 22.0 + + let currentAvatarBadgeCleanBackgroundImage: UIImage? = PresentationResourcesChatList.badgeBackgroundBorder(item.presentationData.theme, diameter: avatarBadgeDiameter + 4.0) let leftInset: CGFloat = params.leftInset + avatarLeftInset @@ -1935,17 +1947,21 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if unreadCount.isProvisonal { badgeTextColor = theme.unreadBadgeInactiveBackgroundColor currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactiveProvisional(item.presentationData.theme, diameter: badgeDiameter) + currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactiveProvisional(item.presentationData.theme, diameter: avatarBadgeDiameter) } else { badgeTextColor = theme.unreadBadgeInactiveTextColor currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: badgeDiameter) + currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: avatarBadgeDiameter) } } else { if unreadCount.isProvisonal { badgeTextColor = theme.unreadBadgeActiveBackgroundColor currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActiveProvisional(item.presentationData.theme, diameter: badgeDiameter) + currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActiveProvisional(item.presentationData.theme, diameter: avatarBadgeDiameter) } else { badgeTextColor = theme.unreadBadgeActiveTextColor currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActive(item.presentationData.theme, diameter: badgeDiameter) + currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActive(item.presentationData.theme, diameter: avatarBadgeDiameter) } } let unreadCountText = compactNumericCountString(Int(unreadCount.count), decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator) @@ -2490,6 +2506,61 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } + if let inlineNavigationLocation = item.interaction.inlineNavigationLocation, badgeContent != .none { + var animateIn = false + + let avatarBadgeBackground: ASImageNode + if let current = strongSelf.avatarBadgeBackground { + avatarBadgeBackground = current + } else { + avatarBadgeBackground = ASImageNode() + strongSelf.avatarBadgeBackground = avatarBadgeBackground + strongSelf.avatarNode.addSubnode(avatarBadgeBackground) + } + + avatarBadgeBackground.image = currentAvatarBadgeCleanBackgroundImage + + let avatarBadgeNode: ChatListBadgeNode + if let current = strongSelf.avatarBadgeNode { + avatarBadgeNode = current + } else { + animateIn = true + avatarBadgeNode = ChatListBadgeNode() + avatarBadgeNode.disableBounce = true + strongSelf.avatarBadgeNode = avatarBadgeNode + strongSelf.avatarNode.addSubnode(avatarBadgeNode) + } + + let makeAvatarBadgeLayout = avatarBadgeNode.asyncLayout() + let (avatarBadgeLayout, avatarBadgeApply) = makeAvatarBadgeLayout(CGSize(width: rawContentWidth, height: CGFloat.greatestFiniteMagnitude), avatarBadgeDiameter, avatarBadgeFont, currentAvatarBadgeBackgroundImage, badgeContent) + let _ = avatarBadgeApply(animateBadges, false) + let avatarBadgeFrame = CGRect(origin: CGPoint(x: avatarFrame.width - avatarBadgeLayout.width, y: avatarFrame.height - avatarBadgeLayout.height), size: avatarBadgeLayout) + avatarBadgeNode.position = avatarBadgeFrame.center + avatarBadgeNode.bounds = CGRect(origin: CGPoint(), size: avatarBadgeFrame.size) + + let avatarBadgeBackgroundFrame = avatarBadgeFrame.insetBy(dx: -2.0, dy: -2.0) + avatarBadgeBackground.position = avatarBadgeBackgroundFrame.center + avatarBadgeBackground.bounds = CGRect(origin: CGPoint(), size: avatarBadgeBackgroundFrame.size) + + if animateIn { + ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: avatarBadgeNode, scale: 0.001) + ContainedViewLayoutTransition.immediate.updateTransformScale(layer: avatarBadgeBackground.layer, scale: 0.001) + } + transition.updateSublayerTransformScale(node: avatarBadgeNode, scale: max(0.001, inlineNavigationLocation.progress)) + transition.updateTransformScale(layer: avatarBadgeBackground.layer, scale: max(0.001, inlineNavigationLocation.progress)) + } else if let avatarBadgeNode = strongSelf.avatarBadgeNode { + strongSelf.avatarBadgeNode = nil + transition.updateSublayerTransformScale(node: avatarBadgeNode, scale: 0.001, completion: { [weak avatarBadgeNode] _ in + avatarBadgeNode?.removeFromSupernode() + }) + if let avatarBadgeBackground = strongSelf.avatarBadgeBackground { + strongSelf.avatarBadgeBackground = nil + transition.updateTransformScale(layer: avatarBadgeBackground.layer, scale: 0.001, completion: { [weak avatarBadgeBackground] _ in + avatarBadgeBackground?.removeFromSupernode() + }) + } + } + if let threadInfo = threadInfo { let avatarIconView: ComponentHostView if let current = strongSelf.avatarIconView { diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift index 1721ee5f84..e0e9e08d9b 100644 --- a/submodules/Display/Source/NavigationBar.swift +++ b/submodules/Display/Source/NavigationBar.swift @@ -110,24 +110,24 @@ public final class NavigationBarPresentationData { } } -enum NavigationPreviousAction: Equatable { +public enum NavigationPreviousAction: Equatable { case item(UINavigationItem) case close - static func ==(lhs: NavigationPreviousAction, rhs: NavigationPreviousAction) -> Bool { + public static func ==(lhs: NavigationPreviousAction, rhs: NavigationPreviousAction) -> Bool { switch lhs { - case let .item(lhsItem): - if case let .item(rhsItem) = rhs, lhsItem === rhsItem { - return true - } else { - return false - } - case .close: - if case .close = rhs { - return true - } else { - return false - } + case let .item(lhsItem): + if case let .item(rhsItem) = rhs, lhsItem === rhsItem { + return true + } else { + return false + } + case .close: + if case .close = rhs { + return true + } else { + return false + } } } } @@ -439,7 +439,6 @@ open class BlurredBackgroundView: UIView { } public protocol NavigationBarHeaderView: UIView { - func update(size: CGSize, transition: ContainedViewLayoutTransition) } open class NavigationBar: ASDisplayNode { @@ -657,7 +656,12 @@ open class NavigationBar: ASDisplayNode { self.customHeaderContentView?.removeFromSuperview() if let customHeaderContentView = self.customHeaderContentView { - self.view.addSubview(customHeaderContentView) + self.buttonsContainerNode.view.addSubview(customHeaderContentView) + self.backButtonNode.isHidden = true + self.backButtonArrow.isHidden = true + } else { + self.backButtonNode.isHidden = false + self.backButtonArrow.isHidden = false } } } @@ -708,7 +712,7 @@ open class NavigationBar: ASDisplayNode { } var _previousItem: NavigationPreviousAction? - var previousItem: NavigationPreviousAction? { + public internal(set) var previousItem: NavigationPreviousAction? { get { return self._previousItem } set(value) { @@ -1373,7 +1377,7 @@ open class NavigationBar: ASDisplayNode { if let customHeaderContentView = self.customHeaderContentView { let headerSize = CGSize(width: size.width, height: nominalHeight) - customHeaderContentView.update(size: headerSize, transition: transition) + //customHeaderContentView.update(size: headerSize, transition: transition) transition.updateFrame(view: customHeaderContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentVerticalOrigin), size: headerSize)) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 33268f09cc..d585251543 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -310,6 +310,7 @@ public enum PresentationResourceParameterKey: Hashable { case badgeBackgroundInactiveReactions(CGFloat) case chatListBadgeBackgroundInactiveMention(CGFloat) case chatListBadgeBackgroundPinned(CGFloat) + case badgeBackgroundBorder(CGFloat) case chatBubbleMediaCorner(incoming: Bool, mainRadius: CGFloat, inset: CGFloat) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift index 4c314fc4f5..b4e4fa2c95 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift @@ -235,6 +235,12 @@ public struct PresentationResourcesChatList { }) } + public static func badgeBackgroundBorder(_ theme: PresentationTheme, diameter: CGFloat) -> UIImage? { + return theme.image(PresentationResourceParameterKey.badgeBackgroundBorder(diameter), { theme in + return generateStretchableFilledCircleImage(diameter: diameter, color: theme.chatList.pinnedItemBackgroundColor.blitOver(theme.chatList.backgroundColor, alpha: 1.0)) + }) + } + public static func mutedIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatListMutedIcon.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerMutedIcon"), color: theme.chatList.muteIconColor) diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index f022709509..87854ad68c 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -302,6 +302,7 @@ swift_library( "//submodules/TelegramUI/Components/ChatTitleView", "//submodules/InviteLinksUI:InviteLinksUI", "//submodules/TelegramUI/Components/NotificationPeerExceptionController", + "//submodules/TelegramUI/Components/ChatListHeaderComponent", "//submodules/MediaPasteboardUI:MediaPasteboardUI", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD index 7467c13e9f..ab8fd7628c 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/BUILD @@ -10,9 +10,15 @@ swift_library( "-warnings-as-errors", ], deps = [ - "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", - "//submodules/Display:Display", - "//submodules/ComponentFlow:ComponentFlow", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUI/Components/ChatListTitleView", + "//submodules/AccountContext", + "//submodules/AppBundle", + "//submodules/AsyncDisplayKit", + "//submodules/AnimationUI", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index 561e32fd15..2fa2829f2f 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -2,14 +2,932 @@ import Foundation import UIKit import Display import ComponentFlow +import TelegramPresentationData +import AccountContext +import ChatListTitleView +import AppBundle -/*public final class ChatListHeaderComponent: Component { - public final class View: UIView, NavigationBarHeaderView { - public func update(size: CGSize, transition: ContainedViewLayoutTransition) { - +public final class HeaderNetworkStatusComponent: Component { + public enum Content: Equatable { + case connecting + case updating + } + + public let content: Content + public let theme: PresentationTheme + public let strings: PresentationStrings + + public init( + content: Content, + theme: PresentationTheme, + strings: PresentationStrings + ) { + self.content = content + self.theme = theme + self.strings = strings + } + + public static func ==(lhs: HeaderNetworkStatusComponent, rhs: HeaderNetworkStatusComponent) -> Bool { + if lhs.content != rhs.content { + return false } - + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + return true + } + + public final class View: UIView { + private var component: HeaderNetworkStatusComponent? + private weak var state: EmptyComponentState? + override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: HeaderNetworkStatusComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.state = state + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class ChatListHeaderComponent: Component { + public final class Content: Equatable { + public let title: String + public let titleComponent: AnyComponent? + public let chatListTitle: NetworkStatusTitle? + public let leftButton: AnyComponentWithIdentity? + public let rightButtons: [AnyComponentWithIdentity] + public let backTitle: String? + public let backPressed: (() -> Void)? + + public init( + title: String, + titleComponent: AnyComponent?, + chatListTitle: NetworkStatusTitle?, + leftButton: AnyComponentWithIdentity?, + rightButtons: [AnyComponentWithIdentity], + backTitle: String?, + backPressed: (() -> Void)? + ) { + self.title = title + self.titleComponent = titleComponent + self.chatListTitle = chatListTitle + self.leftButton = leftButton + self.rightButtons = rightButtons + self.backTitle = backTitle + self.backPressed = backPressed + } + + public static func ==(lhs: Content, rhs: Content) -> Bool { + if lhs.title != rhs.title { + return false + } + if lhs.titleComponent != rhs.titleComponent { + return false + } + if lhs.chatListTitle != rhs.chatListTitle { + return false + } + if lhs.leftButton != rhs.leftButton { + return false + } + if lhs.rightButtons != rhs.rightButtons { + return false + } + if lhs.backTitle != rhs.backTitle { + return false + } + return true + } + } + + public let sideInset: CGFloat + public let primaryContent: Content? + public let secondaryContent: Content? + public let secondaryTransition: CGFloat + public let networkStatus: HeaderNetworkStatusComponent.Content? + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + + public let openStatusSetup: (UIView) -> Void + public let toggleIsLocked: () -> Void + + public init( + sideInset: CGFloat, + primaryContent: Content?, + secondaryContent: Content?, + secondaryTransition: CGFloat, + networkStatus: HeaderNetworkStatusComponent.Content?, + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + openStatusSetup: @escaping (UIView) -> Void, + toggleIsLocked: @escaping () -> Void + ) { + self.sideInset = sideInset + self.primaryContent = primaryContent + self.secondaryContent = secondaryContent + self.secondaryTransition = secondaryTransition + self.context = context + self.networkStatus = networkStatus + self.theme = theme + self.strings = strings + self.openStatusSetup = openStatusSetup + self.toggleIsLocked = toggleIsLocked + } + + public static func ==(lhs: ChatListHeaderComponent, rhs: ChatListHeaderComponent) -> Bool { + if lhs.sideInset != rhs.sideInset { + return false + } + if lhs.primaryContent != rhs.primaryContent { + return false + } + if lhs.secondaryContent != rhs.secondaryContent { + return false + } + if lhs.secondaryTransition != rhs.secondaryTransition { + return false + } + if lhs.networkStatus != rhs.networkStatus { + return false + } + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + return true + } + + private final class BackButtonView: HighlightableButton { + private let onPressed: () -> Void + + let arrowView: UIImageView + let titleOffsetContainer: UIView + let titleView: ImmediateTextView + + private var currentColor: UIColor? + + init(onPressed: @escaping () -> Void) { + self.onPressed = onPressed + + self.arrowView = UIImageView() + self.titleOffsetContainer = UIView() + self.titleView = ImmediateTextView() + + super.init(frame: CGRect()) + + self.addSubview(self.arrowView) + + self.addSubview(self.titleOffsetContainer) + self.titleOffsetContainer.addSubview(self.titleView) + + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.alpha = 0.6 + } else { + self.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + } + } + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func pressed() { + self.onPressed() + } + + func update(title: String, theme: PresentationTheme, availableSize: CGSize, transition: Transition) -> CGSize { + self.titleView.attributedText = NSAttributedString(string: title, font: Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor) + let titleSize = self.titleView.updateLayout(CGSize(width: 100.0, height: 44.0)) + + if self.currentColor != theme.rootController.navigationBar.accentTextColor { + self.currentColor = theme.rootController.navigationBar.accentTextColor + self.arrowView.image = NavigationBarTheme.generateBackArrowImage(color: theme.rootController.navigationBar.accentTextColor) + } + + let iconSpacing: CGFloat = 8.0 + let iconOffset: CGFloat = -7.0 + + let arrowSize = self.arrowView.image?.size ?? CGSize(width: 13.0, height: 22.0) + + let arrowFrame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - arrowSize.height) / 2.0)), size: arrowSize) + transition.setPosition(view: self.arrowView, position: arrowFrame.center) + transition.setBounds(view: self.arrowView, bounds: CGRect(origin: CGPoint(), size: arrowFrame.size)) + + transition.setFrame(view: self.titleView, frame: CGRect(origin: CGPoint(x: iconOffset + arrowSize.width + iconSpacing, y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)) + + return CGSize(width: iconOffset + arrowSize.width + iconSpacing + titleSize.width, height: availableSize.height) + } + } + + private final class ContentView: UIView { + let backPressed: () -> Void + let openStatusSetup: (UIView) -> Void + let toggleIsLocked: () -> Void + + let leftButtonOffsetContainer: UIView + var leftButtonViews: [AnyHashable: ComponentView] = [:] + let rightButtonOffsetContainer: UIView + var rightButtonViews: [AnyHashable: ComponentView] = [:] + var backButtonView: BackButtonView? + + let titleOffsetContainer: UIView + let titleTextView: ImmediateTextView + var titleContentView: ComponentView? + var chatListTitleView: ChatListTitleView? + + init( + backPressed: @escaping () -> Void, + openStatusSetup: @escaping (UIView) -> Void, + toggleIsLocked: @escaping () -> Void + ) { + self.backPressed = backPressed + self.openStatusSetup = openStatusSetup + self.toggleIsLocked = toggleIsLocked + + self.leftButtonOffsetContainer = UIView() + self.rightButtonOffsetContainer = UIView() + self.titleOffsetContainer = UIView() + + self.titleTextView = ImmediateTextView() + + super.init(frame: CGRect()) + + self.addSubview(self.titleOffsetContainer) + self.addSubview(self.leftButtonOffsetContainer) + self.addSubview(self.rightButtonOffsetContainer) + + self.titleOffsetContainer.addSubview(self.titleTextView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let view = self.titleContentView?.view, let result = view.hitTest(self.convert(point, to: view), with: event) { + return result + } + if let view = self.chatListTitleView, let result = view.hitTest(self.convert(point, to: view), with: event) { + return result + } + if let backButtonView = self.backButtonView { + if let result = backButtonView.hitTest(self.convert(point, to: backButtonView), with: event) { + return result + } + } + for (_, buttonView) in self.leftButtonViews { + if let view = buttonView.view, let result = view.hitTest(self.convert(point, to: view), with: event) { + return result + } + } + for (_, buttonView) in self.rightButtonViews { + if let view = buttonView.view, let result = view.hitTest(self.convert(point, to: view), with: event) { + return result + } + } + return nil + } + + func updateNavigationTransitionAsPrevious(nextView: ContentView, fraction: CGFloat, transition: Transition, completion: @escaping () -> Void) { + transition.setBounds(view: self.leftButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: fraction * self.bounds.width * 0.5, y: 0.0), size: self.leftButtonOffsetContainer.bounds.size), completion: { _ in + completion() + }) + transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0)) + + if let chatListTitleView = self.chatListTitleView, let nextBackButtonView = nextView.backButtonView { + let titleFrame = chatListTitleView.titleNode.view.convert(chatListTitleView.titleNode.bounds, to: self.titleOffsetContainer) + let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: nextView) + + let totalOffset = titleFrame.midX - backButtonTitleFrame.midX + + transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: totalOffset * fraction, y: 0.0), size: self.titleOffsetContainer.bounds.size)) + transition.setAlpha(view: self.titleOffsetContainer, alpha: (1.0 - fraction)) + } + } + + func updateNavigationTransitionAsNext(previousView: ContentView, fraction: CGFloat, transition: Transition, completion: @escaping () -> Void) { + transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.titleOffsetContainer.bounds.size), completion: { _ in + completion() + }) + transition.setBounds(view: self.rightButtonOffsetContainer, bounds: CGRect(origin: CGPoint(x: -(1.0 - fraction) * self.bounds.width, y: 0.0), size: self.rightButtonOffsetContainer.bounds.size)) + if let backButtonView = self.backButtonView { + transition.setScale(view: backButtonView.arrowView, scale: pow(max(0.001, fraction), 2.0)) + transition.setAlpha(view: backButtonView.arrowView, alpha: pow(fraction, 2.0)) + + if let previousChatListTitleView = previousView.chatListTitleView { + let previousTitleFrame = previousChatListTitleView.titleNode.view.convert(previousChatListTitleView.titleNode.bounds, to: previousView.titleOffsetContainer) + let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self) + + let totalOffset = previousTitleFrame.midX - backButtonTitleFrame.midX + + transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size)) + transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: fraction) + } + } + } + + func update(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, content: Content, backTitle: String?, sideInset: CGFloat, size: CGSize, transition: Transition) { + self.titleTextView.attributedText = NSAttributedString(string: content.title, font: Font.semibold(17.0), textColor: theme.rootController.navigationBar.primaryTextColor) + + let buttonSpacing: CGFloat = 8.0 + + var leftOffset = sideInset + + if let backTitle = backTitle { + var backButtonTransition = transition + let backButtonView: BackButtonView + if let current = self.backButtonView { + backButtonView = current + } else { + backButtonTransition = .immediate + backButtonView = BackButtonView(onPressed: { [weak self] in + guard let self else { + return + } + self.backPressed() + }) + self.backButtonView = backButtonView + self.addSubview(backButtonView) + } + let backButtonSize = backButtonView.update(title: backTitle, theme: theme, availableSize: CGSize(width: 100.0, height: size.height), transition: backButtonTransition) + backButtonTransition.setFrame(view: backButtonView, frame: CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - backButtonSize.height) / 2.0)), size: backButtonSize)) + leftOffset += backButtonSize.width + buttonSpacing + } else if let backButtonView = self.backButtonView { + self.backButtonView = nil + backButtonView.removeFromSuperview() + } + + var validLeftButtons = Set() + if let leftButton = content.leftButton { + validLeftButtons.insert(leftButton.id) + + var buttonTransition = transition + var animateButtonIn = false + let buttonView: ComponentView + if let current = self.leftButtonViews[leftButton.id] { + buttonView = current + } else { + buttonTransition = .immediate + animateButtonIn = true + buttonView = ComponentView() + self.leftButtonViews[leftButton.id] = buttonView + } + let buttonSize = buttonView.update( + transition: buttonTransition, + component: leftButton.component, + environment: { + NavigationButtonComponentEnvironment(theme: theme) + }, + containerSize: CGSize(width: 100.0, height: size.height) + ) + let buttonFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize) + if let buttonComponentView = buttonView.view { + if buttonComponentView.superview == nil { + self.leftButtonOffsetContainer.addSubview(buttonComponentView) + } + buttonTransition.setFrame(view: buttonComponentView, frame: buttonFrame) + if animateButtonIn { + transition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0) + } + } + leftOffset = buttonFrame.maxX + buttonSpacing + } + var removeLeftButtons: [AnyHashable] = [] + for (id, buttonView) in self.leftButtonViews { + if !validLeftButtons.contains(id) { + if let buttonComponentView = buttonView.view { + transition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in + buttonComponentView?.removeFromSuperview() + }) + } + removeLeftButtons.append(id) + } + } + for id in removeLeftButtons { + self.leftButtonViews.removeValue(forKey: id) + } + + var rightOffset = size.width - sideInset + var validRightButtons = Set() + for rightButton in content.rightButtons { + validRightButtons.insert(rightButton.id) + + var buttonTransition = transition + var animateButtonIn = false + let buttonView: ComponentView + if let current = self.rightButtonViews[rightButton.id] { + buttonView = current + } else { + buttonTransition = .immediate + animateButtonIn = true + buttonView = ComponentView() + self.rightButtonViews[rightButton.id] = buttonView + } + let buttonSize = buttonView.update( + transition: buttonTransition, + component: rightButton.component, + environment: { + NavigationButtonComponentEnvironment(theme: theme) + }, + containerSize: CGSize(width: 100.0, height: size.height) + ) + let buttonFrame = CGRect(origin: CGPoint(x: rightOffset - buttonSize.width, y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize) + if let buttonComponentView = buttonView.view { + if buttonComponentView.superview == nil { + self.rightButtonOffsetContainer.addSubview(buttonComponentView) + } + buttonTransition.setFrame(view: buttonComponentView, frame: buttonFrame) + if animateButtonIn { + transition.animateAlpha(view: buttonComponentView, from: 0.0, to: 1.0) + } + } + rightOffset = buttonFrame.minX - buttonSpacing + } + var removeRightButtons: [AnyHashable] = [] + for (id, buttonView) in self.rightButtonViews { + if !validRightButtons.contains(id) { + if let buttonComponentView = buttonView.view { + transition.setAlpha(view: buttonComponentView, alpha: 0.0, completion: { [weak buttonComponentView] _ in + buttonComponentView?.removeFromSuperview() + }) + } + removeRightButtons.append(id) + } + } + for id in removeRightButtons { + self.rightButtonViews.removeValue(forKey: id) + } + + let commonInset: CGFloat = max(leftOffset, size.width - rightOffset) + let remainingWidth = size.width - commonInset * 2.0 + + let titleTextSize = self.titleTextView.updateLayout(CGSize(width: remainingWidth, height: size.height)) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleTextSize.width) / 2.0), y: floor((size.height - titleTextSize.height) / 2.0)), size: titleTextSize) + transition.setFrame(view: self.titleTextView, frame: titleFrame) + + if let titleComponent = content.titleComponent { + var titleContentTransition = transition + let titleContentView: ComponentView + if let current = self.titleContentView { + titleContentView = current + } else { + titleContentTransition = .immediate + titleContentView = ComponentView() + self.titleContentView = titleContentView + } + let titleContentSize = titleContentView.update( + transition: titleContentTransition, + component: titleComponent, + environment: {}, + containerSize: CGSize(width: remainingWidth, height: size.height) + ) + if let titleContentComponentView = titleContentView.view { + if titleContentComponentView.superview == nil { + self.titleOffsetContainer.addSubview(titleContentComponentView) + } + titleContentTransition.setFrame(view: titleContentComponentView, frame: CGRect(origin: CGPoint(x: floor((size.width - titleContentSize.width) / 2.0), y: floor((size.height - titleContentSize.height) / 2.0)), size: titleContentSize)) + } + } else { + if let titleContentView = self.titleContentView { + self.titleContentView = nil + titleContentView.view?.removeFromSuperview() + } + } + + if let chatListTitle = content.chatListTitle { + var chatListTitleTransition = transition + let chatListTitleView: ChatListTitleView + if let current = self.chatListTitleView { + chatListTitleView = current + } else { + chatListTitleTransition = .immediate + chatListTitleView = ChatListTitleView(context: context, theme: theme, strings: strings, animationCache: context.animationCache, animationRenderer: context.animationRenderer) + chatListTitleView.manualLayout = true + self.chatListTitleView = chatListTitleView + self.titleOffsetContainer.addSubview(chatListTitleView) + } + + let chatListTitleContentSize = size + chatListTitleView.setTitle(chatListTitle, animated: false) + chatListTitleView.updateLayout(size: chatListTitleContentSize, clearBounds: CGRect(origin: CGPoint(), size: chatListTitleContentSize), transition: transition.containedViewLayoutTransition) + + chatListTitleView.openStatusSetup = { [weak self] sourceView in + guard let self else { + return + } + self.openStatusSetup(sourceView) + } + chatListTitleView.toggleIsLocked = { [weak self] in + guard let self else { + return + } + self.toggleIsLocked() + } + + chatListTitleTransition.setFrame(view: chatListTitleView, frame: CGRect(origin: CGPoint(x: floor((size.width - chatListTitleContentSize.width) / 2.0), y: floor((size.height - chatListTitleContentSize.height) / 2.0)), size: chatListTitleContentSize)) + } else { + if let chatListTitleView = self.chatListTitleView { + self.chatListTitleView = nil + chatListTitleView.removeFromSuperview() + } + } + + self.titleTextView.isHidden = self.chatListTitleView != nil || self.titleContentView != nil + } + } + + public final class View: UIView, NavigationBarHeaderView { + private var component: ChatListHeaderComponent? + private weak var state: EmptyComponentState? + + private var primaryContentView: ContentView? + private var secondaryContentView: ContentView? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ChatListHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.state = state + + let previousComponent = self.component + self.component = component + + if let primaryContent = component.primaryContent { + var primaryContentTransition = transition + let primaryContentView: ContentView + if let current = self.primaryContentView { + primaryContentView = current + } else { + primaryContentTransition = .immediate + primaryContentView = ContentView( + backPressed: { [weak self] in + guard let self, let component = self.component else { + return + } + component.primaryContent?.backPressed?() + }, + openStatusSetup: { [weak self] sourceView in + guard let self else { + return + } + self.component?.openStatusSetup(sourceView) + }, + toggleIsLocked: { [weak self] in + guard let self else { + return + } + self.component?.toggleIsLocked() + } + ) + self.primaryContentView = primaryContentView + self.addSubview(primaryContentView) + } + primaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: primaryContent, backTitle: primaryContent.backTitle, sideInset: component.sideInset, size: availableSize, transition: primaryContentTransition) + primaryContentTransition.setFrame(view: primaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize)) + } else if let primaryContentView = self.primaryContentView { + self.primaryContentView = nil + primaryContentView.removeFromSuperview() + } + + if let secondaryContent = component.secondaryContent { + var secondaryContentTransition = transition + let secondaryContentView: ContentView + if let current = self.secondaryContentView { + secondaryContentView = current + } else { + secondaryContentTransition = .immediate + secondaryContentView = ContentView( + backPressed: { [weak self] in + guard let self, let component = self.component else { + return + } + component.secondaryContent?.backPressed?() + }, + openStatusSetup: { [weak self] sourceView in + guard let self else { + return + } + self.component?.openStatusSetup(sourceView) + }, + toggleIsLocked: { [weak self] in + guard let self else { + return + } + self.component?.toggleIsLocked() + } + ) + self.secondaryContentView = secondaryContentView + self.addSubview(secondaryContentView) + } + secondaryContentView.update(context: component.context, theme: component.theme, strings: component.strings, content: secondaryContent, backTitle: component.primaryContent?.title, sideInset: component.sideInset, size: availableSize, transition: secondaryContentTransition) + secondaryContentTransition.setFrame(view: secondaryContentView, frame: CGRect(origin: CGPoint(), size: availableSize)) + + if let primaryContentView = self.primaryContentView { + if let previousComponent = previousComponent, previousComponent.secondaryContent == nil { + primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: .immediate, completion: {}) + secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, fraction: 0.0, transition: .immediate, completion: {}) + } + + primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {}) + secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, fraction: component.secondaryTransition, transition: transition, completion: {}) + } + } else if let secondaryContentView = self.secondaryContentView { + self.secondaryContentView = nil + + if let primaryContentView = self.primaryContentView { + primaryContentView.updateNavigationTransitionAsPrevious(nextView: secondaryContentView, fraction: 0.0, transition: transition, completion: {}) + secondaryContentView.updateNavigationTransitionAsNext(previousView: primaryContentView, fraction: 0.0, transition: transition, completion: { [weak secondaryContentView] in + secondaryContentView?.removeFromSuperview() + }) + } else { + secondaryContentView.removeFromSuperview() + } + } + + return availableSize + } + + public func findTitleView() -> ChatListTitleView? { + return self.primaryContentView?.chatListTitleView + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +public final class NavigationButtonComponentEnvironment: Equatable { + public let theme: PresentationTheme + + public init(theme: PresentationTheme) { + self.theme = theme + } + + public static func ==(lhs: NavigationButtonComponentEnvironment, rhs: NavigationButtonComponentEnvironment) -> Bool { + if lhs.theme != rhs.theme { + return false + } + return true + } +} + +public final class NavigationButtonComponent: Component { + public typealias EnvironmentType = NavigationButtonComponentEnvironment + + public enum Content: Equatable { + case text(title: String, isBold: Bool) + case more + case icon(imageName: String) + case proxy(status: ChatTitleProxyStatus) + } + + public let content: Content + public let pressed: (UIView) -> Void + public let contextAction: ((UIView, ContextGesture?) -> Void)? + + public init( + content: Content, + pressed: @escaping (UIView) -> Void, + contextAction: ((UIView, ContextGesture?) -> Void)? = nil + ) { + self.content = content + self.pressed = pressed + self.contextAction = contextAction + } + + public static func ==(lhs: NavigationButtonComponent, rhs: NavigationButtonComponent) -> Bool { + if lhs.content != rhs.content { + return false + } + return true + } + + public final class View: HighlightTrackingButton { + private var textView: ImmediateTextView? + + private var iconView: UIImageView? + private var iconImageName: String? + + private var proxyNode: ChatTitleProxyNode? + + private var moreButton: MoreHeaderButton? + + private var component: NavigationButtonComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + + self.highligthedChanged = { [weak self] highlighted in + guard let self else { + return + } + if highlighted { + self.textView?.alpha = 0.6 + self.proxyNode?.alpha = 0.6 + self.iconView?.alpha = 0.6 + } else { + self.textView?.alpha = 1.0 + self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + + self.proxyNode?.alpha = 1.0 + self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + + self.iconView?.alpha = 1.0 + self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2) + } + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func pressed() { + self.component?.pressed(self) + } + + func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let theme = environment[NavigationButtonComponentEnvironment.self].value.theme + + let iconOffset: CGFloat = 4.0 + + var textString: NSAttributedString? + var imageName: String? + var proxyStatus: ChatTitleProxyStatus? + var isMore: Bool = false + + switch component.content { + case let .text(title, isBold): + textString = NSAttributedString(string: title, font: isBold ? Font.bold(17.0) : Font.regular(17.0), textColor: theme.rootController.navigationBar.accentTextColor) + case .more: + isMore = true + case let .icon(imageNameValue): + imageName = imageNameValue + case let .proxy(status): + proxyStatus = status + } + + var size = CGSize(width: 0.0, height: availableSize.height) + + if let textString = textString { + let textView: ImmediateTextView + if let current = self.textView { + textView = current + } else { + textView = ImmediateTextView() + textView.isUserInteractionEnabled = false + self.textView = textView + self.addSubview(textView) + } + + textView.attributedText = textString + let textSize = textView.updateLayout(availableSize) + size.width = textSize.width + + textView.frame = CGRect(origin: CGPoint(x: 0.0, y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize) + } else if let textView = self.textView { + self.textView = nil + textView.removeFromSuperview() + } + + if let imageName = imageName { + let iconView: UIImageView + if let current = self.iconView { + iconView = current + } else { + iconView = UIImageView() + iconView.isUserInteractionEnabled = false + self.iconView = iconView + self.addSubview(iconView) + } + if self.iconImageName != imageName { + self.iconImageName = imageName + iconView.image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.rootController.navigationBar.accentTextColor) + } + + if let iconSize = iconView.image?.size { + size.width = iconSize.width + + iconView.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize) + } + } else if let iconView = self.iconView { + self.iconView = nil + iconView.removeFromSuperview() + self.iconImageName = nil + } + + if let proxyStatus = proxyStatus { + let proxyNode: ChatTitleProxyNode + if let current = self.proxyNode { + proxyNode = current + } else { + proxyNode = ChatTitleProxyNode(theme: theme) + proxyNode.isUserInteractionEnabled = false + self.proxyNode = proxyNode + self.addSubnode(proxyNode) + } + + let proxySize = CGSize(width: 30.0, height: 30.0) + size.width = proxySize.width + + proxyNode.theme = theme + proxyNode.status = proxyStatus + + proxyNode.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize) + } else if let proxyNode = self.proxyNode { + self.proxyNode = nil + proxyNode.removeFromSupernode() + } + + if isMore { + let moreButton: MoreHeaderButton + if let current = self.moreButton { + moreButton = current + } else { + moreButton = MoreHeaderButton(color: theme.rootController.navigationBar.buttonColor) + moreButton.isUserInteractionEnabled = true + moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor))) + moreButton.onPressed = { [weak self] in + guard let self, let component = self.component else { + return + } + component.pressed(self) + } + moreButton.contextAction = { [weak self] sourceNode, gesture in + guard let self, let component = self.component else { + return + } + component.contextAction?(self, gesture) + } + self.addSubnode(moreButton) + } + + let buttonSize = CGSize(width: 26.0, height: 44.0) + size.width = buttonSize.width + + moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor))) + + moreButton.frame = CGRect(origin: CGPoint(x: iconOffset, y: floor((availableSize.height - buttonSize.height) / 2.0)), size: buttonSize) + } else if let moreButton = self.moreButton { + self.moreButton = nil + moreButton.removeFromSupernode() + } + + return size + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } -*/ diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/MoreHeaderButton.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/MoreHeaderButton.swift new file mode 100644 index 0000000000..df8464588b --- /dev/null +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/MoreHeaderButton.swift @@ -0,0 +1,168 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import AnimationUI + +public final class MoreHeaderButton: HighlightableButtonNode { + public enum Content { + case image(UIImage?) + case more(UIImage?) + } + + public let referenceNode: ContextReferenceContentNode + public let containerNode: ContextControllerSourceNode + private let iconNode: ASImageNode + private var animationNode: AnimationNode? + + public var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? + + private var color: UIColor + + public var onPressed: (() -> Void)? + + public init(color: UIColor) { + self.color = color + + self.referenceNode = ContextReferenceContentNode() + self.containerNode = ContextControllerSourceNode() + self.containerNode.animateScale = false + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + self.iconNode.contentMode = .scaleToFill + + super.init() + + self.containerNode.addSubnode(self.referenceNode) + self.referenceNode.addSubnode(self.iconNode) + self.addSubnode(self.containerNode) + + self.containerNode.shouldBegin = { [weak self] location in + guard let strongSelf = self, let _ = strongSelf.contextAction else { + return false + } + return true + } + self.containerNode.activated = { [weak self] gesture, _ in + guard let strongSelf = self else { + return + } + strongSelf.contextAction?(strongSelf.containerNode, gesture) + } + + self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0)) + self.referenceNode.frame = self.containerNode.bounds + + self.iconNode.image = MoreHeaderButton.optionsCircleImage(color: color) + if let image = self.iconNode.image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0) + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc private func pressed() { + self.onPressed?() + } + + private var content: Content? + public func setContent(_ content: Content, animated: Bool = false) { + if case .more = content, self.animationNode == nil { + let iconColor = self.color + let animationNode = AnimationNode(animation: "anim_profilemore", colors: ["Point 2.Group 1.Fill 1": iconColor, + "Point 3.Group 1.Fill 1": iconColor, + "Point 1.Group 1.Fill 1": iconColor], scale: 1.0) + let animationSize = CGSize(width: 22.0, height: 22.0) + animationNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - animationSize.width) / 2.0), y: floor((self.containerNode.bounds.height - animationSize.height) / 2.0)), size: animationSize) + self.addSubnode(animationNode) + self.animationNode = animationNode + } + if animated { + if let snapshotView = self.referenceNode.view.snapshotContentTree() { + snapshotView.frame = self.referenceNode.frame + self.view.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false) + + self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3) + + self.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.animationNode?.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3) + } + + switch content { + case let .image(image): + if let image = image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.iconNode.image = image + self.iconNode.isHidden = false + self.animationNode?.isHidden = true + case let .more(image): + if let image = image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.iconNode.image = image + self.iconNode.isHidden = false + self.animationNode?.isHidden = false + } + } else { + self.content = content + switch content { + case let .image(image): + if let image = image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.iconNode.image = image + self.iconNode.isHidden = false + self.animationNode?.isHidden = true + case let .more(image): + if let image = image { + self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size) + } + + self.iconNode.image = image + self.iconNode.isHidden = false + self.animationNode?.isHidden = false + } + } + } + + override public func didLoad() { + super.didLoad() + self.view.isOpaque = false + } + + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: 22.0, height: 44.0) + } + + public func onLayout() { + } + + public func play() { + self.animationNode?.playOnce() + } + + public static func optionsCircleImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setStrokeColor(color.cgColor) + let lineWidth: CGFloat = 1.3 + context.setLineWidth(lineWidth) + + context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth, dy: lineWidth)) + }) + } +} diff --git a/submodules/TelegramUI/Components/ChatListTitleView/BUILD b/submodules/TelegramUI/Components/ChatListTitleView/BUILD new file mode 100644 index 0000000000..e4441abff4 --- /dev/null +++ b/submodules/TelegramUI/Components/ChatListTitleView/BUILD @@ -0,0 +1,30 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ChatListTitleView", + module_name = "ChatListTitleView", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/TelegramCore:TelegramCore", + "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/ActivityIndicator:ActivityIndicator", + "//submodules/AccountContext", + "//submodules/ComponentFlow", + "//submodules/AppBundle", + "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", + "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/ChatListUI/Sources/ChatListTitleLockView.swift b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleLockView.swift similarity index 100% rename from submodules/ChatListUI/Sources/ChatListTitleLockView.swift rename to submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleLockView.swift diff --git a/submodules/ChatListUI/Sources/ChatListTitleProxyNode.swift b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleProxyNode.swift similarity index 94% rename from submodules/ChatListUI/Sources/ChatListTitleProxyNode.swift rename to submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleProxyNode.swift index 617d4fea7b..5ec474b50d 100644 --- a/submodules/ChatListUI/Sources/ChatListTitleProxyNode.swift +++ b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleProxyNode.swift @@ -6,7 +6,7 @@ import TelegramPresentationData import ActivityIndicator import AppBundle -enum ChatTitleProxyStatus { +public enum ChatTitleProxyStatus { case connecting case connected case available @@ -35,11 +35,11 @@ private func generateIcon(color: UIColor, connected: Bool, off: Bool) -> UIImage }) } -final class ChatTitleProxyNode: ASDisplayNode { +public final class ChatTitleProxyNode: ASDisplayNode { private let iconNode: ASImageNode private let activityIndicator: ActivityIndicator - var theme: PresentationTheme { + public var theme: PresentationTheme { didSet { if self.theme !== oldValue { switch self.status { @@ -55,7 +55,7 @@ final class ChatTitleProxyNode: ASDisplayNode { } } - var status: ChatTitleProxyStatus = .connected { + public var status: ChatTitleProxyStatus = .connected { didSet { if self.status != oldValue { switch self.status { @@ -73,7 +73,7 @@ final class ChatTitleProxyNode: ASDisplayNode { } } - init(theme: PresentationTheme) { + public init(theme: PresentationTheme) { self.theme = theme self.iconNode = ASImageNode() diff --git a/submodules/ChatListUI/Sources/ChatListTitleView.swift b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift similarity index 89% rename from submodules/ChatListUI/Sources/ChatListTitleView.swift rename to submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift index 786946a4d4..3c03404c1e 100644 --- a/submodules/ChatListUI/Sources/ChatListTitleView.swift +++ b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift @@ -14,24 +14,42 @@ import AccountContext private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]) -struct NetworkStatusTitle: Equatable { - enum Status: Equatable { +public struct NetworkStatusTitle: Equatable { + public enum Status: Equatable { case premium case emoji(PeerEmojiStatus) } - let text: String - let activity: Bool - let hasProxy: Bool - let connectsViaProxy: Bool - let isPasscodeSet: Bool - let isManuallyLocked: Bool - let peerStatus: Status? + public var text: String + public var activity: Bool + public var hasProxy: Bool + public var connectsViaProxy: Bool + public var isPasscodeSet: Bool + public var isManuallyLocked: Bool + public var peerStatus: Status? + + public init( + text: String, + activity: Bool, + hasProxy: Bool, + connectsViaProxy: Bool, + isPasscodeSet: Bool, + isManuallyLocked: Bool, + peerStatus: Status? + ) { + self.text = text + self.activity = activity + self.hasProxy = hasProxy + self.connectsViaProxy = connectsViaProxy + self.isPasscodeSet = isPasscodeSet + self.isManuallyLocked = isManuallyLocked + self.peerStatus = peerStatus + } } -final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitleTransitionNode { +public final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitleTransitionNode { private let context: AccountContext - private let titleNode: ImmediateTextNode + public let titleNode: ImmediateTextNode private let lockView: ChatListTitleLockView private weak var lockSnapshotView: UIView? private let activityIndicator: ActivityIndicator @@ -42,12 +60,14 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer - var openStatusSetup: ((UIView) -> Void)? + public var openStatusSetup: ((UIView) -> Void)? private var validLayout: (CGSize, CGRect)? + public var manualLayout: Bool = false + private var _title: NetworkStatusTitle = NetworkStatusTitle(text: "", activity: false, hasProxy: false, connectsViaProxy: false, isPasscodeSet: false, isManuallyLocked: false, peerStatus: nil) - var title: NetworkStatusTitle { + public var title: NetworkStatusTitle { get { return self._title } @@ -56,7 +76,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl } } - func setTitle(_ title: NetworkStatusTitle, animated: Bool) { + public func setTitle(_ title: NetworkStatusTitle, animated: Bool) { let oldValue = self._title self._title = title @@ -170,17 +190,19 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl } } - self.setNeedsLayout() + if !self.manualLayout { + self.setNeedsLayout() + } } } - var toggleIsLocked: (() -> Void)? - var openProxySettings: (() -> Void)? + public var toggleIsLocked: (() -> Void)? + public var openProxySettings: (() -> Void)? private var isPasscodeSet = false private var isManuallyLocked = false - var theme: PresentationTheme { + public var theme: PresentationTheme { didSet { self.titleNode.attributedText = NSAttributedString(string: self.title.text, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor) @@ -191,13 +213,13 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl } } - var strings: PresentationStrings { + public var strings: PresentationStrings { didSet { self.proxyButton.accessibilityLabel = self.strings.VoiceOver_Navigation_ProxySettings } } - init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) { + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) { self.context = context self.theme = theme self.strings = strings @@ -283,19 +305,19 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl self.proxyButton.addTarget(self, action: #selector(self.proxyButtonPressed), for: .touchUpInside) } - required init?(coder aDecoder: NSCoder) { + required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override func layoutSubviews() { + override public func layoutSubviews() { super.layoutSubviews() - if let (size, clearBounds) = self.validLayout { + if !self.manualLayout, let (size, clearBounds) = self.validLayout { self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) } } - func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) { self.validLayout = (size, clearBounds) var indicatorPadding: CGFloat = 0.0 @@ -409,7 +431,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl self.openProxySettings?() } - func makeTransitionMirrorNode() -> ASDisplayNode { + public func makeTransitionMirrorNode() -> ASDisplayNode { let snapshotView = self.snapshotView(afterScreenUpdates: false) return ASDisplayNode(viewBlock: { @@ -417,17 +439,17 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl }, didLoad: nil) } - func animateLayoutTransition() { + public func animateLayoutTransition() { } - var proxyButtonFrame: CGRect? { + public var proxyButtonFrame: CGRect? { if !self.proxyNode.isHidden { return proxyNode.frame } return nil } - var lockViewFrame: CGRect? { + public var lockViewFrame: CGRect? { if !self.lockView.isHidden && !self.lockView.frame.isEmpty { return self.lockView.frame } else { @@ -435,7 +457,7 @@ final class ChatListTitleView: UIView, NavigationBarTitleView, NavigationBarTitl } } - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if let titleCredibilityIconView = self.titleCredibilityIconView, !titleCredibilityIconView.isHidden, titleCredibilityIconView.alpha != 0.0 { if titleCredibilityIconView.bounds.insetBy(dx: -8.0, dy: -8.0).contains(self.convert(point, to: titleCredibilityIconView)) { if let result = titleCredibilityIconView.hitTest(titleCredibilityIconView.bounds.center, with: event) { diff --git a/submodules/TelegramUI/Components/ChatTitleView/BUILD b/submodules/TelegramUI/Components/ChatTitleView/BUILD index 3ef632edef..26fb6712c8 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/BUILD +++ b/submodules/TelegramUI/Components/ChatTitleView/BUILD @@ -30,6 +30,7 @@ swift_library( "//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", + "//submodules/Components/ComponentDisplayAdapters:ComponentDisplayAdapters", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 0017a5c95a..0e5049fed7 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -21,11 +21,12 @@ import ComponentFlow import EmojiStatusComponent import AnimationCache import MultiAnimationRenderer +import ComponentDisplayAdapters private let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers]) private let subtitleFont = Font.regular(13.0) -public enum ChatTitleContent { +public enum ChatTitleContent: Equatable { public enum ReplyThreadType { case comments case replies @@ -34,6 +35,48 @@ public enum ChatTitleContent { case peer(peerView: PeerView, customTitle: String?, onlineMemberCount: Int32?, isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?) case replyThread(type: ReplyThreadType, count: Int) case custom(String, String?, Bool) + + public static func ==(lhs: ChatTitleContent, rhs: ChatTitleContent) -> Bool { + switch lhs { + case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount): + if case let .peer(rhsPeerView, rhsCustomTitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount) = rhs { + if peerView !== rhsPeerView { + return false + } + if customTitle != rhsCustomTitle { + return false + } + if onlineMemberCount != rhsOnlineMemberCount { + return false + } + if isScheduledMessages != rhsIsScheduledMessages { + return false + } + if isMuted != rhsIsMuted { + return false + } + if customMessageCount != rhsCustomMessageCount { + return false + } + + return true + } else { + return false + } + case let .replyThread(type, count): + if case .replyThread(type, count) = rhs { + return true + } else { + return false + } + case let .custom(title, status, active): + if case .custom(title, status, active) = rhs { + return true + } else { + return false + } + } + } } private enum ChatTitleIcon { @@ -72,6 +115,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { private let button: HighlightTrackingButtonNode + var manualLayout: Bool = false private var validLayout: (CGSize, CGRect)? private var titleLeftIcon: ChatTitleIcon = .none @@ -89,7 +133,9 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } private func updateNetworkStatusNode(networkState: AccountNetworkState, layout: ContainerViewLayout?) { - self.setNeedsLayout() + if self.manualLayout { + self.setNeedsLayout() + } } public var networkState: AccountNetworkState = .online(proxy: nil) { @@ -306,7 +352,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.button.isUserInteractionEnabled = isEnabled if !self.updateStatus() { if updated { - if let (size, clearBounds) = self.validLayout { + if !self.manualLayout, let (size, clearBounds) = self.validLayout { self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.2, curve: .easeInOut)) } } @@ -559,7 +605,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } if self.activityNode.transitionToState(state, animation: .slide) { - if let (size, clearBounds) = self.validLayout { + if !self.manualLayout, let (size, clearBounds) = self.validLayout { self.updateLayout(size: size, clearBounds: clearBounds, transition: .animated(duration: 0.3, curve: .spring)) } return true @@ -642,7 +688,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { override public func layoutSubviews() { super.layoutSubviews() - if let (size, clearBounds) = self.validLayout { + if !self.manualLayout, let (size, clearBounds) = self.validLayout { self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) } } @@ -657,7 +703,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { self.titleContent = titleContent let _ = self.updateStatus() - if let (size, clearBounds) = self.validLayout { + if !self.manualLayout, let (size, clearBounds) = self.validLayout { self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) } } @@ -861,3 +907,121 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -20.0), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) } } + +public final class ChatTitleComponent: Component { + public let context: AccountContext + public let theme: PresentationTheme + public let strings: PresentationStrings + public let dateTimeFormat: PresentationDateTimeFormat + public let nameDisplayOrder: PresentationPersonNameOrder + public let content: ChatTitleContent + public let tapped: () -> Void + public let longTapped: () -> Void + + public init( + context: AccountContext, + theme: PresentationTheme, + strings: PresentationStrings, + dateTimeFormat: PresentationDateTimeFormat, + nameDisplayOrder: PresentationPersonNameOrder, + content: ChatTitleContent, + tapped: @escaping () -> Void, + longTapped: @escaping () -> Void + ) { + self.context = context + self.theme = theme + self.strings = strings + self.dateTimeFormat = dateTimeFormat + self.nameDisplayOrder = nameDisplayOrder + self.content = content + self.tapped = tapped + self.longTapped = longTapped + } + + public static func ==(lhs: ChatTitleComponent, rhs: ChatTitleComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.dateTimeFormat != rhs.dateTimeFormat { + return false + } + if lhs.nameDisplayOrder != rhs.nameDisplayOrder { + return false + } + if lhs.content != rhs.content { + return false + } + return true + } + + public final class View: UIView { + private var contentView: ChatTitleView? + + private var component: ChatTitleComponent? + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: ChatTitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + self.component = component + + let contentView: ChatTitleView + if let current = self.contentView { + contentView = current + } else { + contentView = ChatTitleView( + context: component.context, + theme: component.theme, + strings: component.strings, + dateTimeFormat: component.dateTimeFormat, + nameDisplayOrder: component.nameDisplayOrder, + animationCache: component.context.animationCache, + animationRenderer: component.context.animationRenderer + ) + contentView.pressed = { [weak self] in + guard let self else { + return + } + self.component?.tapped() + } + contentView.longPressed = { [weak self] in + guard let self else { + return + } + self.component?.longTapped() + } + contentView.manualLayout = true + self.contentView = contentView + self.addSubview(contentView) + } + + if contentView.titleContent != component.content { + contentView.titleContent = component.content + } + + contentView.updateLayout(size: availableSize, clearBounds: CGRect(origin: CGPoint(), size: availableSize), transition: transition.containedViewLayoutTransition) + transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: availableSize)) + + return availableSize + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c8f4c11eb7..7a8dd08ba1 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -85,6 +85,7 @@ import ChatTitleView import EmojiStatusComponent import ChatTimerScreen import MediaPasteboardUI +import ChatListHeaderComponent #if DEBUG import os.signpost From 787b8483c4bea73f5174eaceaae67330b70d52b1 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 18 Nov 2022 02:34:08 +0400 Subject: [PATCH 07/12] Fix chat list header hitTest --- .../Sources/ChatListHeaderComponent.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index 2fa2829f2f..496bd0297c 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -296,12 +296,6 @@ public final class ChatListHeaderComponent: Component { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let view = self.titleContentView?.view, let result = view.hitTest(self.convert(point, to: view), with: event) { - return result - } - if let view = self.chatListTitleView, let result = view.hitTest(self.convert(point, to: view), with: event) { - return result - } if let backButtonView = self.backButtonView { if let result = backButtonView.hitTest(self.convert(point, to: backButtonView), with: event) { return result @@ -317,6 +311,12 @@ public final class ChatListHeaderComponent: Component { return result } } + if let view = self.titleContentView?.view, let result = view.hitTest(self.convert(point, to: view), with: event) { + return result + } + if let view = self.chatListTitleView, let result = view.hitTest(self.convert(point, to: view), with: event) { + return result + } return nil } From a98e69cfa01cb095d980bc29d9f12d333123a30e Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 18 Nov 2022 18:17:24 +0400 Subject: [PATCH 08/12] Inline forum improvements --- .../Sources/ChatListController.swift | 19 +++++-- .../Sources/ChatListControllerNode.swift | 2 + .../Sources/Node/ChatListBadgeNode.swift | 2 +- .../Sources/Node/ChatListItem.swift | 54 +++++++++++++------ .../Sources/CallKitIntegration.swift | 6 --- .../Sources/PresentationCall.swift | 10 ++++ .../Sources/ChatListHeaderComponent.swift | 10 +++- .../Sources/OngoingCallThreadLocalContext.mm | 6 +-- 8 files changed, 75 insertions(+), 34 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index cf8fd86738..e967760287 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -366,7 +366,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController case let .known(offset): let isFirstFilter = strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter == strongSelf.chatListDisplayNode.containerNode.availableFilters.first?.filter - if offset <= navigationBarSearchContentHeight + 1.0 && !isFirstFilter { + if offset <= navigationBarSearchContentHeight + 1.0 && strongSelf.chatListDisplayNode.inlineStackContainerNode != nil { + strongSelf.setInlineChatList(location: nil) + } else if offset <= navigationBarSearchContentHeight + 1.0 && !isFirstFilter { let firstFilter = strongSelf.chatListDisplayNode.containerNode.availableFilters.first ?? .all let targetTab: ChatListFilterTabEntryId switch firstFilter { @@ -380,7 +382,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let searchContentNode = strongSelf.searchContentNode { searchContentNode.updateExpansionProgress(1.0, animated: true) } - strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top) + if let inlineStackContainerNode = strongSelf.chatListDisplayNode.inlineStackContainerNode { + inlineStackContainerNode.currentItemNode.scrollToPosition(.top) + } else { + strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top) + } } } } @@ -949,7 +955,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if case let .channel(channel) = peer, channel.flags.contains(.isForum), threadId == nil { strongSelf.chatListDisplayNode.clearHighlightAnimated(true) - strongSelf.setInlineChatList(location: .forum(peerId: channel.id)) + if strongSelf.chatListDisplayNode.inlineStackContainerNode?.location == .forum(peerId: channel.id) { + strongSelf.setInlineChatList(location: nil) + } else { + strongSelf.setInlineChatList(location: .forum(peerId: channel.id)) + } } else { if let threadId = threadId { let _ = strongSelf.context.sharedContext.navigateToForumThread(context: strongSelf.context, peerId: peer.id, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .never).start() @@ -2633,6 +2643,9 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter?.id == updatedFilter?.id { strongSelf.scrollToTop?() } else { + if strongSelf.chatListDisplayNode.inlineStackContainerNode != nil { + strongSelf.setInlineChatList(location: nil) + } strongSelf.chatListDisplayNode.containerNode.switchToFilter(id: updatedFilter.flatMap { .filter($0.id) } ?? .all) } }) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 33931cb0ef..7832dd34cf 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1702,6 +1702,8 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { func scrollToTop() { if let searchDisplayController = self.searchDisplayController { searchDisplayController.contentNode.scrollToTop() + } else if let inlineStackContainerNode = self.inlineStackContainerNode { + inlineStackContainerNode.scrollToTop() } else { self.containerNode.scrollToTop() } diff --git a/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift b/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift index c5246a2db1..e99291b98c 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift @@ -191,7 +191,7 @@ final class ChatListBadgeNode: ASDisplayNode { let backgroundFrame = CGRect(x: 0.0, y: 0.0, width: badgeWidth, height: strongSelf.backgroundNode.image?.size.height ?? 0.0) if let (textLayout, _) = textLayoutAndApply { - let badgeTextFrame = CGRect(origin: CGPoint(x: backgroundFrame.midX - textLayout.size.width / 2.0, y: backgroundFrame.minY + floorToScreenPixels((backgroundFrame.height - textLayout.size.height) / 2.0)), size: textLayout.size) + let badgeTextFrame = CGRect(origin: CGPoint(x: backgroundFrame.midX - textLayout.size.width / 2.0, y: backgroundFrame.minY + UIScreenPixel + floorToScreenPixels((backgroundFrame.height - textLayout.size.height) / 2.0)), size: textLayout.size) strongSelf.textNode.position = badgeTextFrame.center strongSelf.textNode.bounds = CGRect(origin: CGPoint(), size: badgeTextFrame.size) if animateTextNode { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index dc2282f50d..04f2ec1838 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -790,6 +790,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let contextContainer: ContextControllerSourceNode let mainContentContainerNode: ASDisplayNode + let avatarContainerNode: ASDisplayNode let avatarNode: AvatarNode var avatarIconView: ComponentHostView? var avatarIconComponent: EmojiStatusComponent? @@ -1037,6 +1038,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.backgroundNode.isLayerBacked = true self.backgroundNode.displaysAsynchronously = false + self.avatarContainerNode = ASDisplayNode() self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0)) self.highlightedBackgroundNode = ASDisplayNode() @@ -1096,7 +1098,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { self.addSubnode(self.contextContainer) self.contextContainer.addSubnode(self.mainContentContainerNode) - self.contextContainer.addSubnode(self.avatarNode) + self.avatarContainerNode.addSubnode(self.avatarNode) + self.contextContainer.addSubnode(self.avatarContainerNode) self.contextContainer.addSubnode(self.onlineNode) self.mainContentContainerNode.addSubnode(self.titleNode) @@ -1122,10 +1125,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } if item.interaction.inlineNavigationLocation != nil { - return false - } - - if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view { + strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.avatarContainerNode + } else if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view { strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.compoundHighlightingNode } else { strongSelf.contextContainer.targetNodeForActivationProgress = nil @@ -1293,6 +1294,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { reallyHighlighted = true } } + if item.interaction.inlineNavigationLocation != nil { + reallyHighlighted = false + } } return reallyHighlighted } @@ -1944,7 +1948,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } else { let badgeTextColor: UIColor if unreadCount.muted { - if unreadCount.isProvisonal { + if unreadCount.isProvisonal, case .forum = item.chatListLocation { badgeTextColor = theme.unreadBadgeInactiveBackgroundColor currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactiveProvisional(item.presentationData.theme, diameter: badgeDiameter) currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactiveProvisional(item.presentationData.theme, diameter: avatarBadgeDiameter) @@ -1954,7 +1958,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundInactive(item.presentationData.theme, diameter: avatarBadgeDiameter) } } else { - if unreadCount.isProvisonal { + if unreadCount.isProvisonal, case .forum = item.chatListLocation { badgeTextColor = theme.unreadBadgeActiveBackgroundColor currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActiveProvisional(item.presentationData.theme, diameter: badgeDiameter) currentAvatarBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActiveProvisional(item.presentationData.theme, diameter: avatarBadgeDiameter) @@ -2186,9 +2190,18 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { case .chatList: maxTitleLines = 1 } + + var titleLeftCutout: CGFloat = 0.0 + if item.interaction.isInlineMode { + titleLeftCutout = 22.0 + } let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth - let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: maxTitleLines, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + var titleCutout: TextNodeCutout? + if !titleLeftCutout.isZero { + titleCutout = TextNodeCutout(topLeft: CGSize(width: titleLeftCutout, height: 10.0), topRight: nil, bottomRight: nil) + } + let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: maxTitleLines, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: titleCutout, insets: UIEdgeInsets())) var inputActivitiesSize: CGSize? var inputActivitiesApply: (() -> Void)? @@ -2221,8 +2234,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } - let peerRevealOptions: [ItemListRevealOption] - let peerLeftRevealOptions: [ItemListRevealOption] + var peerRevealOptions: [ItemListRevealOption] + var peerLeftRevealOptions: [ItemListRevealOption] switch item.content { case let .peer(_, renderedPeer, _, _, _, presence, _, _, _, _, _, _, displayAsMessage, _, _, _): if !displayAsMessage { @@ -2300,6 +2313,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { peerLeftRevealOptions = [] } + if item.interaction.inlineNavigationLocation != nil { + peerRevealOptions = [] + peerLeftRevealOptions = [] + } + let (onlineLayout, onlineApply) = onlineLayout(online, onlineIsVoiceChat) var animateContent = false if let currentItem = currentItem, currentItem.content.chatLocation == item.content.chatLocation { @@ -2466,7 +2484,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let targetAvatarScaleOffset: CGFloat = -(avatarFrame.width - avatarFrame.width * avatarScale) * 0.5 avatarScaleOffset = targetAvatarScaleOffset * inlineNavigationLocation.progress } - transition.updatePosition(node: strongSelf.avatarNode, position: avatarFrame.center.offsetBy(dx: avatarScaleOffset, dy: 0.0)) + transition.updateFrame(node: strongSelf.avatarContainerNode, frame: avatarFrame) + transition.updatePosition(node: strongSelf.avatarNode, position: avatarFrame.offsetBy(dx: -avatarFrame.minX, dy: -avatarFrame.minY).center.offsetBy(dx: avatarScaleOffset, dy: 0.0)) transition.updateBounds(node: strongSelf.avatarNode, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size)) transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale) strongSelf.avatarNode.updateSize(size: avatarFrame.size) @@ -2610,9 +2629,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } if case .forum = item.index { - strongSelf.avatarNode.isHidden = true + strongSelf.avatarContainerNode.isHidden = true } else { - strongSelf.avatarNode.isHidden = false + strongSelf.avatarContainerNode.isHidden = false } let onlineFrame: CGRect @@ -2623,6 +2642,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } transition.updateFrame(node: strongSelf.onlineNode, frame: onlineFrame) + let onlineInlineNavigationFraction: CGFloat = item.interaction.inlineNavigationLocation?.progress ?? 0.0 + transition.updateAlpha(node: strongSelf.onlineNode, alpha: 1.0 - onlineInlineNavigationFraction) + transition.updateSublayerTransformScale(node: strongSelf.onlineNode, scale: (1.0 - onlineInlineNavigationFraction) * 1.0 + onlineInlineNavigationFraction * 0.001) + let onlineIcon: UIImage? if strongSelf.reallyHighlighted { onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .highlighted, voiceChat: onlineIsVoiceChat) @@ -2726,9 +2749,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } var titleOffset: CGFloat = 0.0 - if item.interaction.isInlineMode { - titleOffset += 22.0 - } if let currentSecretIconImage = currentSecretIconImage { let iconNode: ASImageNode if let current = strongSelf.secretIconNode { @@ -3039,7 +3059,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let currentMutedIconImage = currentMutedIconImage { strongSelf.mutedIconNode.image = currentMutedIconImage strongSelf.mutedIconNode.isHidden = false - transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: titleFrame.minY - 1.0 - UIScreenPixel), size: currentMutedIconImage.size)) + transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: titleFrame.maxY - currentMutedIconImage.size.height + 0.0 + UIScreenPixel), size: currentMutedIconImage.size)) nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0 } else { strongSelf.mutedIconNode.image = nil diff --git a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift index dc78dd3ef6..a17e0eabee 100644 --- a/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift +++ b/submodules/TelegramCallsUI/Sources/CallKitIntegration.swift @@ -260,12 +260,6 @@ class CallKitProviderDelegate: NSObject, CXProviderDelegate { OngoingCallContext.setupAudioSession() - /*do { - try AVAudioSession.sharedInstance().setMode(.voiceChat) - } catch let e { - print("AVAudioSession.sharedInstance().setMode(.voiceChat) error \(e)") - }*/ - self.provider.reportNewIncomingCall(with: uuid, update: update, completion: { error in completion?(error as NSError?) }) diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index e4084d3cf1..ee9d590d12 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -536,6 +536,8 @@ public final class PresentationCallImpl: PresentationCall { } } + #if DEBUG + #else if let audioSessionControl = audioSessionControl, previous == nil || previousControl == nil { if let callKitIntegration = self.callKitIntegration { callKitIntegration.applyVoiceChatOutputMode(outputMode: .custom(self.currentAudioOutputValue)) @@ -544,6 +546,7 @@ public final class PresentationCallImpl: PresentationCall { audioSessionControl.setup(synchronous: true) } } + #endif let mappedVideoState: PresentationCallState.VideoState let mappedRemoteVideoState: PresentationCallState.RemoteVideoState @@ -866,9 +869,13 @@ public final class PresentationCallImpl: PresentationCall { } if tone != self.toneRenderer?.tone { if let tone = tone { + #if DEBUG + let _ = tone + #else let toneRenderer = PresentationCallToneRenderer(tone: tone) self.toneRenderer = toneRenderer toneRenderer.setAudioSessionActive(self.isAudioSessionActive) + #endif } else { self.toneRenderer = nil } @@ -1045,6 +1052,8 @@ public final class PresentationCallImpl: PresentationCall { |> delay(1.0, queue: Queue.mainQueue()) )) + #if DEBUG + #else if let audioSessionControl = self.audioSessionControl { if let callKitIntegration = self.callKitIntegration { callKitIntegration.applyVoiceChatOutputMode(outputMode: .custom(self.currentAudioOutputValue)) @@ -1052,6 +1061,7 @@ public final class PresentationCallImpl: PresentationCall { audioSessionControl.setOutputMode(.custom(output)) } } + #endif } public func debugInfo() -> Signal<(String, String), NoError> { diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index 496bd0297c..2759827ef1 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -326,11 +326,17 @@ public final class ChatListHeaderComponent: Component { }) transition.setAlpha(view: self.rightButtonOffsetContainer, alpha: pow(1.0 - fraction, 2.0)) + if let backButtonView = self.backButtonView { + transition.setBounds(view: backButtonView, bounds: CGRect(origin: CGPoint(x: fraction * self.bounds.width * 0.5, y: 0.0), size: backButtonView.bounds.size), completion: { _ in + completion() + }) + } + if let chatListTitleView = self.chatListTitleView, let nextBackButtonView = nextView.backButtonView { let titleFrame = chatListTitleView.titleNode.view.convert(chatListTitleView.titleNode.bounds, to: self.titleOffsetContainer) let backButtonTitleFrame = nextBackButtonView.convert(nextBackButtonView.titleView.frame, to: nextView) - let totalOffset = titleFrame.midX - backButtonTitleFrame.midX + let totalOffset = titleFrame.minX - backButtonTitleFrame.minX transition.setBounds(view: self.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: totalOffset * fraction, y: 0.0), size: self.titleOffsetContainer.bounds.size)) transition.setAlpha(view: self.titleOffsetContainer, alpha: (1.0 - fraction)) @@ -350,7 +356,7 @@ public final class ChatListHeaderComponent: Component { let previousTitleFrame = previousChatListTitleView.titleNode.view.convert(previousChatListTitleView.titleNode.bounds, to: previousView.titleOffsetContainer) let backButtonTitleFrame = backButtonView.convert(backButtonView.titleView.frame, to: self) - let totalOffset = previousTitleFrame.midX - backButtonTitleFrame.midX + let totalOffset = previousTitleFrame.minX - backButtonTitleFrame.minX transition.setBounds(view: backButtonView.titleOffsetContainer, bounds: CGRect(origin: CGPoint(x: -totalOffset * (1.0 - fraction), y: 0.0), size: backButtonView.titleOffsetContainer.bounds.size)) transition.setAlpha(view: backButtonView.titleOffsetContainer, alpha: fraction) diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index b3ac7582b2..13dbe2c062 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -890,11 +890,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; #ifdef WEBRTC_IOS RTCAudioSessionConfiguration *sharedConfiguration = [RTCAudioSessionConfiguration webRTCConfiguration]; - if (useManualAudioSessionControl) { - sharedConfiguration.mode = AVAudioSessionModeVoiceChat; - } else { - sharedConfiguration.mode = AVAudioSessionModeVoiceChat; - } + sharedConfiguration.mode = AVAudioSessionModeVoiceChat; sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers; sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP; sharedConfiguration.outputNumberOfChannels = 1; From 7338393c592c6bf6ff54b7d1bfac6761a8478591 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 18 Nov 2022 18:32:31 +0400 Subject: [PATCH 09/12] Fix animation --- submodules/ChatListUI/Sources/ChatListController.swift | 2 +- submodules/ChatListUI/Sources/ChatListControllerNode.swift | 3 +++ .../SearchUI/Sources/NavigationBarSearchContentNode.swift | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index e967760287..da1f572850 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1163,7 +1163,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if validLayout.inVoiceOver { offset = .known(0.0) } - searchContentNode.updateListVisibleContentOffset(offset) + searchContentNode.updateListVisibleContentOffset(offset, transition: strongSelf.chatListDisplayNode.temporaryContentOffsetChangeTransition ?? .immediate) } } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 7832dd34cf..271cf2059f 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1162,6 +1162,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { private(set) var inlineStackContainerTransitionFraction: CGFloat = 0.0 private(set) var inlineStackContainerNode: ChatListContainerNode? private var inlineContentPanRecognizer: InteractiveTransitionGestureRecognizer? + private(set) var temporaryContentOffsetChangeTransition: ContainedViewLayoutTransition? private var tapRecognizer: UITapGestureRecognizer? var navigationBar: NavigationBar? @@ -1687,7 +1688,9 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { inlineStackContainerNode?.removeFromSupernode() }) + self.temporaryContentOffsetChangeTransition = transition self.controller?.requestLayout(transition: transition) + self.temporaryContentOffsetChangeTransition = nil } else { inlineStackContainerNode.removeFromSupernode() } diff --git a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift index fda3895d6a..932a7a1e83 100644 --- a/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift +++ b/submodules/SearchUI/Sources/NavigationBarSearchContentNode.swift @@ -54,7 +54,7 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { } } - public func updateListVisibleContentOffset(_ offset: ListViewVisibleContentOffset) { + public func updateListVisibleContentOffset(_ offset: ListViewVisibleContentOffset, transition: ContainedViewLayoutTransition = .immediate) { var progress: CGFloat = 0.0 switch offset { case let .known(offset): @@ -64,7 +64,7 @@ public class NavigationBarSearchContentNode: NavigationBarContentNode { default: break } - self.updateExpansionProgress(progress) + self.updateExpansionProgress(progress, animated: transition.isAnimated) } public func updateGridVisibleContentOffset(_ offset: GridNodeVisibleContentOffset) { From 8687b65acdc5cf72ac98d35f0f345067a44d7542 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 18 Nov 2022 19:35:20 +0400 Subject: [PATCH 10/12] Fix inline chat list theme update --- .../Sources/ChatListController.swift | 106 +++++++----------- .../Sources/ChatListControllerNode.swift | 1 + .../Sources/Node/ChatListItem.swift | 2 +- submodules/Display/Source/TextNode.swift | 2 +- .../Sources/ChatListHeaderComponent.swift | 18 ++- .../Sources/ChatListTitleView.swift | 18 +-- .../ChatTitleView/Sources/ChatTitleView.swift | 25 +++-- 7 files changed, 85 insertions(+), 87 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index da1f572850..75a254d17a 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -852,8 +852,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController ))) }*/ - self.primaryContext?.updatePresentationData(presentationData: self.presentationData) - self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) @@ -4170,7 +4168,6 @@ private final class ChatListLocationContext { let context: AccountContext let location: ChatListControllerLocation weak var parentController: ChatListControllerImpl? - var presentationData: PresentationData private var proxyUnavailableTooltipController: TooltipController? private var didShowProxyUnavailableTooltipController = false @@ -4212,7 +4209,6 @@ private final class ChatListLocationContext { self.context = context self.location = location self.parentController = parentController - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } let hasProxy = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings]) |> map { sharedData -> (Bool, Bool) in @@ -4262,8 +4258,9 @@ private final class ChatListLocationContext { passcode, chatListDisplayNode.containerNode.currentItemState, isReorderingTabs, - peerStatus - ).start(next: { [weak self] networkState, proxy, passcode, stateAndFilterId, isReorderingTabs, peerStatus in + peerStatus, + parentController.updatedPresentationData.1 + ).start(next: { [weak self] networkState, proxy, passcode, stateAndFilterId, isReorderingTabs, peerStatus, presentationData in guard let self else { return } @@ -4273,37 +4270,15 @@ private final class ChatListLocationContext { passcode: passcode, stateAndFilterId: stateAndFilterId, isReorderingTabs: isReorderingTabs, - peerStatus: peerStatus + peerStatus: peerStatus, + presentationData: presentationData ) }) } else { self.didSetReady = true self.ready.set(.single(true)) } - case let .forum(peerId): - //self.navigationItem.titleView = chatTitleView - - /*chatTitleView.pressed = { [weak self] in - guard let self = self else { - return - } - let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) - ) - |> deliverOnMainQueue).start(next: { [weak self] peer in - guard let self = self, let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) else { - return - } - (self.navigationController as? NavigationController)?.pushViewController(controller) - }) - } - self.chatTitleView?.longPressed = { [weak self] in - guard let self else { - return - } - self.activateSearch() - }*/ - + case let .forum(peerId): let peerView = Promise() peerView.set(context.account.viewTracker.peerView(peerId)) @@ -4342,9 +4317,10 @@ private final class ChatListLocationContext { self.titleDisposable = (combineLatest(queue: Queue.mainQueue(), peerView.get(), onlineMemberCount, - chatListDisplayNode.containerNode.currentItemState + chatListDisplayNode.containerNode.currentItemState, + parentController.updatedPresentationData.1 ) - |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, stateAndFilterId in + |> deliverOnMainQueue).start(next: { [weak self] peerView, onlineMemberCount, stateAndFilterId, presentationData in guard let self else { return } @@ -4352,7 +4328,8 @@ private final class ChatListLocationContext { peerId: peerId, peerView: peerView, onlineMemberCount: onlineMemberCount, - stateAndFilterId: stateAndFilterId + stateAndFilterId: stateAndFilterId, + presentationData: presentationData ) }) } @@ -4364,15 +4341,16 @@ private final class ChatListLocationContext { passcode: (Bool, Bool), stateAndFilterId: (state: ChatListNodeState, filterId: Int32?), isReorderingTabs: Bool, - peerStatus: NetworkStatusTitle.Status? + peerStatus: NetworkStatusTitle.Status?, + presentationData: PresentationData ) { let defaultTitle: String switch location { case let .chatList(groupId): if groupId == .root { - defaultTitle = self.presentationData.strings.DialogList_Title + defaultTitle = presentationData.strings.DialogList_Title } else { - defaultTitle = self.presentationData.strings.ChatList_ArchivedChatsTitle + defaultTitle = presentationData.strings.ChatList_ArchivedChatsTitle } case .forum: defaultTitle = "" @@ -4385,7 +4363,7 @@ private final class ChatListLocationContext { if case .chatList(.root) = self.location { self.rightButton = nil } - let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? self.presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle + let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle var animated = false if let (previousEditing, previousNetworkState) = previousEditingAndNetworkState { @@ -4400,7 +4378,7 @@ private final class ChatListLocationContext { self.rightButton = nil } self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + content: .text(title: presentationData.strings.Common_Done, isBold: true), pressed: { [weak self] _ in self?.parentController?.reorderingDonePressed() } @@ -4410,16 +4388,16 @@ private final class ChatListLocationContext { switch networkState { case .waitingForNetwork: - titleContent = NetworkStatusTitle(text: self.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + titleContent = NetworkStatusTitle(text: presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) case let .connecting(proxy): - let text = self.presentationData.strings.State_Connecting + let text = presentationData.strings.State_Connecting let _ = proxy /*if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 { text = self.presentationData.strings.State_ConnectingToProxy }*/ titleContent = NetworkStatusTitle(text: text, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) case .updating: - titleContent = NetworkStatusTitle(text: self.presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) + titleContent = NetworkStatusTitle(text: presentationData.strings.State_Updating, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) case .online: titleContent = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: false, isManuallyLocked: false, peerStatus: peerStatus) } @@ -4430,7 +4408,7 @@ private final class ChatListLocationContext { if isReorderingTabs { self.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + content: .text(title: presentationData.strings.Common_Done, isBold: true), pressed: { [weak self] _ in self?.parentController?.editPressed() } @@ -4446,7 +4424,7 @@ private final class ChatListLocationContext { if isReorderingTabs { self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + content: .text(title: presentationData.strings.Common_Done, isBold: true), pressed: { [weak self] _ in self?.parentController?.reorderingDonePressed() } @@ -4454,14 +4432,14 @@ private final class ChatListLocationContext { } else { if stateAndFilterId.state.editing { self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + content: .text(title: presentationData.strings.Common_Done, isBold: true), pressed: { [weak self] _ in self?.parentController?.donePressed() } ))) } else { self.leftButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), + content: .text(title: presentationData.strings.Common_Edit, isBold: false), pressed: { [weak self] _ in self?.parentController?.editPressed() } @@ -4470,7 +4448,7 @@ private final class ChatListLocationContext { } } else { self.rightButton = AnyComponentWithIdentity(id: "edit", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Edit, isBold: false), + content: .text(title: presentationData.strings.Common_Edit, isBold: false), pressed: { [weak self] _ in self?.parentController?.editPressed() } @@ -4482,9 +4460,9 @@ private final class ChatListLocationContext { var checkProxy = false switch networkState { case .waitingForNetwork: - titleContent = NetworkStatusTitle(text: self.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + titleContent = NetworkStatusTitle(text: presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: false, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) case let .connecting(proxy): - let text = self.presentationData.strings.State_Connecting + let text = presentationData.strings.State_Connecting /*if let layout = strongSelf.validLayout, proxy != nil && layout.metrics.widthClass != .regular && layout.size.width > 320.0 {*/ //text = self.presentationData.strings.State_ConnectingToProxy //} @@ -4493,7 +4471,7 @@ private final class ChatListLocationContext { } titleContent = NetworkStatusTitle(text: text, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) case .updating: - titleContent = NetworkStatusTitle(text: self.presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) + titleContent = NetworkStatusTitle(text: presentationData.strings.State_Updating, activity: true, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) case .online: titleContent = NetworkStatusTitle(text: defaultTitle, activity: false, hasProxy: isRoot && hasProxy, connectsViaProxy: connectsViaProxy, isPasscodeSet: isRoot && isPasscodeSet, isManuallyLocked: isRoot && isManuallyLocked, peerStatus: peerStatus) } @@ -4527,7 +4505,7 @@ private final class ChatListLocationContext { if case .chatList(.root) = self.location, checkProxy { if self.proxyUnavailableTooltipController == nil, !self.didShowProxyUnavailableTooltipController, let parentController = self.parentController, parentController.isNodeLoaded, parentController.displayNode.view.window != nil, parentController.navigationController?.topViewController == nil { self.didShowProxyUnavailableTooltipController = true - let tooltipController = TooltipController(content: .text(self.presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true) + let tooltipController = TooltipController(content: .text(presentationData.strings.Proxy_TooltipUnavailable), baseFontSize: presentationData.listsFontSize.baseDisplaySize, timeout: 60.0, dismissByTapOutside: true) self.proxyUnavailableTooltipController = tooltipController tooltipController.dismissed = { [weak self, weak tooltipController] _ in if let strongSelf = self, let tooltipController = tooltipController, strongSelf.proxyUnavailableTooltipController === tooltipController { @@ -4562,23 +4540,24 @@ private final class ChatListLocationContext { peerId: EnginePeer.Id, peerView: PeerView, onlineMemberCount: Int32?, - stateAndFilterId: (state: ChatListNodeState, filterId: Int32?) + stateAndFilterId: (state: ChatListNodeState, filterId: Int32?), + presentationData: PresentationData ) { if stateAndFilterId.state.editing && stateAndFilterId.state.selectedThreadIds.count > 0 { self.chatTitleComponent = ChatTitleComponent( context: self.context, - theme: self.presentationData.theme, - strings: self.presentationData.strings, - dateTimeFormat: self.presentationData.dateTimeFormat, - nameDisplayOrder: self.presentationData.nameDisplayOrder, - content: .custom(self.presentationData.strings.ChatList_SelectedTopics(Int32(stateAndFilterId.state.selectedThreadIds.count)), nil, false), + theme: presentationData.theme, + strings: presentationData.strings, + dateTimeFormat: presentationData.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, + content: .custom(presentationData.strings.ChatList_SelectedTopics(Int32(stateAndFilterId.state.selectedThreadIds.count)), nil, false), tapped: { }, longTapped: { } ) self.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent( - content: .text(title: self.presentationData.strings.Common_Done, isBold: true), + content: .text(title: presentationData.strings.Common_Done, isBold: true), pressed: { [weak self] _ in self?.parentController?.donePressed() } @@ -4586,10 +4565,10 @@ private final class ChatListLocationContext { } else { self.chatTitleComponent = ChatTitleComponent( context: self.context, - theme: self.presentationData.theme, - strings: self.presentationData.strings, - dateTimeFormat: self.presentationData.dateTimeFormat, - nameDisplayOrder: self.presentationData.nameDisplayOrder, + theme: presentationData.theme, + strings: presentationData.strings, + dateTimeFormat: presentationData.dateTimeFormat, + nameDisplayOrder: presentationData.nameDisplayOrder, content: .peer(peerView: peerView, customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil), tapped: { [weak self] in guard let self else { @@ -4645,9 +4624,6 @@ private final class ChatListLocationContext { } } - func updatePresentationData(presentationData: PresentationData) { - } - deinit { self.titleDisposable?.dispose() } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 271cf2059f..cc9e781ce2 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1363,6 +1363,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.containerNode.updatePresentationData(presentationData) + self.inlineStackContainerNode?.updatePresentationData(presentationData) self.searchDisplayController?.updatePresentationData(presentationData) if let toolbarNode = self.toolbarNode { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 04f2ec1838..30382e6b59 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3021,7 +3021,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.textNode.textNode.recursivelyEnsureDisplaySynchronously(true) } - var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.size.width + 3.0 + titleOffset + var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.trailingLineWidth + 3.0 + titleOffset if let currentCredibilityIconContent = currentCredibilityIconContent { let credibilityIconView: ComponentHostView diff --git a/submodules/Display/Source/TextNode.swift b/submodules/Display/Source/TextNode.swift index 75d5733895..933a8590c8 100644 --- a/submodules/Display/Source/TextNode.swift +++ b/submodules/Display/Source/TextNode.swift @@ -360,7 +360,7 @@ public final class TextNodeLayout: NSObject { public var trailingLineWidth: CGFloat { if let lastLine = self.lines.last { - return lastLine.frame.width + return lastLine.frame.maxX } else { return 0.0 } diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift index 2759827ef1..296906a2de 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListHeaderComponent.swift @@ -548,6 +548,8 @@ public final class ChatListHeaderComponent: Component { let chatListTitleContentSize = size chatListTitleView.setTitle(chatListTitle, animated: false) chatListTitleView.updateLayout(size: chatListTitleContentSize, clearBounds: CGRect(origin: CGPoint(), size: chatListTitleContentSize), transition: transition.containedViewLayoutTransition) + chatListTitleView.theme = theme + chatListTitleView.strings = strings chatListTitleView.openStatusSetup = { [weak self] sourceView in guard let self else { @@ -761,6 +763,7 @@ public final class NavigationButtonComponent: Component { private var moreButton: MoreHeaderButton? private var component: NavigationButtonComponent? + private var theme: PresentationTheme? override init(frame: CGRect) { super.init(frame: frame) @@ -800,6 +803,11 @@ public final class NavigationButtonComponent: Component { self.component = component let theme = environment[NavigationButtonComponentEnvironment.self].value.theme + var themeUpdated = false + if self.theme !== theme { + self.theme = theme + themeUpdated = true + } let iconOffset: CGFloat = 4.0 @@ -852,7 +860,7 @@ public final class NavigationButtonComponent: Component { self.iconView = iconView self.addSubview(iconView) } - if self.iconImageName != imageName { + if self.iconImageName != imageName || themeUpdated { self.iconImageName = imageName iconView.image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.rootController.navigationBar.accentTextColor) } @@ -893,9 +901,14 @@ public final class NavigationButtonComponent: Component { if isMore { let moreButton: MoreHeaderButton - if let current = self.moreButton { + if let current = self.moreButton, !themeUpdated { moreButton = current } else { + if let moreButton = self.moreButton { + moreButton.removeFromSupernode() + self.moreButton = nil + } + moreButton = MoreHeaderButton(color: theme.rootController.navigationBar.buttonColor) moreButton.isUserInteractionEnabled = true moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor))) @@ -911,6 +924,7 @@ public final class NavigationButtonComponent: Component { } component.contextAction?(self, gesture) } + self.moreButton = moreButton self.addSubnode(moreButton) } diff --git a/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift index 3c03404c1e..7548adbca4 100644 --- a/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift +++ b/submodules/TelegramUI/Components/ChatListTitleView/Sources/ChatListTitleView.swift @@ -204,18 +204,22 @@ public final class ChatListTitleView: UIView, NavigationBarTitleView, Navigation public var theme: PresentationTheme { didSet { - self.titleNode.attributedText = NSAttributedString(string: self.title.text, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor) - - self.lockView.updateTheme(self.theme) - - self.activityIndicator.type = .custom(self.theme.rootController.navigationBar.primaryTextColor, 22.0, 1.5, false) - self.proxyNode.theme = self.theme + if self.theme !== oldValue { + self.titleNode.attributedText = NSAttributedString(string: self.title.text, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor) + + self.lockView.updateTheme(self.theme) + + self.activityIndicator.type = .custom(self.theme.rootController.navigationBar.primaryTextColor, 22.0, 1.5, false) + self.proxyNode.theme = self.theme + } } } public var strings: PresentationStrings { didSet { - self.proxyButton.accessibilityLabel = self.strings.VoiceOver_Navigation_ProxySettings + if self.strings !== oldValue { + self.proxyButton.accessibilityLabel = self.strings.VoiceOver_Navigation_ProxySettings + } } } diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 0e5049fed7..8a94554f7a 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -694,17 +694,19 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, hasEmbeddedTitleContent: Bool) { - self.theme = theme - self.hasEmbeddedTitleContent = hasEmbeddedTitleContent - self.strings = strings - - let titleContent = self.titleContent - self.titleCredibilityIcon = .none - self.titleContent = titleContent - let _ = self.updateStatus() - - if !self.manualLayout, let (size, clearBounds) = self.validLayout { - self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + if self.theme !== theme || self.strings !== strings || self.hasEmbeddedTitleContent != hasEmbeddedTitleContent { + self.theme = theme + self.hasEmbeddedTitleContent = hasEmbeddedTitleContent + self.strings = strings + + let titleContent = self.titleContent + self.titleCredibilityIcon = .none + self.titleContent = titleContent + let _ = self.updateStatus() + + if !self.manualLayout, let (size, clearBounds) = self.validLayout { + self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate) + } } } @@ -1009,6 +1011,7 @@ public final class ChatTitleComponent: Component { if contentView.titleContent != component.content { contentView.titleContent = component.content } + contentView.updateThemeAndStrings(theme: component.theme, strings: component.strings, hasEmbeddedTitleContent: false) contentView.updateLayout(size: availableSize, clearBounds: CGRect(origin: CGPoint(), size: availableSize), transition: transition.containedViewLayoutTransition) transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(), size: availableSize)) From 0697f5f8dda0647f747639e0c602a98e48081db1 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 18 Nov 2022 19:35:44 +0400 Subject: [PATCH 11/12] Add ids [skip ci] --- Telegram/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Telegram/BUILD b/Telegram/BUILD index 95ba60873a..964ba86f2d 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -413,6 +413,8 @@ official_apple_pay_merchants = [ "merchant.org.telegram.Best2pay.test", "merchant.psbank.test.telegramios", "merchant.psbank.prod.telegramios", + "merchant.org.telegram.billinenet.test", + "merchant.org.telegram.portmone.test", ] official_bundle_ids = [ From dd37aa968437faf55a397644ef22be4aa396efa4 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 18 Nov 2022 19:44:48 +0400 Subject: [PATCH 12/12] Fix general hide/unhide --- .../Sources/ChatListControllerNode.swift | 1 + .../TelegramCore/Sources/ForumChannels.swift | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index cc9e781ce2..4615477753 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -1640,6 +1640,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { inlineStackContainerNode.deletePeerThread = self.containerNode.deletePeerThread inlineStackContainerNode.setPeerThreadStopped = self.containerNode.setPeerThreadStopped inlineStackContainerNode.setPeerThreadPinned = self.containerNode.setPeerThreadPinned + inlineStackContainerNode.setPeerThreadHidden = self.containerNode.setPeerThreadHidden inlineStackContainerNode.peerSelected = self.containerNode.peerSelected inlineStackContainerNode.groupSelected = self.containerNode.groupSelected inlineStackContainerNode.updatePeerGrouping = self.containerNode.updatePeerGrouping diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 1e9044dae7..afe2d4c9a6 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -380,6 +380,18 @@ func _internal_setForumChannelTopicHidden(account: Account, id: EnginePeer.Id, t return .fail(.generic) } return account.postbox.transaction { transaction -> Api.InputChannel? in + if let initialData = transaction.getMessageHistoryThreadInfo(peerId: id, threadId: threadId)?.data.get(MessageHistoryThreadData.self) { + var data = initialData + + data.isHidden = isHidden + + if data != initialData { + if let entry = StoredMessageHistoryThreadInfo(data) { + transaction.setMessageHistoryThreadInfo(peerId: id, threadId: threadId, info: entry) + } + } + } + return transaction.getPeer(id).flatMap(apiInputChannel) } |> castError(EditForumChannelTopicError.self) @@ -399,11 +411,16 @@ func _internal_setForumChannelTopicHidden(account: Account, id: EnginePeer.Id, t closed: nil, hidden: isHidden ? .boolTrue : .boolFalse )) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } |> mapError { _ -> EditForumChannelTopicError in - return .generic } |> mapToSignal { result -> Signal in - account.stateManager.addUpdates(result) + if let result = result { + account.stateManager.addUpdates(result) + } return account.postbox.transaction { transaction -> Void in if let initialData = transaction.getMessageHistoryThreadInfo(peerId: id, threadId: threadId)?.data.get(MessageHistoryThreadData.self) {